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.
- 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 +203 -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 +234 -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.2a1.dist-info/METADATA +210 -0
- ltbams-1.0.2a1.dist-info/RECORD +188 -0
- {ltbams-0.9.9.dist-info → ltbams-1.0.2a1.dist-info}/WHEEL +1 -1
- ltbams-1.0.2a1.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.2a1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
"""
|
2
|
+
Distributed energy storage model.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from andes.core.param import NumParam
|
6
|
+
|
7
|
+
from ams.core.model import Model
|
8
|
+
|
9
|
+
from ams.models.distributed.pvd1 import PVD1Data
|
10
|
+
|
11
|
+
|
12
|
+
class ESD1Data(PVD1Data):
|
13
|
+
"""
|
14
|
+
ESD1 model data.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def __init__(self):
|
18
|
+
PVD1Data.__init__(self)
|
19
|
+
|
20
|
+
self.SOCmin = NumParam(default=0.0, tex_name='SOC_{min}',
|
21
|
+
info='Minimum required value for SOC in limiter',
|
22
|
+
)
|
23
|
+
|
24
|
+
self.SOCmax = NumParam(default=1.0, tex_name='SOC_{max}',
|
25
|
+
info='Maximum allowed value for SOC in limiter',
|
26
|
+
)
|
27
|
+
|
28
|
+
self.SOCinit = NumParam(default=0.5, tex_name='SOC_{init}',
|
29
|
+
info='Initial state of charge',
|
30
|
+
)
|
31
|
+
|
32
|
+
self.En = NumParam(default=100.0, tex_name='E_n',
|
33
|
+
info='Rated energy capacity',
|
34
|
+
unit="MWh"
|
35
|
+
)
|
36
|
+
|
37
|
+
self.EtaC = NumParam(default=1.0, tex_name='Eta_C',
|
38
|
+
info='Efficiency during charging',
|
39
|
+
vrange=(0, 1),
|
40
|
+
)
|
41
|
+
|
42
|
+
self.EtaD = NumParam(default=1.0, tex_name='Eta_D',
|
43
|
+
info='Efficiency during discharging',
|
44
|
+
vrange=(0, 1),
|
45
|
+
)
|
46
|
+
|
47
|
+
|
48
|
+
class ESD1(ESD1Data, Model):
|
49
|
+
"""
|
50
|
+
Distributed energy storage model, revised from ANDES ``ESD1`` model for
|
51
|
+
scheduling.
|
52
|
+
|
53
|
+
Following parameters are omitted from the original dynamic model:
|
54
|
+
``fn``, ``busf``, ``xc``, ``pqflag``, ``igreg``, ``v0``, ``v1``,
|
55
|
+
``dqdv``, ``fdbd``, ``ddn``, ``ialim``, ``vt0``, ``vt1``, ``vt2``,
|
56
|
+
``vt3``, ``vrflag``, ``ft0``, ``ft1``, ``ft2``, ``ft3``, ``frflag``,
|
57
|
+
``tip``, ``tiq``, ``recflag``.
|
58
|
+
|
59
|
+
Reference:
|
60
|
+
|
61
|
+
[1] ANDES Documentation, ESD1
|
62
|
+
|
63
|
+
Available:
|
64
|
+
|
65
|
+
https://docs.andes.app/en/latest/groupdoc/DG.html#esd1
|
66
|
+
"""
|
67
|
+
|
68
|
+
def __init__(self, system, config):
|
69
|
+
ESD1Data.__init__(self)
|
70
|
+
Model.__init__(self, system, config)
|
71
|
+
self.group = 'DG'
|
@@ -0,0 +1,60 @@
|
|
1
|
+
"""
|
2
|
+
EV model.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from andes.core.param import NumParam, IdxParam
|
6
|
+
from andes.core.model import ModelData
|
7
|
+
|
8
|
+
from ams.core.model import Model
|
9
|
+
|
10
|
+
|
11
|
+
class EV1(ModelData, Model):
|
12
|
+
"""
|
13
|
+
Aggregated EV model for scheduling at transmission level.
|
14
|
+
|
15
|
+
For co-simulation with ADNES, it is expected to be used in
|
16
|
+
conjunction with the dynamic models `EV1` or `EV2`.
|
17
|
+
|
18
|
+
Reference:
|
19
|
+
|
20
|
+
[1] J. Wang et al., "Electric Vehicles Charging Time Constrained Deliverable Provision of Secondary
|
21
|
+
Frequency Regulation," in IEEE Transactions on Smart Grid, doi: 10.1109/TSG.2024.3356948.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(self, system, config):
|
25
|
+
ModelData.__init__(self)
|
26
|
+
Model.__init__(self, system, config)
|
27
|
+
self.group = 'DG'
|
28
|
+
|
29
|
+
self.bus = IdxParam(model='Bus',
|
30
|
+
info="interface bus idx",
|
31
|
+
mandatory=True,
|
32
|
+
)
|
33
|
+
self.gen = IdxParam(info="static generator index",
|
34
|
+
mandatory=True,
|
35
|
+
)
|
36
|
+
self.Sn = NumParam(default=100.0, tex_name='S_n',
|
37
|
+
info='device MVA rating',
|
38
|
+
unit='MVA',
|
39
|
+
)
|
40
|
+
self.gammap = NumParam(default=1.0,
|
41
|
+
info="P ratio of linked static gen",
|
42
|
+
tex_name=r'\gamma_P'
|
43
|
+
)
|
44
|
+
self.gammaq = NumParam(default=1.0,
|
45
|
+
info="Q ratio of linked static gen",
|
46
|
+
tex_name=r'\gamma_Q'
|
47
|
+
)
|
48
|
+
self.N = NumParam(default=10000,
|
49
|
+
info="number of related EVs",
|
50
|
+
tex_name='N'
|
51
|
+
)
|
52
|
+
|
53
|
+
|
54
|
+
class EV2(EV1):
|
55
|
+
"""
|
56
|
+
EV aggregation model at transmission level, identical to :ref:`EV1`.
|
57
|
+
"""
|
58
|
+
|
59
|
+
def __init__(self, system=None, config=None) -> None:
|
60
|
+
EV1.__init__(self, system, config)
|
@@ -0,0 +1,67 @@
|
|
1
|
+
"""
|
2
|
+
Distributed PV models.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from andes.core.param import IdxParam, NumParam
|
6
|
+
from andes.core.model import ModelData
|
7
|
+
|
8
|
+
from ams.core.model import Model
|
9
|
+
|
10
|
+
|
11
|
+
class PVD1Data(ModelData):
|
12
|
+
"""
|
13
|
+
PVD1 model data.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(self):
|
17
|
+
ModelData.__init__(self)
|
18
|
+
|
19
|
+
self.bus = IdxParam(model='Bus',
|
20
|
+
info="interface bus id (place holder)",
|
21
|
+
mandatory=True,
|
22
|
+
)
|
23
|
+
|
24
|
+
self.gen = IdxParam(info="static generator index",
|
25
|
+
mandatory=True,
|
26
|
+
)
|
27
|
+
|
28
|
+
self.Sn = NumParam(default=100.0, tex_name='S_n',
|
29
|
+
info='device MVA rating',
|
30
|
+
unit='MVA',
|
31
|
+
)
|
32
|
+
|
33
|
+
self.gammap = NumParam(default=1.0, tex_name=r'\gamma_p',
|
34
|
+
info='Ratio of ESD1.pref0 w.r.t to that of static PV',
|
35
|
+
vrange='(0, 1]',
|
36
|
+
)
|
37
|
+
|
38
|
+
self.gammaq = NumParam(default=1.0, tex_name=r'\gamma_q',
|
39
|
+
info='Ratio of ESD1.qref0 w.r.t to that of static PV',
|
40
|
+
vrange='(0, 1]',
|
41
|
+
)
|
42
|
+
|
43
|
+
|
44
|
+
class PVD1(PVD1Data, Model):
|
45
|
+
"""
|
46
|
+
Distributed PV model, revised from ANDES ``PVD1`` model for
|
47
|
+
scheduling.
|
48
|
+
|
49
|
+
Following parameters are omitted from the original dynamic model:
|
50
|
+
``fn``, ``busf``, ``xc``, ``pqflag``, ``igreg``, ``v0``, ``v1``,
|
51
|
+
``dqdv``, ``fdbd``, ``ddn``, ``ialim``, ``vt0``, ``vt1``, ``vt2``,
|
52
|
+
``vt3``, ``vrflag``, ``ft0``, ``ft1``, ``ft2``, ``ft3``, ``frflag``,
|
53
|
+
``tip``, ``tiq``, ``recflag``.
|
54
|
+
|
55
|
+
Reference:
|
56
|
+
|
57
|
+
[1] ANDES Documentation, PVD1
|
58
|
+
|
59
|
+
Available:
|
60
|
+
|
61
|
+
https://docs.andes.app/en/latest/groupdoc/DG.html#pvd1
|
62
|
+
"""
|
63
|
+
|
64
|
+
def __init__(self, system, config):
|
65
|
+
PVD1Data.__init__(self)
|
66
|
+
Model.__init__(self, system, config)
|
67
|
+
self.group = 'DG'
|
ams/models/group.py
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from andes.models.group import GroupBase as adGroupBase
|
4
|
+
from andes.core.service import BackRef
|
5
|
+
|
6
|
+
logger = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
|
9
|
+
class GroupBase(adGroupBase):
|
10
|
+
"""
|
11
|
+
Base class for groups.
|
12
|
+
|
13
|
+
Add common_vars and common_params to the group class.
|
14
|
+
|
15
|
+
This class is revised from ``andes.models.group.GroupBase``.
|
16
|
+
"""
|
17
|
+
|
18
|
+
def __init__(self):
|
19
|
+
super().__init__()
|
20
|
+
self.common_params.extend(('idx',))
|
21
|
+
|
22
|
+
def get_idx(self):
|
23
|
+
"""
|
24
|
+
Return the value of group idx sorted in a human-readable style.
|
25
|
+
|
26
|
+
Notes
|
27
|
+
-----
|
28
|
+
This function sorts the idx values using a custom sorting key,
|
29
|
+
which handles varying length strings with letters and numbers.
|
30
|
+
"""
|
31
|
+
all_idx = [mdl.idx.v for mdl in self.models.values()]
|
32
|
+
flat_list = [item for sublist in all_idx for item in sublist]
|
33
|
+
return flat_list
|
34
|
+
|
35
|
+
def _check_src(self, src: str):
|
36
|
+
"""
|
37
|
+
Helper function for checking if ``src`` is a shared field.
|
38
|
+
|
39
|
+
The requirement is not strictly enforced and is only for debugging purposed.
|
40
|
+
|
41
|
+
Disable debug logging in dispath modeling.
|
42
|
+
"""
|
43
|
+
if src not in self.common_vars + self.common_params:
|
44
|
+
logger.debug(f'Group <{self.class_name}> does not share property <{src}>.')
|
45
|
+
|
46
|
+
def __repr__(self):
|
47
|
+
dev_text = 'device' if self.n == 1 else 'devices'
|
48
|
+
return f'{self.class_name} ({self.n} {dev_text}) at {hex(id(self))}'
|
49
|
+
|
50
|
+
def __setattr__(self, key, value):
|
51
|
+
if hasattr(value, 'owner'):
|
52
|
+
if value.owner is None:
|
53
|
+
value.owner = self
|
54
|
+
if hasattr(value, 'name'):
|
55
|
+
if value.name is None:
|
56
|
+
value.name = key
|
57
|
+
|
58
|
+
if isinstance(value, BackRef):
|
59
|
+
self.services_ref[key] = value
|
60
|
+
|
61
|
+
super().__setattr__(key, value)
|
62
|
+
|
63
|
+
def set_backref(self, name, from_idx, to_idx):
|
64
|
+
"""
|
65
|
+
Set idxes to ``BackRef``, and set them to models.
|
66
|
+
"""
|
67
|
+
|
68
|
+
uid = self.idx2uid(to_idx)
|
69
|
+
self.services_ref[name].v[uid].append(from_idx)
|
70
|
+
|
71
|
+
model = self.idx2model(to_idx)
|
72
|
+
model.set_backref(name, from_idx, to_idx)
|
73
|
+
|
74
|
+
|
75
|
+
class Undefined(GroupBase):
|
76
|
+
"""
|
77
|
+
The undefined group. Holds models with no ``group``.
|
78
|
+
"""
|
79
|
+
|
80
|
+
def __init__(self):
|
81
|
+
super().__init__()
|
82
|
+
|
83
|
+
|
84
|
+
class ACTopology(GroupBase):
|
85
|
+
def __init__(self):
|
86
|
+
super().__init__()
|
87
|
+
self.common_vars.extend(('a', 'v'))
|
88
|
+
|
89
|
+
|
90
|
+
class RenGen(GroupBase):
|
91
|
+
"""
|
92
|
+
Renewable generator (converter) group.
|
93
|
+
|
94
|
+
See ANDES Documentation SynGen here for the notes on replacing StaticGen and setting the power
|
95
|
+
ratio parameters.
|
96
|
+
|
97
|
+
Reference:
|
98
|
+
|
99
|
+
[1] ANDES Documentation, RenGen, [Online]
|
100
|
+
|
101
|
+
Available:
|
102
|
+
|
103
|
+
https://docs.andes.app/en/latest/groupdoc/RenGen.html#rengen
|
104
|
+
"""
|
105
|
+
|
106
|
+
def __init__(self):
|
107
|
+
super().__init__()
|
108
|
+
self.common_params.extend(('bus', 'gen', 'Sn', 'q0'))
|
109
|
+
self.common_vars.extend(('Pe', 'Qe'))
|
110
|
+
|
111
|
+
|
112
|
+
class VSG(GroupBase):
|
113
|
+
"""
|
114
|
+
Renewable generator with virtual synchronous generator (VSG) control group.
|
115
|
+
|
116
|
+
Note that this is a group separate from ``RenGen`` for VSG scheduling study.
|
117
|
+
"""
|
118
|
+
|
119
|
+
def __init__(self):
|
120
|
+
super().__init__()
|
121
|
+
self.common_params.extend(('bus', 'gen', 'Sn'))
|
122
|
+
self.common_vars.extend(('Pe', 'Qe'))
|
123
|
+
|
124
|
+
|
125
|
+
class DG(GroupBase):
|
126
|
+
"""
|
127
|
+
Distributed generation (small-scale).
|
128
|
+
|
129
|
+
See ANDES Documentation SynGen here for the notes on replacing StaticGen and setting the power
|
130
|
+
ratio parameters.
|
131
|
+
|
132
|
+
Reference:
|
133
|
+
|
134
|
+
[1] ANDES Documentation, SynGen, [Online]
|
135
|
+
|
136
|
+
Available:
|
137
|
+
|
138
|
+
https://docs.andes.app/en/latest/groupdoc/SynGen.html#syngen
|
139
|
+
"""
|
140
|
+
|
141
|
+
def __init__(self):
|
142
|
+
super().__init__()
|
143
|
+
self.common_params.extend(('bus', 'fn'))
|
144
|
+
|
145
|
+
|
146
|
+
class Cost(GroupBase):
|
147
|
+
def __init__(self):
|
148
|
+
super().__init__()
|
149
|
+
self.common_params.extend(('gen',))
|
150
|
+
|
151
|
+
|
152
|
+
class Collection(GroupBase):
|
153
|
+
"""
|
154
|
+
Collection of topology models
|
155
|
+
"""
|
156
|
+
|
157
|
+
def __init__(self):
|
158
|
+
super().__init__()
|
159
|
+
|
160
|
+
|
161
|
+
class Horizon(GroupBase):
|
162
|
+
"""
|
163
|
+
Time horizon group.
|
164
|
+
"""
|
165
|
+
|
166
|
+
def __init__(self):
|
167
|
+
super().__init__()
|
168
|
+
|
169
|
+
|
170
|
+
class Reserve(GroupBase):
|
171
|
+
def __init__(self):
|
172
|
+
super().__init__()
|
173
|
+
self.common_params.extend(('zone',))
|
174
|
+
|
175
|
+
|
176
|
+
class StaticGen(GroupBase):
|
177
|
+
"""
|
178
|
+
Generator group.
|
179
|
+
|
180
|
+
Notes
|
181
|
+
-----
|
182
|
+
For co-simulation with ANDES, check
|
183
|
+
`ANDES StaticGen <https://docs.andes.app/en/latest/groupdoc/StaticGen.html#staticgen>`_
|
184
|
+
for replacing static generators with dynamic generators.
|
185
|
+
"""
|
186
|
+
|
187
|
+
def __init__(self):
|
188
|
+
super().__init__()
|
189
|
+
self.common_params.extend(('bus', 'Sn', 'Vn', 'p0', 'q0', 'ra', 'xs', 'subidx',
|
190
|
+
'pmax', 'pmin', 'pg0', 'ctrl', 'R10', 'td1', 'td2',
|
191
|
+
'zone'))
|
192
|
+
self.common_vars.extend(('p', 'q'))
|
193
|
+
|
194
|
+
|
195
|
+
class ACLine(GroupBase):
|
196
|
+
def __init__(self):
|
197
|
+
super().__init__()
|
198
|
+
self.common_params.extend(('bus1', 'bus2', 'r', 'x'))
|
199
|
+
|
200
|
+
|
201
|
+
class ACShort(GroupBase):
|
202
|
+
def __init__(self):
|
203
|
+
super(ACShort, self).__init__()
|
204
|
+
self.common_params.extend(('bus1', 'bus2'))
|
205
|
+
|
206
|
+
|
207
|
+
class StaticLoad(GroupBase):
|
208
|
+
"""
|
209
|
+
Static load group.
|
210
|
+
"""
|
211
|
+
|
212
|
+
def __init__(self):
|
213
|
+
super().__init__()
|
214
|
+
self.common_params.extend(('bus', 'p0', 'q0', 'ctrl', 'zone'))
|
215
|
+
|
216
|
+
|
217
|
+
class StaticShunt(GroupBase):
|
218
|
+
"""
|
219
|
+
Static shunt compensator group.
|
220
|
+
"""
|
221
|
+
pass
|
222
|
+
|
223
|
+
|
224
|
+
class Information(GroupBase):
|
225
|
+
"""
|
226
|
+
Group for information container models.
|
227
|
+
"""
|
228
|
+
|
229
|
+
def __init__(self):
|
230
|
+
GroupBase.__init__(self)
|
231
|
+
self.common_params = []
|
ams/models/info.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
"""
|
2
|
+
Module for the information container models.
|
3
|
+
"""
|
4
|
+
from andes.core.param import DataParam
|
5
|
+
from andes.core.model.modeldata import ModelData
|
6
|
+
|
7
|
+
from ams.core.model import Model
|
8
|
+
|
9
|
+
|
10
|
+
class Summary(ModelData, Model):
|
11
|
+
"""
|
12
|
+
Class for storing system summary.
|
13
|
+
Can be used for random information or notes.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(self, system, config):
|
17
|
+
ModelData.__init__(self, three_params=False)
|
18
|
+
|
19
|
+
self.field = DataParam(info='field name')
|
20
|
+
self.comment = DataParam(info='information, comment, or anything')
|
21
|
+
self.comment2 = DataParam(info='comment field 2')
|
22
|
+
self.comment3 = DataParam(info='comment field 3')
|
23
|
+
self.comment4 = DataParam(info='comment field 4')
|
24
|
+
|
25
|
+
Model.__init__(self, system, config)
|
26
|
+
self.group = 'Information'
|
ams/models/line.py
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
from andes.models.line.line import LineData
|
2
|
+
from andes.models.line.jumper import JumperData
|
3
|
+
from andes.core.param import NumParam
|
4
|
+
from andes.shared import deg2rad, np, spmatrix
|
5
|
+
|
6
|
+
from ams.core.model import Model
|
7
|
+
|
8
|
+
|
9
|
+
class Line(LineData, Model):
|
10
|
+
"""
|
11
|
+
AC transmission line model.
|
12
|
+
|
13
|
+
The model is also used for two-winding transformer. Transformers can set the
|
14
|
+
tap ratio in ``tap`` and/or phase shift angle ``phi``.
|
15
|
+
|
16
|
+
Note that the bus admittance matrix is built on fly and is not stored in the
|
17
|
+
object.
|
18
|
+
|
19
|
+
Notes
|
20
|
+
-----
|
21
|
+
There is a known issue that adding Algeb ``ud`` will cause Line.algebs run into
|
22
|
+
AttributeError: 'NoneType' object has no attribute 'n'. Not figured out why yet.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, system=None, config=None) -> None:
|
26
|
+
LineData.__init__(self)
|
27
|
+
Model.__init__(self, system, config)
|
28
|
+
self.group = 'ACLine'
|
29
|
+
|
30
|
+
self.amin = NumParam(default=-360 * deg2rad,
|
31
|
+
info="minimum angle difference, from bus - to bus",
|
32
|
+
unit='rad',
|
33
|
+
tex_name=r'a_{min}',
|
34
|
+
)
|
35
|
+
self.amax = NumParam(default=360 * deg2rad,
|
36
|
+
info="maximum angle difference, from bus - to bus",
|
37
|
+
unit='rad',
|
38
|
+
tex_name=r'a_{max}',
|
39
|
+
)
|
40
|
+
self.rate_a.unit = 'p.u.'
|
41
|
+
self.rate_b.unit = 'p.u.'
|
42
|
+
self.rate_c.unit = 'p.u.'
|
43
|
+
self.rate_a.default = 999.0
|
44
|
+
self.rate_b.default = 999.0
|
45
|
+
self.rate_c.default = 999.0
|
46
|
+
|
47
|
+
# NOTE: following parameters are prepared for building matrices
|
48
|
+
# they are initialized here but populated in ``System.setup()``.
|
49
|
+
self.a1a = None
|
50
|
+
self.a2a = None
|
51
|
+
|
52
|
+
# NOTE: following code are minly copied from `andes.models.line.Line`
|
53
|
+
# and they are not fully verified
|
54
|
+
# potential issues:
|
55
|
+
# `build_Bp` contains 'fdxb', which is not included in the input parameters,
|
56
|
+
# and the results are the negative of `Bbus` from `makeBdc` in PYPOWER
|
57
|
+
# `build_Bpp` ignores the line resistance for all three methods
|
58
|
+
# `build_Bdc` results are the negative of `Bbus` from `makeBdc` in PYPOWER
|
59
|
+
# `build_y` results have inignorable differences at diagonal elements with `makeYbus` in PYPOWER
|
60
|
+
|
61
|
+
def build_y(self):
|
62
|
+
"""
|
63
|
+
Build bus admittance matrix. Copied from ``andes.models.line.line.Line``.
|
64
|
+
|
65
|
+
Returns
|
66
|
+
-------
|
67
|
+
Y : spmatrix
|
68
|
+
Bus admittance matrix.
|
69
|
+
"""
|
70
|
+
|
71
|
+
nb = self.system.Bus.n
|
72
|
+
|
73
|
+
y1 = self.u.v * (self.g1.v + self.b1.v * 1j)
|
74
|
+
y2 = self.u.v * (self.g2.v + self.b2.v * 1j)
|
75
|
+
y12 = self.u.v / (self.r.v + self.x.v * 1j)
|
76
|
+
m = self.tap.v * np.exp(1j * self.phi.v)
|
77
|
+
m2 = self.tap.v**2
|
78
|
+
mconj = np.conj(m)
|
79
|
+
|
80
|
+
# build self and mutual admittances into Y
|
81
|
+
Y = spmatrix((y12 + y1 / m2), self.a1a, self.a1a, (nb, nb), 'z')
|
82
|
+
Y -= spmatrix(y12 / mconj, self.a1a, self.a2a, (nb, nb), 'z')
|
83
|
+
Y -= spmatrix(y12 / m, self.a2a, self.a1a, (nb, nb), 'z')
|
84
|
+
Y += spmatrix(y12 + y2, self.a2a, self.a2a, (nb, nb), 'z')
|
85
|
+
|
86
|
+
return Y
|
87
|
+
|
88
|
+
def build_Bp(self, method='fdpf'):
|
89
|
+
"""
|
90
|
+
Function for building B' matrix.
|
91
|
+
|
92
|
+
Parameters
|
93
|
+
----------
|
94
|
+
method : str
|
95
|
+
Method for building B' matrix. Choose from 'fdpf', 'fdbx', 'dcpf'.
|
96
|
+
|
97
|
+
Returns
|
98
|
+
-------
|
99
|
+
Bp : spmatrix
|
100
|
+
B' matrix.
|
101
|
+
"""
|
102
|
+
nb = self.system.Bus.n
|
103
|
+
|
104
|
+
if method not in ("fdpf", "fdbx", "dcpf"):
|
105
|
+
raise ValueError(f"Invalid method {method}; choose from 'fdpf', 'fdbx', 'dcpf'")
|
106
|
+
|
107
|
+
# Build B prime matrix -- FDPF
|
108
|
+
# `y1`` neglects line charging shunt, and g1 is usually 0 in HV lines
|
109
|
+
# `y2`` neglects line charging shunt, and g2 is usually 0 in HV lines
|
110
|
+
y1 = self.u.v * self.g1.v
|
111
|
+
y2 = self.u.v * self.g2.v
|
112
|
+
|
113
|
+
# `m` neglected tap ratio
|
114
|
+
m = np.exp(self.phi.v * 1j)
|
115
|
+
mconj = np.conj(m)
|
116
|
+
m2 = np.ones(self.n)
|
117
|
+
|
118
|
+
if method in ('fdxb', 'dcpf'):
|
119
|
+
# neglect line resistance in Bp in XB method
|
120
|
+
y12 = self.u.v / (self.x.v * 1j)
|
121
|
+
else:
|
122
|
+
y12 = self.u.v / (self.r.v + self.x.v * 1j)
|
123
|
+
|
124
|
+
Bdc = spmatrix((y12 + y1) / m2, self.a1a, self.a1a, (nb, nb), 'z')
|
125
|
+
Bdc -= spmatrix(y12 / mconj, self.a1a, self.a2a, (nb, nb), 'z')
|
126
|
+
Bdc -= spmatrix(y12 / m, self.a2a, self.a1a, (nb, nb), 'z')
|
127
|
+
Bdc += spmatrix(y12 + y2, self.a2a, self.a2a, (nb, nb), 'z')
|
128
|
+
Bdc = Bdc.imag()
|
129
|
+
|
130
|
+
for item in range(nb):
|
131
|
+
if abs(Bdc[item, item]) == 0:
|
132
|
+
Bdc[item, item] = 1e-6 + 0j
|
133
|
+
|
134
|
+
return Bdc
|
135
|
+
|
136
|
+
def build_Bpp(self, method='fdpf'):
|
137
|
+
"""
|
138
|
+
Function for building B'' matrix.
|
139
|
+
|
140
|
+
Parameters
|
141
|
+
----------
|
142
|
+
method : str
|
143
|
+
Method for building B'' matrix. Choose from 'fdpf', 'fdbx', 'dcpf'.
|
144
|
+
|
145
|
+
Returns
|
146
|
+
-------
|
147
|
+
Bpp : spmatrix
|
148
|
+
B'' matrix.
|
149
|
+
"""
|
150
|
+
|
151
|
+
nb = self.system.Bus.n
|
152
|
+
|
153
|
+
if method not in ("fdpf", "fdbx", "dcpf"):
|
154
|
+
raise ValueError(f"Invalid method {method}; choose from 'fdpf', 'fdbx', 'dcpf'")
|
155
|
+
|
156
|
+
# Build B double prime matrix
|
157
|
+
# y1 neglected line charging shunt, and g1 is usually 0 in HV lines
|
158
|
+
# y2 neglected line charging shunt, and g2 is usually 0 in HV lines
|
159
|
+
# m neglected phase shifter
|
160
|
+
y1 = self.u.v * (self.g1.v + self.b1.v * 1j)
|
161
|
+
y2 = self.u.v * (self.g2.v + self.b2.v * 1j)
|
162
|
+
|
163
|
+
m = self.tap.v
|
164
|
+
m2 = abs(m)**2
|
165
|
+
|
166
|
+
if method in ('fdbx', 'fdpf', 'dcpf'):
|
167
|
+
# neglect line resistance in Bpp in BX method
|
168
|
+
y12 = self.u.v / (self.x.v * 1j)
|
169
|
+
else:
|
170
|
+
y12 = self.u.v / (self.r.v + self.x.v * 1j)
|
171
|
+
|
172
|
+
Bpp = spmatrix((y12 + y1) / m2, self.a1a, self.a1a, (nb, nb), 'z')
|
173
|
+
Bpp -= spmatrix(y12 / np.conj(m), self.a1a, self.a2a, (nb, nb), 'z')
|
174
|
+
Bpp -= spmatrix(y12 / m, self.a2a, self.a1a, (nb, nb), 'z')
|
175
|
+
Bpp += spmatrix(y12 + y2, self.a2a, self.a2a, (nb, nb), 'z')
|
176
|
+
Bpp = Bpp.imag()
|
177
|
+
|
178
|
+
for item in range(nb):
|
179
|
+
if abs(Bpp[item, item]) == 0:
|
180
|
+
Bpp[item, item] = 1e-6 + 0j
|
181
|
+
|
182
|
+
return Bpp
|
183
|
+
|
184
|
+
def build_Bdc(self):
|
185
|
+
"""
|
186
|
+
The MATPOWER-flavor Bdc matrix for DC power flow.
|
187
|
+
|
188
|
+
The method neglects line charging and line resistance. It retains tap ratio.
|
189
|
+
|
190
|
+
Returns
|
191
|
+
-------
|
192
|
+
Bdc : spmatrix
|
193
|
+
Bdc matrix.
|
194
|
+
"""
|
195
|
+
|
196
|
+
nb = self.system.Bus.n
|
197
|
+
|
198
|
+
y12 = self.u.v / (self.x.v * 1j)
|
199
|
+
y12 = y12 / self.tap.v
|
200
|
+
|
201
|
+
Bdc = spmatrix(y12, self.a1a, self.a1a, (nb, nb), 'z')
|
202
|
+
Bdc -= spmatrix(y12, self.a1a, self.a2a, (nb, nb), 'z')
|
203
|
+
Bdc -= spmatrix(y12, self.a2a, self.a1a, (nb, nb), 'z')
|
204
|
+
Bdc += spmatrix(y12, self.a2a, self.a2a, (nb, nb), 'z')
|
205
|
+
Bdc = Bdc.imag()
|
206
|
+
|
207
|
+
for item in range(nb):
|
208
|
+
if abs(Bdc[item, item]) == 0:
|
209
|
+
Bdc[item, item] = 1e-6
|
210
|
+
|
211
|
+
return Bdc
|
212
|
+
|
213
|
+
|
214
|
+
class Jumper(JumperData, Model):
|
215
|
+
"""
|
216
|
+
Jumper is a device to short two buses (merging two buses into one).
|
217
|
+
|
218
|
+
Jumper can connect two buses satisfying one of the following conditions:
|
219
|
+
|
220
|
+
- neither bus is voltage-controlled
|
221
|
+
- either bus is voltage-controlled
|
222
|
+
- both buses are voltage-controlled, and the voltages are the same.
|
223
|
+
|
224
|
+
If the buses are controlled in different voltages, power flow will
|
225
|
+
not solve (as the power flow through the jumper will be infinite).
|
226
|
+
|
227
|
+
In the solutions, the ``p`` and ``q`` are flowing out of bus1
|
228
|
+
and flowing into bus2.
|
229
|
+
|
230
|
+
Setting a Jumper's connectivity status ``u`` to zero will disconnect the two
|
231
|
+
buses. In the case of a system split, one will need to call
|
232
|
+
``System.connectivity()`` immediately following the split to detect islands.
|
233
|
+
"""
|
234
|
+
|
235
|
+
def __init__(self, system=None, config=None) -> None:
|
236
|
+
JumperData.__init__(self)
|
237
|
+
Model.__init__(self, system, config)
|
238
|
+
self.group = 'ACShort'
|