ltbams 0.9.9__py3-none-any.whl → 1.0.2__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.
- ams/__init__.py +4 -11
- ams/_version.py +3 -3
- ams/cases/5bus/pjm5bus_demo.xlsx +0 -0
- ams/cases/5bus/pjm5bus_jumper.xlsx +0 -0
- ams/cases/5bus/pjm5bus_uced.json +1062 -0
- ams/cases/5bus/pjm5bus_uced.xlsx +0 -0
- ams/cases/5bus/pjm5bus_uced_esd1.xlsx +0 -0
- ams/cases/5bus/pjm5bus_uced_ev.xlsx +0 -0
- ams/cases/ieee123/ieee123.xlsx +0 -0
- ams/cases/ieee123/ieee123_regcv1.xlsx +0 -0
- ams/cases/ieee14/ieee14.json +1166 -0
- ams/cases/ieee14/ieee14.raw +92 -0
- ams/cases/ieee14/ieee14_conn.xlsx +0 -0
- ams/cases/ieee14/ieee14_uced.xlsx +0 -0
- ams/cases/ieee39/ieee39.xlsx +0 -0
- ams/cases/ieee39/ieee39_uced.xlsx +0 -0
- ams/cases/ieee39/ieee39_uced_esd1.xlsx +0 -0
- ams/cases/ieee39/ieee39_uced_pvd1.xlsx +0 -0
- ams/cases/ieee39/ieee39_uced_vis.xlsx +0 -0
- ams/cases/matpower/benchmark.json +1594 -0
- ams/cases/matpower/case118.m +787 -0
- ams/cases/matpower/case14.m +129 -0
- ams/cases/matpower/case300.m +1315 -0
- ams/cases/matpower/case39.m +205 -0
- ams/cases/matpower/case5.m +62 -0
- ams/cases/matpower/case_ACTIVSg2000.m +9460 -0
- ams/cases/npcc/npcc.m +644 -0
- ams/cases/npcc/npcc_uced.xlsx +0 -0
- ams/cases/pglib/pglib_opf_case39_epri__api.m +243 -0
- ams/cases/wecc/wecc.m +714 -0
- ams/cases/wecc/wecc_uced.xlsx +0 -0
- ams/cli.py +6 -0
- ams/core/__init__.py +2 -0
- ams/core/documenter.py +652 -0
- ams/core/matprocessor.py +782 -0
- ams/core/model.py +330 -0
- ams/core/param.py +322 -0
- ams/core/service.py +918 -0
- ams/core/symprocessor.py +224 -0
- ams/core/var.py +59 -0
- ams/extension/__init__.py +5 -0
- ams/extension/eva.py +401 -0
- ams/interface.py +1085 -0
- ams/io/__init__.py +133 -0
- ams/io/json.py +82 -0
- ams/io/matpower.py +406 -0
- ams/io/psse.py +6 -0
- ams/io/pypower.py +103 -0
- ams/io/xlsx.py +80 -0
- ams/main.py +81 -4
- ams/models/__init__.py +24 -0
- ams/models/area.py +40 -0
- ams/models/bus.py +52 -0
- ams/models/cost.py +169 -0
- ams/models/distributed/__init__.py +3 -0
- ams/models/distributed/esd1.py +71 -0
- ams/models/distributed/ev.py +60 -0
- ams/models/distributed/pvd1.py +67 -0
- ams/models/group.py +231 -0
- ams/models/info.py +26 -0
- ams/models/line.py +238 -0
- ams/models/renewable/__init__.py +5 -0
- ams/models/renewable/regc.py +119 -0
- ams/models/reserve.py +94 -0
- ams/models/shunt.py +14 -0
- ams/models/static/__init__.py +2 -0
- ams/models/static/gen.py +165 -0
- ams/models/static/pq.py +61 -0
- ams/models/timeslot.py +69 -0
- ams/models/zone.py +49 -0
- ams/opt/__init__.py +12 -0
- ams/opt/constraint.py +175 -0
- ams/opt/exprcalc.py +127 -0
- ams/opt/expression.py +188 -0
- ams/opt/objective.py +174 -0
- ams/opt/omodel.py +432 -0
- ams/opt/optzbase.py +192 -0
- ams/opt/param.py +156 -0
- ams/opt/var.py +233 -0
- ams/pypower/__init__.py +8 -0
- ams/pypower/_compat.py +9 -0
- ams/pypower/core/__init__.py +8 -0
- ams/pypower/core/pips.py +894 -0
- ams/pypower/core/ppoption.py +244 -0
- ams/pypower/core/ppver.py +18 -0
- ams/pypower/core/solver.py +2451 -0
- ams/pypower/eps.py +6 -0
- ams/pypower/idx.py +174 -0
- ams/pypower/io.py +604 -0
- ams/pypower/make/__init__.py +11 -0
- ams/pypower/make/matrices.py +665 -0
- ams/pypower/make/pdv.py +506 -0
- ams/pypower/routines/__init__.py +7 -0
- ams/pypower/routines/cpf.py +513 -0
- ams/pypower/routines/cpf_callbacks.py +114 -0
- ams/pypower/routines/opf.py +1803 -0
- ams/pypower/routines/opffcns.py +1946 -0
- ams/pypower/routines/pflow.py +852 -0
- ams/pypower/toggle.py +1098 -0
- ams/pypower/utils.py +293 -0
- ams/report.py +212 -50
- ams/routines/__init__.py +23 -0
- ams/routines/acopf.py +117 -0
- ams/routines/cpf.py +65 -0
- ams/routines/dcopf.py +241 -0
- ams/routines/dcpf.py +209 -0
- ams/routines/dcpf0.py +196 -0
- ams/routines/dopf.py +150 -0
- ams/routines/ed.py +312 -0
- ams/routines/pflow.py +255 -0
- ams/routines/pflow0.py +113 -0
- ams/routines/routine.py +1033 -0
- ams/routines/rted.py +519 -0
- ams/routines/type.py +160 -0
- ams/routines/uc.py +376 -0
- ams/shared.py +63 -9
- ams/system.py +61 -22
- ams/utils/__init__.py +3 -0
- ams/utils/misc.py +77 -0
- ams/utils/paths.py +257 -0
- docs/Makefile +21 -0
- docs/make.bat +35 -0
- docs/source/_templates/autosummary/base.rst +5 -0
- docs/source/_templates/autosummary/class.rst +35 -0
- docs/source/_templates/autosummary/module.rst +65 -0
- docs/source/_templates/autosummary/module_toctree.rst +66 -0
- docs/source/api.rst +102 -0
- docs/source/conf.py +206 -0
- docs/source/examples/index.rst +34 -0
- docs/source/genmodelref.py +61 -0
- docs/source/genroutineref.py +47 -0
- docs/source/getting_started/copyright.rst +20 -0
- docs/source/getting_started/formats/index.rst +20 -0
- docs/source/getting_started/formats/matpower.rst +183 -0
- docs/source/getting_started/formats/psse.rst +46 -0
- docs/source/getting_started/formats/pypower.rst +223 -0
- docs/source/getting_started/formats/xlsx.png +0 -0
- docs/source/getting_started/formats/xlsx.rst +23 -0
- docs/source/getting_started/index.rst +76 -0
- docs/source/getting_started/install.rst +231 -0
- docs/source/getting_started/overview.rst +26 -0
- docs/source/getting_started/testcase.rst +45 -0
- docs/source/getting_started/verification.rst +13 -0
- docs/source/images/curent.ico +0 -0
- docs/source/images/dcopf_time.png +0 -0
- docs/source/images/sponsors/CURENT_Logo_NameOnTrans.png +0 -0
- docs/source/images/sponsors/CURENT_Logo_Transparent.png +0 -0
- docs/source/images/sponsors/CURENT_Logo_Transparent_Name.png +0 -0
- docs/source/images/sponsors/doe.png +0 -0
- docs/source/index.rst +108 -0
- docs/source/modeling/example.rst +159 -0
- docs/source/modeling/index.rst +17 -0
- docs/source/modeling/model.rst +210 -0
- docs/source/modeling/routine.rst +122 -0
- docs/source/modeling/system.rst +51 -0
- docs/source/release-notes.rst +398 -0
- ltbams-1.0.2.dist-info/METADATA +215 -0
- ltbams-1.0.2.dist-info/RECORD +188 -0
- {ltbams-0.9.9.dist-info → ltbams-1.0.2.dist-info}/WHEEL +1 -1
- ltbams-1.0.2.dist-info/top_level.txt +3 -0
- tests/__init__.py +0 -0
- tests/test_1st_system.py +33 -0
- tests/test_addressing.py +40 -0
- tests/test_andes_mats.py +61 -0
- tests/test_case.py +266 -0
- tests/test_cli.py +34 -0
- tests/test_export_csv.py +89 -0
- tests/test_group.py +83 -0
- tests/test_interface.py +216 -0
- tests/test_io.py +32 -0
- tests/test_jumper.py +27 -0
- tests/test_known_good.py +267 -0
- tests/test_matp.py +437 -0
- tests/test_model.py +54 -0
- tests/test_omodel.py +119 -0
- tests/test_paths.py +22 -0
- tests/test_report.py +251 -0
- tests/test_repr.py +21 -0
- tests/test_routine.py +178 -0
- tests/test_rtn_dcopf.py +101 -0
- tests/test_rtn_dcpf.py +77 -0
- tests/test_rtn_ed.py +279 -0
- tests/test_rtn_pflow.py +219 -0
- tests/test_rtn_rted.py +273 -0
- tests/test_rtn_uc.py +248 -0
- tests/test_service.py +73 -0
- ltbams-0.9.9.dist-info/LICENSE +0 -692
- ltbams-0.9.9.dist-info/METADATA +0 -859
- ltbams-0.9.9.dist-info/RECORD +0 -14
- ltbams-0.9.9.dist-info/top_level.txt +0 -1
- {ltbams-0.9.9.dist-info → ltbams-1.0.2.dist-info}/entry_points.txt +0 -0
ams/routines/acopf.py
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
"""
|
2
|
+
ACOPF routines using PYPOWER.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
from collections import OrderedDict
|
6
|
+
|
7
|
+
from ams.pypower import runopf
|
8
|
+
from ams.pypower.core import ppoption
|
9
|
+
|
10
|
+
from ams.io.pypower import system2ppc
|
11
|
+
from ams.core.param import RParam
|
12
|
+
|
13
|
+
from ams.routines.dcpf0 import DCPF0
|
14
|
+
from ams.opt import Var, Constraint, Objective
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
class ACOPF(DCPF0):
|
20
|
+
"""
|
21
|
+
Standard AC optimal power flow.
|
22
|
+
|
23
|
+
Notes
|
24
|
+
-----
|
25
|
+
1. ACOPF is solved with PYPOWER ``runopf`` function.
|
26
|
+
2. ACOPF formulation in AMS style is NOT DONE YET,
|
27
|
+
but this does not affect the results
|
28
|
+
because the data are passed to PYPOWER for solving.
|
29
|
+
"""
|
30
|
+
|
31
|
+
def __init__(self, system, config):
|
32
|
+
DCPF0.__init__(self, system, config)
|
33
|
+
self.info = 'AC Optimal Power Flow'
|
34
|
+
self.type = 'ACED'
|
35
|
+
|
36
|
+
self.map1 = OrderedDict() # ACOPF does not receive
|
37
|
+
self.map2.update({
|
38
|
+
'vBus': ('Bus', 'v0'),
|
39
|
+
'ug': ('StaticGen', 'u'),
|
40
|
+
'pg': ('StaticGen', 'p0'),
|
41
|
+
})
|
42
|
+
|
43
|
+
# --- params ---
|
44
|
+
self.c2 = RParam(info='Gen cost coefficient 2',
|
45
|
+
name='c2', tex_name=r'c_{2}',
|
46
|
+
unit=r'$/(p.u.^2)', model='GCost',
|
47
|
+
indexer='gen', imodel='StaticGen',
|
48
|
+
nonneg=True)
|
49
|
+
self.c1 = RParam(info='Gen cost coefficient 1',
|
50
|
+
name='c1', tex_name=r'c_{1}',
|
51
|
+
unit=r'$/(p.u.)', model='GCost',
|
52
|
+
indexer='gen', imodel='StaticGen',)
|
53
|
+
self.c0 = RParam(info='Gen cost coefficient 0',
|
54
|
+
name='c0', tex_name=r'c_{0}',
|
55
|
+
unit=r'$', model='GCost',
|
56
|
+
indexer='gen', imodel='StaticGen',
|
57
|
+
no_parse=True)
|
58
|
+
self.qd = RParam(info='reactive demand',
|
59
|
+
name='qd', tex_name=r'q_{d}',
|
60
|
+
model='StaticLoad', src='q0',
|
61
|
+
unit='p.u.',)
|
62
|
+
# --- bus ---
|
63
|
+
self.vBus = Var(info='Bus voltage magnitude',
|
64
|
+
unit='p.u.',
|
65
|
+
name='vBus', tex_name=r'v_{Bus}',
|
66
|
+
src='v', model='Bus',)
|
67
|
+
# --- gen ---
|
68
|
+
self.qg = Var(info='Gen reactive power',
|
69
|
+
unit='p.u.',
|
70
|
+
name='qg', tex_name=r'q_{g}',
|
71
|
+
model='StaticGen', src='q',)
|
72
|
+
# --- constraints ---
|
73
|
+
self.pb = Constraint(name='pb',
|
74
|
+
info='power balance',
|
75
|
+
e_str='sum(pd) - sum(pg)',
|
76
|
+
is_eq=True,)
|
77
|
+
# TODO: ACOPF formulation
|
78
|
+
# --- objective ---
|
79
|
+
self.obj = Objective(name='obj',
|
80
|
+
info='total cost',
|
81
|
+
e_str='sum(c2 * pg**2 + c1 * pg + c0)',
|
82
|
+
sense='min',)
|
83
|
+
|
84
|
+
def solve(self, method=None, **kwargs):
|
85
|
+
"""
|
86
|
+
Solve ACOPF using PYPOWER with PIPS.
|
87
|
+
"""
|
88
|
+
ppc = system2ppc(self.system)
|
89
|
+
ppopt = ppoption()
|
90
|
+
res, sstats = runopf(casedata=ppc, ppopt=ppopt, **kwargs)
|
91
|
+
return res, sstats
|
92
|
+
|
93
|
+
def run(self, **kwargs):
|
94
|
+
"""
|
95
|
+
Run ACOPF using PYPOWER with PIPS.
|
96
|
+
*args and **kwargs go to `self.solve()`, which are not used yet.
|
97
|
+
|
98
|
+
Examples
|
99
|
+
--------
|
100
|
+
>>> ss = ams.load(ams.get_case('matpower/case14.m'))
|
101
|
+
>>> ss.ACOPF.run()
|
102
|
+
|
103
|
+
Parameters
|
104
|
+
----------
|
105
|
+
force_init : bool
|
106
|
+
Force initialization.
|
107
|
+
no_code : bool
|
108
|
+
Disable showing code.
|
109
|
+
method : str
|
110
|
+
Placeholder for future use.
|
111
|
+
|
112
|
+
Returns
|
113
|
+
-------
|
114
|
+
exit_code : int
|
115
|
+
Exit code of the routine.
|
116
|
+
"""
|
117
|
+
super().run(**kwargs)
|
ams/routines/cpf.py
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
"""
|
2
|
+
Continuous power flow routine.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
|
6
|
+
from ams.pypower import runcpf
|
7
|
+
|
8
|
+
from ams.io.pypower import system2ppc
|
9
|
+
from ams.pypower.core import ppoption
|
10
|
+
|
11
|
+
from ams.routines.pflow import PFlow
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class CPF(PFlow):
|
17
|
+
"""
|
18
|
+
Continuous power flow.
|
19
|
+
|
20
|
+
Still under development, not ready for use.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, system, config):
|
24
|
+
PFlow.__init__(self, system, config)
|
25
|
+
self.info = 'AC continuous power flow'
|
26
|
+
self.type = 'PF'
|
27
|
+
|
28
|
+
def solve(self, method=None, **kwargs):
|
29
|
+
"""
|
30
|
+
Solve the CPF using PYPOWER.
|
31
|
+
"""
|
32
|
+
ppc = system2ppc(self.system)
|
33
|
+
ppopt = ppoption()
|
34
|
+
res, success, sstats = runcpf(casedata=ppc, ppopt=ppopt, **kwargs)
|
35
|
+
return res, success, sstats
|
36
|
+
|
37
|
+
# FIXME: unpack results?
|
38
|
+
|
39
|
+
def run(self, force_init=False, no_code=True,
|
40
|
+
method='newton', **kwargs):
|
41
|
+
"""
|
42
|
+
Run continuous power flow using PYPOWER.
|
43
|
+
|
44
|
+
Examples
|
45
|
+
--------
|
46
|
+
>>> ss = ams.load(ams.get_case('matpower/case14.m'))
|
47
|
+
>>> ss.CPF.run()
|
48
|
+
|
49
|
+
Parameters
|
50
|
+
----------
|
51
|
+
force_init : bool
|
52
|
+
Force initialization.
|
53
|
+
no_code : bool
|
54
|
+
Disable showing code.
|
55
|
+
method : str
|
56
|
+
Method for solving the power flow.
|
57
|
+
|
58
|
+
Returns
|
59
|
+
-------
|
60
|
+
exit_code : int
|
61
|
+
Exit code of the routine.
|
62
|
+
"""
|
63
|
+
super().run(force_init=force_init,
|
64
|
+
no_code=no_code, method=method,
|
65
|
+
**kwargs, )
|
ams/routines/dcopf.py
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
"""
|
2
|
+
DCOPF routines.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
|
6
|
+
import numpy as np
|
7
|
+
from ams.core.param import RParam
|
8
|
+
from ams.core.service import NumOp, NumOpDual
|
9
|
+
|
10
|
+
from ams.routines.dcpf import DCPFBase
|
11
|
+
|
12
|
+
from ams.opt import Constraint, Objective, ExpressionCalc, Expression
|
13
|
+
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
class DCOPF(DCPFBase):
|
19
|
+
"""
|
20
|
+
DC optimal power flow (DCOPF).
|
21
|
+
|
22
|
+
The nodal price is calculated as ``pi`` in ``pic``.
|
23
|
+
|
24
|
+
References
|
25
|
+
----------
|
26
|
+
1. R. D. Zimmerman, C. E. Murillo-Sanchez, and R. J. Thomas, “MATPOWER: Steady-State Operations, Planning, and
|
27
|
+
Analysis Tools for Power Systems Research and Education,” IEEE Trans. Power Syst., vol. 26, no. 1, pp. 12–19,
|
28
|
+
Feb. 2011
|
29
|
+
"""
|
30
|
+
|
31
|
+
def __init__(self, system, config):
|
32
|
+
DCPFBase.__init__(self, system, config)
|
33
|
+
self.info = 'DC Optimal Power Flow'
|
34
|
+
self.type = 'DCED'
|
35
|
+
|
36
|
+
# --- Mapping Section ---
|
37
|
+
# --- from map ---
|
38
|
+
self.map1.update({
|
39
|
+
'ug': ('StaticGen', 'u'),
|
40
|
+
})
|
41
|
+
# --- to map ---
|
42
|
+
self.map2.update({
|
43
|
+
'vBus': ('Bus', 'v0'),
|
44
|
+
'ug': ('StaticGen', 'u'),
|
45
|
+
'pg': ('StaticGen', 'p0'),
|
46
|
+
})
|
47
|
+
|
48
|
+
# --- Data Section ---
|
49
|
+
# --- generator cost ---
|
50
|
+
self.c2 = RParam(info='Gen cost coefficient 2',
|
51
|
+
name='c2', tex_name=r'c_{2}',
|
52
|
+
unit=r'$/(p.u.^2)', model='GCost',
|
53
|
+
indexer='gen', imodel='StaticGen',
|
54
|
+
nonneg=True, no_parse=True)
|
55
|
+
self.c1 = RParam(info='Gen cost coefficient 1',
|
56
|
+
name='c1', tex_name=r'c_{1}',
|
57
|
+
unit=r'$/(p.u.)', model='GCost',
|
58
|
+
indexer='gen', imodel='StaticGen',)
|
59
|
+
self.c0 = RParam(info='Gen cost coefficient 0',
|
60
|
+
name='c0', tex_name=r'c_{0}',
|
61
|
+
unit=r'$', model='GCost',
|
62
|
+
indexer='gen', imodel='StaticGen',
|
63
|
+
no_parse=True)
|
64
|
+
# --- generator ---
|
65
|
+
self.ctrl = RParam(info='Gen controllability',
|
66
|
+
name='ctrl', tex_name=r'c_{trl}',
|
67
|
+
model='StaticGen', src='ctrl',
|
68
|
+
no_parse=True)
|
69
|
+
self.ctrle = NumOpDual(info='Effective Gen controllability',
|
70
|
+
name='ctrle', tex_name=r'c_{trl, e}',
|
71
|
+
u=self.ctrl, u2=self.ug,
|
72
|
+
fun=np.multiply, no_parse=True)
|
73
|
+
self.nctrl = NumOp(u=self.ctrl, fun=np.logical_not,
|
74
|
+
name='nctrl', tex_name=r'c_{trl,n}',
|
75
|
+
info='Effective Gen uncontrollability',
|
76
|
+
no_parse=True,)
|
77
|
+
self.nctrle = NumOpDual(info='Effective Gen uncontrollability',
|
78
|
+
name='nctrle', tex_name=r'c_{trl,n,e}',
|
79
|
+
u=self.nctrl, u2=self.ug,
|
80
|
+
fun=np.multiply, no_parse=True)
|
81
|
+
self.pmax = RParam(info='Gen maximum active power',
|
82
|
+
name='pmax', tex_name=r'p_{g, max}',
|
83
|
+
unit='p.u.', model='StaticGen',
|
84
|
+
no_parse=False,)
|
85
|
+
self.pmin = RParam(info='Gen minimum active power',
|
86
|
+
name='pmin', tex_name=r'p_{g, min}',
|
87
|
+
unit='p.u.', model='StaticGen',
|
88
|
+
no_parse=False,)
|
89
|
+
|
90
|
+
# --- line ---
|
91
|
+
self.ul = RParam(info='Line connection status',
|
92
|
+
name='ul', tex_name=r'u_{l}',
|
93
|
+
model='Line', src='u',
|
94
|
+
no_parse=True,)
|
95
|
+
self.rate_a = RParam(info='long-term flow limit',
|
96
|
+
name='rate_a', tex_name=r'R_{ATEA}',
|
97
|
+
unit='p.u.', model='Line',)
|
98
|
+
# --- line angle difference ---
|
99
|
+
self.amax = RParam(model='Line', src='amax',
|
100
|
+
name='amax', tex_name=r'\theta_{bus, max}',
|
101
|
+
info='max line angle difference',
|
102
|
+
no_parse=True,)
|
103
|
+
self.amin = RParam(model='Line', src='amin',
|
104
|
+
name='amin', tex_name=r'\theta_{bus, min}',
|
105
|
+
info='min line angle difference',
|
106
|
+
no_parse=True,)
|
107
|
+
|
108
|
+
# --- Model Section ---
|
109
|
+
self.pmaxe = Expression(info='Effective pmax',
|
110
|
+
name='pmaxe', tex_name=r'p_{g, max, e}',
|
111
|
+
e_str='mul(nctrle, pg0) + mul(ctrle, pmax)',
|
112
|
+
model='StaticGen', src=None, unit='p.u.',)
|
113
|
+
self.pmine = Expression(info='Effective pmin',
|
114
|
+
name='pmine', tex_name=r'p_{g, min, e}',
|
115
|
+
e_str='mul(nctrle, pg0) + mul(ctrle, pmin)',
|
116
|
+
model='StaticGen', src=None, unit='p.u.',)
|
117
|
+
self.pglb = Constraint(name='pglb', info='pg min',
|
118
|
+
e_str='-pg + pmine', is_eq=False,)
|
119
|
+
self.pgub = Constraint(name='pgub', info='pg max',
|
120
|
+
e_str='pg - pmaxe', is_eq=False,)
|
121
|
+
|
122
|
+
# --- line flow ---
|
123
|
+
self.plflb = Constraint(info='line flow lower bound',
|
124
|
+
name='plflb', is_eq=False,
|
125
|
+
e_str='-plf - mul(ul, rate_a)',)
|
126
|
+
self.plfub = Constraint(info='line flow upper bound',
|
127
|
+
name='plfub', is_eq=False,
|
128
|
+
e_str='plf - mul(ul, rate_a)',)
|
129
|
+
self.alflb = Constraint(info='line angle difference lower bound',
|
130
|
+
name='alflb', is_eq=False,
|
131
|
+
e_str='-CftT@aBus + amin',)
|
132
|
+
self.alfub = Constraint(info='line angle difference upper bound',
|
133
|
+
name='alfub', is_eq=False,
|
134
|
+
e_str='CftT@aBus - amax',)
|
135
|
+
# NOTE: in CVXPY, dual_variables returns a list
|
136
|
+
self.pi = ExpressionCalc(info='LMP, dual of <pb>',
|
137
|
+
name='pi', unit='$/p.u.',
|
138
|
+
model='Bus', src=None,
|
139
|
+
e_str='pb.dual_variables[0]')
|
140
|
+
|
141
|
+
# --- objective ---
|
142
|
+
obj = 'sum(mul(c2, pg**2))'
|
143
|
+
obj += '+ sum(mul(c1, pg))'
|
144
|
+
obj += '+ sum(mul(ug, c0))'
|
145
|
+
self.obj = Objective(name='obj',
|
146
|
+
info='total cost', unit='$',
|
147
|
+
sense='min', e_str=obj,)
|
148
|
+
|
149
|
+
def dc2ac(self, kloss=1.0, **kwargs):
|
150
|
+
"""
|
151
|
+
Convert the RTED results with ACOPF.
|
152
|
+
|
153
|
+
Parameters
|
154
|
+
----------
|
155
|
+
kloss : float, optional
|
156
|
+
The loss factor for the conversion. Defaults to 1.2.
|
157
|
+
"""
|
158
|
+
exec_time = self.exec_time
|
159
|
+
if self.exec_time == 0 or self.exit_code != 0:
|
160
|
+
logger.warning(f'{self.class_name} is not executed successfully, quit conversion.')
|
161
|
+
return False
|
162
|
+
|
163
|
+
# --- ACOPF ---
|
164
|
+
# scale up load
|
165
|
+
pq_idx = self.system.StaticLoad.get_all_idxes()
|
166
|
+
pd0 = self.system.StaticLoad.get(src='p0', attr='v', idx=pq_idx).copy()
|
167
|
+
qd0 = self.system.StaticLoad.get(src='q0', attr='v', idx=pq_idx).copy()
|
168
|
+
self.system.StaticLoad.set(src='p0', idx=pq_idx, attr='v', value=pd0 * kloss)
|
169
|
+
self.system.StaticLoad.set(src='q0', idx=pq_idx, attr='v', value=qd0 * kloss)
|
170
|
+
# run ACOPF
|
171
|
+
ACOPF = self.system.ACOPF
|
172
|
+
ACOPF.run()
|
173
|
+
# scale load back
|
174
|
+
self.system.StaticLoad.set(src='p0', idx=pq_idx, attr='v', value=pd0)
|
175
|
+
self.system.StaticLoad.set(src='q0', idx=pq_idx, attr='v', value=qd0)
|
176
|
+
if not ACOPF.exit_code == 0:
|
177
|
+
logger.warning('<ACOPF> did not converge, conversion failed.')
|
178
|
+
# NOTE: mock results to fit interface with ANDES
|
179
|
+
self.vBus = ACOPF.vBus
|
180
|
+
self.vBus.optz.value = np.ones(self.system.Bus.n)
|
181
|
+
self.aBus.optz.value = np.zeros(self.system.Bus.n)
|
182
|
+
return False
|
183
|
+
self.pg.optz.value = ACOPF.pg.v
|
184
|
+
|
185
|
+
# NOTE: mock results to fit interface with ANDES
|
186
|
+
self.vBus.optz.value = ACOPF.vBus.v
|
187
|
+
self.aBus.optz.value = ACOPF.aBus.v
|
188
|
+
self.exec_time = exec_time
|
189
|
+
|
190
|
+
# --- set status ---
|
191
|
+
self.system.recent = self
|
192
|
+
self.converted = True
|
193
|
+
logger.warning(f'<{self.class_name}> converted to AC.')
|
194
|
+
return True
|
195
|
+
|
196
|
+
def run(self, **kwargs):
|
197
|
+
"""
|
198
|
+
Run the routine.
|
199
|
+
|
200
|
+
Following kwargs go to `self.init()`: `force`, `force_mats`, `force_constr`, `force_om`.
|
201
|
+
|
202
|
+
Following kwargs go to `self.solve()`: `solver`, `verbose`, `gp`, `qcp`, `requires_grad`,
|
203
|
+
`enforce_dpp`, `ignore_dpp`, `method`, and all rest.
|
204
|
+
|
205
|
+
Parameters
|
206
|
+
----------
|
207
|
+
force : bool, optional
|
208
|
+
If True, force re-initialization. Defaults to False.
|
209
|
+
force_mats : bool, optional
|
210
|
+
If True, force re-generating matrices. Defaults to False.
|
211
|
+
force_constr : bool, optional
|
212
|
+
Whether to turn on all constraints.
|
213
|
+
force_om : bool, optional
|
214
|
+
If True, force re-generating optimization model. Defaults to False.
|
215
|
+
solver: str, optional
|
216
|
+
The solver to use. For example, 'GUROBI', 'ECOS', 'SCS', or 'OSQP'.
|
217
|
+
verbose : bool, optional
|
218
|
+
Overrides the default of hiding solver output and prints logging
|
219
|
+
information describing CVXPY's compilation process.
|
220
|
+
gp : bool, optional
|
221
|
+
If True, parses the problem as a disciplined geometric program
|
222
|
+
instead of a disciplined convex program.
|
223
|
+
qcp : bool, optional
|
224
|
+
If True, parses the problem as a disciplined quasiconvex program
|
225
|
+
instead of a disciplined convex program.
|
226
|
+
requires_grad : bool, optional
|
227
|
+
Makes it possible to compute gradients of a solution with respect to Parameters
|
228
|
+
by calling problem.backward() after solving, or to compute perturbations to the variables
|
229
|
+
given perturbations to Parameters by calling problem.derivative().
|
230
|
+
Gradients are only supported for DCP and DGP problems, not quasiconvex problems.
|
231
|
+
When computing gradients (i.e., when this argument is True), the problem must satisfy the DPP rules.
|
232
|
+
enforce_dpp : bool, optional
|
233
|
+
When True, a DPPError will be thrown when trying to solve a
|
234
|
+
non-DPP problem (instead of just a warning).
|
235
|
+
Only relevant for problems involving Parameters. Defaults to False.
|
236
|
+
ignore_dpp : bool, optional
|
237
|
+
When True, DPP problems will be treated as non-DPP, which may speed up compilation. Defaults to False.
|
238
|
+
method : function, optional
|
239
|
+
A custom solve method to use.
|
240
|
+
"""
|
241
|
+
return super().run(**kwargs)
|
ams/routines/dcpf.py
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
"""
|
2
|
+
Power flow routines.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
|
6
|
+
from ams.opt import Var, Constraint, Expression, Objective
|
7
|
+
from ams.routines.routine import RoutineBase
|
8
|
+
|
9
|
+
from ams.core.param import RParam
|
10
|
+
from ams.core.service import VarSelect
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class DCPFBase(RoutineBase):
|
16
|
+
"""
|
17
|
+
Base class for DC power flow.
|
18
|
+
"""
|
19
|
+
|
20
|
+
def __init__(self, system, config):
|
21
|
+
RoutineBase.__init__(self, system, config)
|
22
|
+
|
23
|
+
self.ug = RParam(info='Gen connection status',
|
24
|
+
name='ug', tex_name=r'u_{g}',
|
25
|
+
model='StaticGen', src='u',
|
26
|
+
no_parse=True)
|
27
|
+
self.pg0 = RParam(info='Gen initial active power',
|
28
|
+
name='pg0', tex_name=r'p_{g, 0}',
|
29
|
+
unit='p.u.', model='StaticGen',
|
30
|
+
src='p0', no_parse=False,)
|
31
|
+
# --- shunt ---
|
32
|
+
self.gsh = RParam(info='shunt conductance',
|
33
|
+
name='gsh', tex_name=r'g_{sh}',
|
34
|
+
model='Shunt', src='g',
|
35
|
+
no_parse=True,)
|
36
|
+
|
37
|
+
self.buss = RParam(info='Bus slack',
|
38
|
+
name='buss', tex_name=r'B_{us,s}',
|
39
|
+
model='Slack', src='bus',
|
40
|
+
no_parse=True,)
|
41
|
+
# --- load ---
|
42
|
+
self.pd = RParam(info='active demand',
|
43
|
+
name='pd', tex_name=r'p_{d}',
|
44
|
+
model='StaticLoad', src='p0',
|
45
|
+
unit='p.u.',)
|
46
|
+
|
47
|
+
# --- connection matrix ---
|
48
|
+
self.Cg = RParam(info='Gen connection matrix',
|
49
|
+
name='Cg', tex_name=r'C_{g}',
|
50
|
+
model='mats', src='Cg',
|
51
|
+
no_parse=True, sparse=True,)
|
52
|
+
self.Cl = RParam(info='Load connection matrix',
|
53
|
+
name='Cl', tex_name=r'C_{l}',
|
54
|
+
model='mats', src='Cl',
|
55
|
+
no_parse=True, sparse=True,)
|
56
|
+
self.CftT = RParam(info='Transpose of line connection matrix',
|
57
|
+
name='CftT', tex_name=r'C_{ft}^T',
|
58
|
+
model='mats', src='CftT',
|
59
|
+
no_parse=True, sparse=True,)
|
60
|
+
self.Csh = RParam(info='Shunt connection matrix',
|
61
|
+
name='Csh', tex_name=r'C_{sh}',
|
62
|
+
model='mats', src='Csh',
|
63
|
+
no_parse=True, sparse=True,)
|
64
|
+
|
65
|
+
# --- system matrix ---
|
66
|
+
self.Bbus = RParam(info='Bus admittance matrix',
|
67
|
+
name='Bbus', tex_name=r'B_{bus}',
|
68
|
+
model='mats', src='Bbus',
|
69
|
+
no_parse=True, sparse=True,)
|
70
|
+
self.Bf = RParam(info='Bf matrix',
|
71
|
+
name='Bf', tex_name=r'B_{f}',
|
72
|
+
model='mats', src='Bf',
|
73
|
+
no_parse=True, sparse=True,)
|
74
|
+
self.Pbusinj = RParam(info='Bus power injection vector',
|
75
|
+
name='Pbusinj', tex_name=r'P_{bus}^{inj}',
|
76
|
+
model='mats', src='Pbusinj',
|
77
|
+
no_parse=True,)
|
78
|
+
self.Pfinj = RParam(info='Line power injection vector',
|
79
|
+
name='Pfinj', tex_name=r'P_{f}^{inj}',
|
80
|
+
model='mats', src='Pfinj',
|
81
|
+
no_parse=True,)
|
82
|
+
|
83
|
+
# --- generation ---
|
84
|
+
self.pg = Var(info='Gen active power',
|
85
|
+
unit='p.u.',
|
86
|
+
name='pg', tex_name=r'p_g',
|
87
|
+
model='StaticGen', src='p',
|
88
|
+
v0=self.pg0)
|
89
|
+
|
90
|
+
# --- bus ---
|
91
|
+
self.vBus = Var(info='Bus voltage magnitude, placeholder',
|
92
|
+
unit='p.u.',
|
93
|
+
name='vBus', tex_name=r'v_{Bus}',
|
94
|
+
src='v', model='Bus',)
|
95
|
+
self.aBus = Var(info='Bus voltage angle',
|
96
|
+
unit='rad',
|
97
|
+
name='aBus', tex_name=r'\theta_{bus}',
|
98
|
+
model='Bus', src='a',)
|
99
|
+
|
100
|
+
# --- power balance ---
|
101
|
+
pb = 'Bbus@aBus + Pbusinj + Cl@pd + Csh@gsh - Cg@pg'
|
102
|
+
self.pb = Constraint(name='pb', info='power balance',
|
103
|
+
e_str=pb, is_eq=True,)
|
104
|
+
|
105
|
+
# --- bus ---
|
106
|
+
self.csb = VarSelect(info='select slack bus',
|
107
|
+
name='csb', tex_name=r'c_{sb}',
|
108
|
+
u=self.aBus, indexer='buss',
|
109
|
+
no_parse=True,)
|
110
|
+
self.sba = Constraint(info='align slack bus angle',
|
111
|
+
name='sbus', is_eq=True,
|
112
|
+
e_str='csb@aBus',)
|
113
|
+
|
114
|
+
# --- line flow ---
|
115
|
+
self.plf = Expression(info='Line flow',
|
116
|
+
name='plf', tex_name=r'p_{lf}',
|
117
|
+
unit='p.u.',
|
118
|
+
e_str='Bf@aBus + Pfinj',
|
119
|
+
model='Line', src=None,)
|
120
|
+
|
121
|
+
def solve(self, **kwargs):
|
122
|
+
"""
|
123
|
+
Solve the routine optimization model.
|
124
|
+
args and kwargs go to `self.om.prob.solve()` (`cvxpy.Problem.solve()`).
|
125
|
+
"""
|
126
|
+
return self.om.prob.solve(**kwargs)
|
127
|
+
|
128
|
+
def unpack(self, **kwargs):
|
129
|
+
"""
|
130
|
+
Unpack the results from CVXPY model.
|
131
|
+
"""
|
132
|
+
# --- solver Var results to routine algeb ---
|
133
|
+
for _, var in self.vars.items():
|
134
|
+
# --- copy results from routine algeb into system algeb ---
|
135
|
+
if var.model is None: # if no owner
|
136
|
+
continue
|
137
|
+
if var.src is None: # if no source
|
138
|
+
continue
|
139
|
+
else:
|
140
|
+
try:
|
141
|
+
idx = var.owner.get_all_idxes()
|
142
|
+
except AttributeError:
|
143
|
+
idx = var.owner.idx.v
|
144
|
+
|
145
|
+
# NOTE: only unpack the variables that are in the model or group
|
146
|
+
try:
|
147
|
+
var.owner.set(src=var.src, idx=idx, attr='v', value=var.v)
|
148
|
+
except (KeyError, TypeError):
|
149
|
+
logger.error(f'Failed to unpack <{var}> to <{var.owner.class_name}>.')
|
150
|
+
|
151
|
+
# --- solver ExpressionCalc results to routine algeb ---
|
152
|
+
for _, exprc in self.exprcs.items():
|
153
|
+
if exprc.model is None:
|
154
|
+
continue
|
155
|
+
if exprc.src is None:
|
156
|
+
continue
|
157
|
+
else:
|
158
|
+
try:
|
159
|
+
idx = exprc.owner.get_all_idxes()
|
160
|
+
except AttributeError:
|
161
|
+
idx = exprc.owner.idx.v
|
162
|
+
|
163
|
+
try:
|
164
|
+
exprc.owner.set(src=exprc.src, idx=idx, attr='v', value=exprc.v)
|
165
|
+
except (KeyError, TypeError):
|
166
|
+
logger.error(f'Failed to unpack <{exprc}> to <{exprc.owner.class_name}>.')
|
167
|
+
|
168
|
+
# label the most recent solved routine
|
169
|
+
self.system.recent = self.system.routines[self.class_name]
|
170
|
+
return True
|
171
|
+
|
172
|
+
def _post_solve(self):
|
173
|
+
"""
|
174
|
+
Post-solve calculations.
|
175
|
+
"""
|
176
|
+
# NOTE: unpack Expressions if owner and arc are available
|
177
|
+
for expr in self.exprs.values():
|
178
|
+
if expr.owner and expr.src:
|
179
|
+
expr.owner.set(src=expr.src, attr='v',
|
180
|
+
idx=expr.get_all_idxes(), value=expr.v)
|
181
|
+
return True
|
182
|
+
|
183
|
+
|
184
|
+
class DCPF(DCPFBase):
|
185
|
+
"""
|
186
|
+
DC power flow.
|
187
|
+
"""
|
188
|
+
|
189
|
+
def __init__(self, system, config):
|
190
|
+
DCPFBase.__init__(self, system, config)
|
191
|
+
self.info = 'DC Power Flow'
|
192
|
+
self.type = 'PF'
|
193
|
+
|
194
|
+
self.genpv = RParam(info='gen of PV',
|
195
|
+
name='genpv', tex_name=r'g_{DG}',
|
196
|
+
model='PV', src='idx',
|
197
|
+
no_parse=True,)
|
198
|
+
self.cpv = VarSelect(u=self.pg, indexer='genpv',
|
199
|
+
name='cpv', tex_name=r'C_{PV}',
|
200
|
+
info='Select PV from pg',
|
201
|
+
no_parse=True,)
|
202
|
+
|
203
|
+
self.pvb = Constraint(name='pvb', info='PV generator',
|
204
|
+
e_str='cpv @ (pg - mul(ug, pg0))',
|
205
|
+
is_eq=True,)
|
206
|
+
|
207
|
+
self.obj = Objective(name='obj',
|
208
|
+
info='place holder', unit='$',
|
209
|
+
sense='min', e_str='0',)
|