ltbams 0.9.12__tar.gz → 0.9.13__tar.gz
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.
- {ltbams-0.9.12/ltbams.egg-info → ltbams-0.9.13}/PKG-INFO +21 -7
- {ltbams-0.9.12 → ltbams-0.9.13}/README.md +20 -6
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/_version.py +3 -3
- ltbams-0.9.13/ams/cases/5bus/pjm5bus_demo.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/interface.py +46 -2
- ltbams-0.9.13/ams/report.py +356 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/acopf.py +5 -8
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/dcopf.py +47 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/dcpf.py +4 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/dcpf0.py +6 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/ed.py +2 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/routine.py +59 -16
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/rted.py +18 -15
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/uc.py +3 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/release-notes.rst +27 -9
- {ltbams-0.9.12 → ltbams-0.9.13/ltbams.egg-info}/PKG-INFO +21 -7
- {ltbams-0.9.12 → ltbams-0.9.13}/ltbams.egg-info/SOURCES.txt +2 -1
- {ltbams-0.9.12 → ltbams-0.9.13}/ltbams.egg-info/requires.txt +0 -1
- {ltbams-0.9.12 → ltbams-0.9.13}/requirements.txt +0 -1
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_export_csv.py +8 -4
- ltbams-0.9.12/tests/test_interop.py → ltbams-0.9.13/tests/test_interface.py +39 -0
- ltbams-0.9.13/tests/test_report.py +245 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_rtn_dcopf.py +24 -0
- ltbams-0.9.13/tests/test_rtn_dcpf.py +77 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_rtn_ed.py +99 -8
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_rtn_rted.py +111 -14
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_rtn_uc.py +86 -3
- ltbams-0.9.12/ams/cases/5bus/pjm5bus_demo.xlsx +0 -0
- ltbams-0.9.12/ams/report.py +0 -211
- ltbams-0.9.12/tests/test_report.py +0 -76
- {ltbams-0.9.12 → ltbams-0.9.13}/CONTRIBUTING.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/LICENSE +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/MANIFEST.in +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/__main__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/benchmarks.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/5bus/pjm5bus_jumper.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/5bus/pjm5bus_uced.json +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/5bus/pjm5bus_uced.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/5bus/pjm5bus_uced_esd1.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/5bus/pjm5bus_uced_ev.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/ieee123/ieee123.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/ieee123/ieee123_regcv1.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/ieee14/ieee14.json +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/ieee14/ieee14.raw +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/ieee14/ieee14_uced.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/ieee39/ieee39.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/ieee39/ieee39_uced.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/ieee39/ieee39_uced_esd1.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/ieee39/ieee39_uced_pvd1.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/ieee39/ieee39_uced_vis.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/matpower/benchmark.json +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/matpower/case118.m +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/matpower/case14.m +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/matpower/case300.m +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/matpower/case39.m +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/matpower/case5.m +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/matpower/case_ACTIVSg2000.m +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/npcc/npcc.m +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/npcc/npcc_uced.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/pglib/pglib_opf_case39_epri__api.m +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/wecc/wecc.m +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cases/wecc/wecc_uced.xlsx +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/cli.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/core/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/core/documenter.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/core/matprocessor.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/core/model.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/core/param.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/core/service.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/core/symprocessor.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/core/var.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/extension/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/extension/eva.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/io/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/io/json.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/io/matpower.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/io/psse.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/io/pypower.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/io/xlsx.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/main.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/area.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/bus.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/cost.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/distributed/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/distributed/esd1.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/distributed/ev.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/distributed/pvd1.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/group.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/info.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/line.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/region.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/renewable/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/renewable/regc.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/reserve.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/shunt.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/static/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/static/gen.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/static/pq.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/models/timeslot.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/opt/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/opt/constraint.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/opt/exprcalc.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/opt/expression.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/opt/objective.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/opt/omodel.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/opt/optbase.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/opt/param.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/opt/var.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/_compat.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/core/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/core/pips.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/core/ppoption.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/core/ppver.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/core/solver.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/eps.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/idx.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/io.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/make/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/make/matrices.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/make/pdv.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/routines/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/routines/cpf.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/routines/cpf_callbacks.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/routines/opf.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/routines/opffcns.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/routines/pflow.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/toggle.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/pypower/utils.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/cpf.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/dopf.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/pflow.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/pflow0.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/routines/type.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/shared.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/system.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/utils/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ams/utils/paths.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/Makefile +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/make.bat +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/_templates/autosummary/base.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/_templates/autosummary/class.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/_templates/autosummary/module.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/_templates/autosummary/module_toctree.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/api.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/conf.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/examples/index.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/genmodelref.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/genroutineref.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/copyright.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/formats/index.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/formats/matpower.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/formats/psse.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/formats/pypower.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/formats/xlsx.png +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/formats/xlsx.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/index.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/install.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/overview.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/testcase.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/getting_started/verification.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/images/dcopf_time.png +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/images/sponsors/CURENT_Logo_NameOnTrans.png +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/images/sponsors/CURENT_Logo_Transparent.png +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/images/sponsors/CURENT_Logo_Transparent_Name.png +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/images/sponsors/doe.png +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/index.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/modeling/example.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/modeling/index.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/modeling/model.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/modeling/routine.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/docs/source/modeling/system.rst +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ltbams.egg-info/dependency_links.txt +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ltbams.egg-info/entry_points.txt +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/ltbams.egg-info/top_level.txt +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/pyproject.toml +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/requirements-extra.txt +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/setup.cfg +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/setup.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/__init__.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_1st_system.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_addressing.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_andes_mats.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_benchmarks.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_case.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_cli.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_group.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_io.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_jumper.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_known_good.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_matp.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_model.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_omodel.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_paths.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_repr.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_routine.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_rtn_pflow.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/tests/test_service.py +0 -0
- {ltbams-0.9.12 → ltbams-0.9.13}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ltbams
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.13
|
4
4
|
Summary: Python software for scheduling modeling and co-simulation with dynanics.
|
5
5
|
Home-page: https://github.com/CURENT/ams
|
6
6
|
Author: Jinning Wang
|
@@ -35,11 +35,12 @@ License-File: LICENSE
|
|
35
35
|
|
36
36
|
# LTB AMS
|
37
37
|
|
38
|
-
Python Software for Power System Scheduling Modeling and Co-Simulation with
|
38
|
+
Python Software for Power System Scheduling Modeling and Co-Simulation with Dynamics, serving as the market simulator for the [CURENT Largescale Testbed][LTB Repository].
|
39
39
|
|
40
40
|
[](https://github.com/CURENT/ams/blob/master/LICENSE)
|
41
41
|

|
42
42
|
[](https://www.python.org/)
|
43
|
+
[](https://www.repostatus.org/#active)
|
43
44
|

|
44
45
|
[](https://github.com/CURENT/ams/commits/master/)
|
45
46
|
[](https://github.com/CURENT/ams/commits/develop/)
|
@@ -66,6 +67,10 @@ Python Software for Power System Scheduling Modeling and Co-Simulation with Dyna
|
|
66
67
|
With the built-in interface with ANDES, AMS enables **Dynamics Incorporated**
|
67
68
|
**Stability-Constrained Scheduling**.
|
68
69
|
|
70
|
+
This package can be helpful for power system engineers, researchers, and students
|
71
|
+
who need to conduct scheduling studies and transient stability studies at given
|
72
|
+
operating points.
|
73
|
+
|
69
74
|
AMS is a **Modeling Framework** that provides a descriptive way to formulate
|
70
75
|
scheduling problems. The optimization problems are then handled by **CVXPY**
|
71
76
|
and solved with third-party solvers.
|
@@ -102,6 +107,7 @@ Use the following resources to get involved.
|
|
102
107
|
- Version **0.9.9** has known issues and has been yanked from PyPI
|
103
108
|
- `kvxopt` is recommended to install via `conda` as sometimes ``pip`` struggles to set the correct path for compiled libraries
|
104
109
|
- `cvxpy` versions **below 1.5** are incompatible with `numpy` versions **2.0 and above**
|
110
|
+
- If solver `SCIP` run into import error, try to reinstall its Python interface by running `pip install pyscipopt --no-binary scip --force`
|
105
111
|
|
106
112
|
AMS is released as ``ltbams`` on PyPI and conda-forge.
|
107
113
|
Install from PyPI using pip:
|
@@ -124,15 +130,23 @@ pip install git+https://github.com/CURENT/ams.git
|
|
124
130
|
|
125
131
|
# Example Usage
|
126
132
|
|
127
|
-
Using AMS to run a Real-Time Economic Dispatch (RTED) simulation:
|
128
|
-
|
129
133
|
```python
|
130
134
|
import ams
|
135
|
+
import andes
|
136
|
+
|
137
|
+
ss = ams.load(ams.get_case('ieee14/ieee14_uced.xlsx'))
|
138
|
+
|
139
|
+
# solve RTED
|
140
|
+
ss.RTED.run(solver='CLARABEL')
|
131
141
|
|
132
|
-
ss
|
133
|
-
|
142
|
+
ss.RTED.pg.v
|
143
|
+
>>> array([1.8743862, 0.3226138, 0.01 , 0.02 , 0.01 ])
|
134
144
|
|
135
|
-
|
145
|
+
# convert to ANDES case
|
146
|
+
sa = ss.to_andes(addfile=andes.get_case('ieee14/ieee14_full.xlsx'),
|
147
|
+
setup=True, verify=False)
|
148
|
+
sa
|
149
|
+
>>> <andes.system.System at 0x14bd98190>
|
136
150
|
```
|
137
151
|
|
138
152
|
# Sponsors and Contributors
|
@@ -1,10 +1,11 @@
|
|
1
1
|
# LTB AMS
|
2
2
|
|
3
|
-
Python Software for Power System Scheduling Modeling and Co-Simulation with
|
3
|
+
Python Software for Power System Scheduling Modeling and Co-Simulation with Dynamics, serving as the market simulator for the [CURENT Largescale Testbed][LTB Repository].
|
4
4
|
|
5
5
|
[](https://github.com/CURENT/ams/blob/master/LICENSE)
|
6
6
|

|
7
7
|
[](https://www.python.org/)
|
8
|
+
[](https://www.repostatus.org/#active)
|
8
9
|

|
9
10
|
[](https://github.com/CURENT/ams/commits/master/)
|
10
11
|
[](https://github.com/CURENT/ams/commits/develop/)
|
@@ -31,6 +32,10 @@ Python Software for Power System Scheduling Modeling and Co-Simulation with Dyna
|
|
31
32
|
With the built-in interface with ANDES, AMS enables **Dynamics Incorporated**
|
32
33
|
**Stability-Constrained Scheduling**.
|
33
34
|
|
35
|
+
This package can be helpful for power system engineers, researchers, and students
|
36
|
+
who need to conduct scheduling studies and transient stability studies at given
|
37
|
+
operating points.
|
38
|
+
|
34
39
|
AMS is a **Modeling Framework** that provides a descriptive way to formulate
|
35
40
|
scheduling problems. The optimization problems are then handled by **CVXPY**
|
36
41
|
and solved with third-party solvers.
|
@@ -67,6 +72,7 @@ Use the following resources to get involved.
|
|
67
72
|
- Version **0.9.9** has known issues and has been yanked from PyPI
|
68
73
|
- `kvxopt` is recommended to install via `conda` as sometimes ``pip`` struggles to set the correct path for compiled libraries
|
69
74
|
- `cvxpy` versions **below 1.5** are incompatible with `numpy` versions **2.0 and above**
|
75
|
+
- If solver `SCIP` run into import error, try to reinstall its Python interface by running `pip install pyscipopt --no-binary scip --force`
|
70
76
|
|
71
77
|
AMS is released as ``ltbams`` on PyPI and conda-forge.
|
72
78
|
Install from PyPI using pip:
|
@@ -89,15 +95,23 @@ pip install git+https://github.com/CURENT/ams.git
|
|
89
95
|
|
90
96
|
# Example Usage
|
91
97
|
|
92
|
-
Using AMS to run a Real-Time Economic Dispatch (RTED) simulation:
|
93
|
-
|
94
98
|
```python
|
95
99
|
import ams
|
100
|
+
import andes
|
101
|
+
|
102
|
+
ss = ams.load(ams.get_case('ieee14/ieee14_uced.xlsx'))
|
103
|
+
|
104
|
+
# solve RTED
|
105
|
+
ss.RTED.run(solver='CLARABEL')
|
96
106
|
|
97
|
-
ss
|
98
|
-
|
107
|
+
ss.RTED.pg.v
|
108
|
+
>>> array([1.8743862, 0.3226138, 0.01 , 0.02 , 0.01 ])
|
99
109
|
|
100
|
-
|
110
|
+
# convert to ANDES case
|
111
|
+
sa = ss.to_andes(addfile=andes.get_case('ieee14/ieee14_full.xlsx'),
|
112
|
+
setup=True, verify=False)
|
113
|
+
sa
|
114
|
+
>>> <andes.system.System at 0x14bd98190>
|
101
115
|
```
|
102
116
|
|
103
117
|
# Sponsors and Contributors
|
@@ -8,11 +8,11 @@ import json
|
|
8
8
|
|
9
9
|
version_json = '''
|
10
10
|
{
|
11
|
-
"date": "2024-
|
11
|
+
"date": "2024-12-05T16:49:45-0500",
|
12
12
|
"dirty": false,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "0.9.
|
14
|
+
"full-revisionid": "db9279e8765429827062032b803c756025c018b2",
|
15
|
+
"version": "0.9.13"
|
16
16
|
}
|
17
17
|
''' # END VERSION_JSON
|
18
18
|
|
Binary file
|
@@ -78,7 +78,6 @@ def sync_adsys(amsys, adsys):
|
|
78
78
|
ad_mdl.set(src=param, attr='v', idx=idx,
|
79
79
|
value=am_mdl.get(src=param, attr='v', idx=idx))
|
80
80
|
except Exception:
|
81
|
-
logger.debug(f"Skip updating {mname}.{param}")
|
82
81
|
continue
|
83
82
|
return True
|
84
83
|
|
@@ -333,6 +332,27 @@ def parse_addfile(adsys, amsys, addfile):
|
|
333
332
|
df[idxn] = df[idxn].replace(idx_map[mdl_guess])
|
334
333
|
logger.debug(f'Adjust {idxp.class_name} <{name}.{idxp.name}>')
|
335
334
|
|
335
|
+
# NOTE: Group TimedEvent needs special treatment
|
336
|
+
# adjust Toggle and Fault models
|
337
|
+
toggle_df = df_models.get('Toggle') or df_models.get('Toggler')
|
338
|
+
if toggle_df is not None:
|
339
|
+
toggle_df['dev'] = toggle_df.apply(replace_dev, axis=1,
|
340
|
+
mdl='model', dev='dev',
|
341
|
+
idx_map=idx_map)
|
342
|
+
|
343
|
+
alter_df = df_models.get('Alter')
|
344
|
+
if alter_df is not None:
|
345
|
+
alter_df['dev'] = alter_df.apply(replace_dev, axis=1,
|
346
|
+
mdl='model', dev='dev',
|
347
|
+
idx_map=idx_map)
|
348
|
+
|
349
|
+
# adjust Fault model
|
350
|
+
fault_df = df_models.get('Fault')
|
351
|
+
if fault_df is not None:
|
352
|
+
fault_df['bus'] = fault_df.apply(replace_dev, axis=1,
|
353
|
+
mdl='bus', dev='bus',
|
354
|
+
idx_map=idx_map)
|
355
|
+
|
336
356
|
# add dynamic models
|
337
357
|
for name, df in df_models.items():
|
338
358
|
# drop rows that all nan
|
@@ -966,7 +986,7 @@ def make_link_table(adsys):
|
|
966
986
|
right=ssa_rg[['stg_idx', 'rg_idx']])
|
967
987
|
|
968
988
|
# NOTE: use this instead of fillna to avoid type conversion
|
969
|
-
idxc = ['
|
989
|
+
idxc = ['syg_idx', 'dg_idx', 'rg_idx']
|
970
990
|
ssa_key0[idxc] = ssa_key0[idxc].astype('str').replace({'nan': ''}).astype('bool')
|
971
991
|
|
972
992
|
dyr = ssa_key0['syg_idx'] + ssa_key0['dg_idx'] + ssa_key0['rg_idx']
|
@@ -1037,3 +1057,27 @@ def verify_pf(amsys, adsys, tol=1e-3):
|
|
1037
1057
|
logger.warning(msg)
|
1038
1058
|
logger.warning(diff_msg)
|
1039
1059
|
return check
|
1060
|
+
|
1061
|
+
|
1062
|
+
def replace_dev(row, mdl, dev, idx_map):
|
1063
|
+
"""
|
1064
|
+
Replace the device idx in the row based on the idx_map.
|
1065
|
+
|
1066
|
+
Parameters
|
1067
|
+
----------
|
1068
|
+
row : pd.Series
|
1069
|
+
The row of the DataFrame.
|
1070
|
+
mdl : str
|
1071
|
+
The column name for the Model.
|
1072
|
+
dev : str
|
1073
|
+
The column name for the Device idx.
|
1074
|
+
idx_map : dict
|
1075
|
+
The index map for replacement.
|
1076
|
+
|
1077
|
+
Returns
|
1078
|
+
-------
|
1079
|
+
str
|
1080
|
+
The new device idx.
|
1081
|
+
"""
|
1082
|
+
old_idx = row[dev]
|
1083
|
+
return idx_map.get(row[mdl], {}).get(old_idx, old_idx)
|
@@ -0,0 +1,356 @@
|
|
1
|
+
"""
|
2
|
+
Module for report generation.
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
from collections import OrderedDict
|
6
|
+
from time import strftime
|
7
|
+
from typing import List, Dict, Optional
|
8
|
+
|
9
|
+
from andes.io.txt import dump_data
|
10
|
+
from andes.shared import np
|
11
|
+
from andes.utils.misc import elapsed
|
12
|
+
|
13
|
+
from ams import __version__ as version
|
14
|
+
from ams.shared import copyright_msg
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
DECIMALS = 6
|
20
|
+
|
21
|
+
|
22
|
+
def report_info(system) -> list:
|
23
|
+
info = list()
|
24
|
+
info.append('AMS' + ' ' + version + '\n')
|
25
|
+
info.append(f'{copyright_msg}\n\n')
|
26
|
+
info.append('AMS comes with ABSOLUTELY NO WARRANTY\n')
|
27
|
+
info.append('Case file: ' + str(system.files.case) + '\n')
|
28
|
+
info.append('Report time: ' + strftime("%m/%d/%Y %I:%M:%S %p") + '\n\n')
|
29
|
+
return info
|
30
|
+
|
31
|
+
|
32
|
+
class Report:
|
33
|
+
"""
|
34
|
+
Report class to store routine analysis reports.
|
35
|
+
|
36
|
+
Notes
|
37
|
+
-----
|
38
|
+
Revised from the ANDES project (https://github.com/CURENT/andes).
|
39
|
+
Original author: Hantao Cui
|
40
|
+
License: GPL3
|
41
|
+
"""
|
42
|
+
|
43
|
+
def __init__(self, system):
|
44
|
+
self.system = system
|
45
|
+
self.basic = OrderedDict()
|
46
|
+
|
47
|
+
@property
|
48
|
+
def info(self):
|
49
|
+
return report_info(self.system)
|
50
|
+
|
51
|
+
def update(self):
|
52
|
+
"""
|
53
|
+
Update values based on the requested content
|
54
|
+
"""
|
55
|
+
system = self.system
|
56
|
+
self.basic.update({
|
57
|
+
'Buses': system.Bus.n,
|
58
|
+
'Generators': system.PV.n + system.Slack.n,
|
59
|
+
'Loads': system.PQ.n,
|
60
|
+
'Shunts': system.Shunt.n,
|
61
|
+
'Lines': system.Line.n,
|
62
|
+
'Transformers': np.count_nonzero(system.Line.trans.v == 1),
|
63
|
+
'Areas': system.Area.n,
|
64
|
+
'Regions': system.Region.n,
|
65
|
+
})
|
66
|
+
|
67
|
+
def collect(self, rtn, horizon=None):
|
68
|
+
"""
|
69
|
+
Collect report data.
|
70
|
+
|
71
|
+
Parameters
|
72
|
+
----------
|
73
|
+
rtn : Routine
|
74
|
+
Routine object to collect data from.
|
75
|
+
horizon : str, optional
|
76
|
+
Timeslot to collect data from. Only single timeslot is supported.
|
77
|
+
"""
|
78
|
+
text = list()
|
79
|
+
header = list()
|
80
|
+
row_name = list()
|
81
|
+
data = list()
|
82
|
+
|
83
|
+
if not rtn.converged:
|
84
|
+
return text, header, row_name, data
|
85
|
+
|
86
|
+
owners = collect_owners(rtn)
|
87
|
+
owners = collect_vars(owners, rtn, horizon, DECIMALS)
|
88
|
+
owners = collect_exprs(owners, rtn, horizon, DECIMALS)
|
89
|
+
owners = collect_exprcs(owners, rtn, horizon, DECIMALS)
|
90
|
+
|
91
|
+
dump_collected_data(owners, text, header, row_name, data)
|
92
|
+
|
93
|
+
return text, header, row_name, data
|
94
|
+
|
95
|
+
def write(self):
|
96
|
+
"""
|
97
|
+
Write report to file.
|
98
|
+
"""
|
99
|
+
system = self.system
|
100
|
+
if system.files.no_output is True:
|
101
|
+
return
|
102
|
+
|
103
|
+
text = list()
|
104
|
+
header = list()
|
105
|
+
row_name = list()
|
106
|
+
data = list()
|
107
|
+
self.update()
|
108
|
+
|
109
|
+
t, _ = elapsed()
|
110
|
+
|
111
|
+
# --- system info section ---
|
112
|
+
text.append(self.info)
|
113
|
+
header.append(None)
|
114
|
+
row_name.append(None)
|
115
|
+
data.append(None)
|
116
|
+
|
117
|
+
# --- system summary section ---
|
118
|
+
text.append(['='*10 + ' System Statistics ' + '='*10 + '\n'])
|
119
|
+
header.append(None)
|
120
|
+
row_name.append(self.basic.keys())
|
121
|
+
data.append(list(self.basic.values()))
|
122
|
+
|
123
|
+
# --- rountine data section ---
|
124
|
+
rtns_to_collect = [rtn for rtn in system.routines.values() if rtn.converged]
|
125
|
+
for rtn in rtns_to_collect:
|
126
|
+
# --- routine summary ---
|
127
|
+
text.append(['='*30 + f' {rtn.class_name} ' + '='*30])
|
128
|
+
header.append(None)
|
129
|
+
row_name.append(None)
|
130
|
+
data.append(None)
|
131
|
+
if hasattr(rtn, 'timeslot'):
|
132
|
+
for slot in rtn.timeslot.v:
|
133
|
+
# --- timeslot summary ---
|
134
|
+
text.append(['-'*28 + f' {slot} ' + '-'*28])
|
135
|
+
header.append(None)
|
136
|
+
row_name.append(None)
|
137
|
+
data.append(None)
|
138
|
+
text_sum, header_sum, row_name_sum, data_sum = self.collect(rtn, horizon=[slot])
|
139
|
+
# --- timeslot data ---
|
140
|
+
text.extend(text_sum)
|
141
|
+
header.extend(header_sum)
|
142
|
+
row_name.extend(row_name_sum)
|
143
|
+
data.extend(data_sum)
|
144
|
+
else:
|
145
|
+
# single-period
|
146
|
+
text_sum, header_sum, row_name_sum, data_sum = self.collect(rtn)
|
147
|
+
# --- routine extended ---
|
148
|
+
text.append([''])
|
149
|
+
row_name.append(
|
150
|
+
['Generation', 'Load'])
|
151
|
+
|
152
|
+
if hasattr(rtn, 'pd'):
|
153
|
+
pd = rtn.pd.v.sum().round(DECIMALS)
|
154
|
+
else:
|
155
|
+
pd = rtn.system.PQ.p0.v.sum().round(DECIMALS)
|
156
|
+
if hasattr(rtn, 'qd'):
|
157
|
+
qd = rtn.qd.v.sum().round(DECIMALS)
|
158
|
+
else:
|
159
|
+
qd = rtn.system.PQ.q0.v.sum().round(DECIMALS)
|
160
|
+
|
161
|
+
if not hasattr(rtn, 'qg'):
|
162
|
+
header.append(['P (p.u.)'])
|
163
|
+
Pcol = [rtn.pg.v.sum().round(DECIMALS), pd]
|
164
|
+
data.append([Pcol])
|
165
|
+
else:
|
166
|
+
header.append(['P (p.u.)', 'Q (p.u.)'])
|
167
|
+
Pcol = [rtn.pg.v.sum().round(DECIMALS), pd]
|
168
|
+
Qcol = [rtn.qg.v.sum().round(DECIMALS), qd]
|
169
|
+
data.append([Pcol, Qcol])
|
170
|
+
|
171
|
+
# --- routine data ---
|
172
|
+
text.extend(text_sum)
|
173
|
+
header.extend(header_sum)
|
174
|
+
row_name.extend(row_name_sum)
|
175
|
+
data.extend(data_sum)
|
176
|
+
dump_data(text, header, row_name, data, system.files.txt)
|
177
|
+
|
178
|
+
_, s = elapsed(t)
|
179
|
+
logger.info(f'Report saved to "{system.files.txt}" in {s}.')
|
180
|
+
|
181
|
+
|
182
|
+
def dump_collected_data(owners: dict, text: List, header: List, row_name: List, data: List) -> None:
|
183
|
+
"""
|
184
|
+
Dump collected data into the provided lists.
|
185
|
+
|
186
|
+
Parameters
|
187
|
+
----------
|
188
|
+
owners : dict
|
189
|
+
Dictionary of owners.
|
190
|
+
text : list
|
191
|
+
List to append text data to.
|
192
|
+
header : list
|
193
|
+
List to append header data to.
|
194
|
+
row_name : list
|
195
|
+
List to append row names to.
|
196
|
+
data : list
|
197
|
+
List to append data to.
|
198
|
+
"""
|
199
|
+
for key, val in owners.items():
|
200
|
+
text.append([f'{key} DATA:\n'])
|
201
|
+
row_name.append(val['idx'])
|
202
|
+
header.append(val['header'])
|
203
|
+
data.append(val['data'])
|
204
|
+
|
205
|
+
|
206
|
+
def collect_exprcs(owners: Dict, rtn, horizon: Optional[str], decimals: int) -> Dict:
|
207
|
+
"""
|
208
|
+
Collect expression calculations and populate the data dictionary.
|
209
|
+
|
210
|
+
Parameters
|
211
|
+
----------
|
212
|
+
owners : dict
|
213
|
+
Dictionary of owners.
|
214
|
+
rtn : Routine
|
215
|
+
Routine object to collect data from.
|
216
|
+
horizon : str, optional
|
217
|
+
Timeslot to collect data from. Only single timeslot is supported.
|
218
|
+
decimals : int
|
219
|
+
Number of decimal places to round the data.
|
220
|
+
|
221
|
+
Returns
|
222
|
+
-------
|
223
|
+
dict
|
224
|
+
Updated dictionary of owners with collected ExpressionCalc data.
|
225
|
+
"""
|
226
|
+
for key, exprc in rtn.exprcs.items():
|
227
|
+
if exprc.owner is None:
|
228
|
+
continue
|
229
|
+
owner_name = exprc.owner.class_name
|
230
|
+
idx_v = owners[owner_name]['idx']
|
231
|
+
header_v = key if exprc.unit is None else f'{key} ({exprc.unit})'
|
232
|
+
try:
|
233
|
+
data_v = rtn.get(src=key, attr='v', idx=idx_v, horizon=horizon).round(decimals)
|
234
|
+
except Exception:
|
235
|
+
data_v = [np.nan] * len(idx_v)
|
236
|
+
owners[owner_name]['header'].append(header_v)
|
237
|
+
owners[owner_name]['data'].append(data_v)
|
238
|
+
|
239
|
+
return owners
|
240
|
+
|
241
|
+
|
242
|
+
def collect_exprs(owners: Dict, rtn, horizon: Optional[str], decimals: int) -> Dict:
|
243
|
+
"""
|
244
|
+
Collect expressions and populate the data dictionary.
|
245
|
+
|
246
|
+
Parameters
|
247
|
+
----------
|
248
|
+
owners : dict
|
249
|
+
Dictionary of owners.
|
250
|
+
rtn : Routine
|
251
|
+
Routine object to collect data from.
|
252
|
+
horizon : str, optional
|
253
|
+
Timeslot to collect data from. Only single timeslot is supported.
|
254
|
+
decimals : int
|
255
|
+
Number of decimal places to round the data.
|
256
|
+
|
257
|
+
Returns
|
258
|
+
-------
|
259
|
+
dict
|
260
|
+
Updated dictionary of owners with collected Expression data.
|
261
|
+
"""
|
262
|
+
for key, expr in rtn.exprs.items():
|
263
|
+
if expr.owner is None:
|
264
|
+
continue
|
265
|
+
owner_name = expr.owner.class_name
|
266
|
+
idx_v = owners[owner_name]['idx']
|
267
|
+
header_v = key if expr.unit is None else f'{key} ({expr.unit})'
|
268
|
+
try:
|
269
|
+
data_v = rtn.get(src=key, attr='v', idx=idx_v, horizon=horizon).round(decimals)
|
270
|
+
except Exception:
|
271
|
+
data_v = [np.nan] * len(idx_v)
|
272
|
+
owners[owner_name]['header'].append(header_v)
|
273
|
+
owners[owner_name]['data'].append(data_v)
|
274
|
+
|
275
|
+
return owners
|
276
|
+
|
277
|
+
|
278
|
+
def collect_vars(owners: Dict, rtn, horizon: Optional[str], decimals: int) -> Dict:
|
279
|
+
"""
|
280
|
+
Collect variables and populate the data dictionary.
|
281
|
+
|
282
|
+
Parameters
|
283
|
+
----------
|
284
|
+
owners : dict
|
285
|
+
Dictionary of owners.
|
286
|
+
rtn : Routine
|
287
|
+
Routine object to collect data from.
|
288
|
+
horizon : str, optional
|
289
|
+
Timeslot to collect data from. Only single timeslot is supported.
|
290
|
+
decimals : int
|
291
|
+
Number of decimal places to round the data.
|
292
|
+
|
293
|
+
Returns
|
294
|
+
-------
|
295
|
+
dict
|
296
|
+
Updated dictionary of owners with collected Var data.
|
297
|
+
"""
|
298
|
+
|
299
|
+
for key, var in rtn.vars.items():
|
300
|
+
if var.owner is None:
|
301
|
+
continue
|
302
|
+
owner_name = var.owner.class_name
|
303
|
+
idx_v = owners[owner_name]['idx']
|
304
|
+
header_v = key if var.unit is None else f'{key} ({var.unit})'
|
305
|
+
try:
|
306
|
+
data_v = rtn.get(src=key, attr='v', idx=idx_v, horizon=horizon).round(decimals)
|
307
|
+
except Exception:
|
308
|
+
data_v = [np.nan] * len(idx_v)
|
309
|
+
owners[owner_name]['header'].append(header_v)
|
310
|
+
owners[owner_name]['data'].append(data_v)
|
311
|
+
|
312
|
+
return owners
|
313
|
+
|
314
|
+
|
315
|
+
def collect_owners(rtn):
|
316
|
+
"""
|
317
|
+
Initialize an owners dictionary for data collection.
|
318
|
+
|
319
|
+
Returns
|
320
|
+
-------
|
321
|
+
dict
|
322
|
+
A dictionary of initialized owners.
|
323
|
+
"""
|
324
|
+
# initialize data section by model
|
325
|
+
owners_all = ['Bus', 'Line', 'StaticGen',
|
326
|
+
'PV', 'Slack', 'RenGen',
|
327
|
+
'DG', 'ESD1', 'PVD1',
|
328
|
+
'StaticLoad']
|
329
|
+
|
330
|
+
# Filter owners that exist in the system
|
331
|
+
owners_e = list({
|
332
|
+
var.owner.class_name for var in rtn.vars.values() if var.owner is not None
|
333
|
+
}.union(
|
334
|
+
expr.owner.class_name for expr in rtn.exprs.values() if expr.owner is not None
|
335
|
+
).union(
|
336
|
+
exprc.owner.class_name for exprc in rtn.exprcs.values() if exprc.owner is not None
|
337
|
+
))
|
338
|
+
|
339
|
+
# Use a dictionary comprehension to create vars_by_owner
|
340
|
+
owners = {
|
341
|
+
name: {'idx': [],
|
342
|
+
'name': [],
|
343
|
+
'header': [],
|
344
|
+
'data': [], }
|
345
|
+
for name in owners_all if name in owners_e and getattr(rtn.system, name).n > 0
|
346
|
+
}
|
347
|
+
|
348
|
+
for key, val in owners.items():
|
349
|
+
owner = getattr(rtn.system, key)
|
350
|
+
idx_v = owner.get_idx()
|
351
|
+
val['idx'] = idx_v
|
352
|
+
val['name'] = owner.get(src='name', attr='v', idx=idx_v)
|
353
|
+
val['header'].append('Name')
|
354
|
+
val['data'].append(val['name'])
|
355
|
+
|
356
|
+
return owners
|
@@ -34,14 +34,11 @@ class ACOPF(DCPF0):
|
|
34
34
|
self.type = 'ACED'
|
35
35
|
|
36
36
|
self.map1 = OrderedDict() # ACOPF does not receive
|
37
|
-
self.map2
|
38
|
-
('Bus',
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
'pg': 'p0',
|
43
|
-
}),
|
44
|
-
])
|
37
|
+
self.map2.update({
|
38
|
+
'vBus': ('Bus', 'v0'),
|
39
|
+
'ug': ('StaticGen', 'u'),
|
40
|
+
'pg': ('StaticGen', 'p0'),
|
41
|
+
})
|
45
42
|
|
46
43
|
# --- params ---
|
47
44
|
self.c2 = RParam(info='Gen cost coefficient 2',
|
@@ -146,6 +146,53 @@ class DCOPF(DCPFBase):
|
|
146
146
|
info='total cost', unit='$',
|
147
147
|
sense='min', e_str=obj,)
|
148
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_idx()
|
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
|
+
|
149
196
|
def run(self, **kwargs):
|
150
197
|
"""
|
151
198
|
Run the routine.
|
@@ -88,6 +88,10 @@ class DCPFBase(RoutineBase):
|
|
88
88
|
v0=self.pg0)
|
89
89
|
|
90
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',)
|
91
95
|
self.aBus = Var(info='Bus voltage angle',
|
92
96
|
unit='rad',
|
93
97
|
name='aBus', tex_name=r'\theta_{bus}',
|
@@ -35,6 +35,11 @@ class DCPF0(RoutineBase):
|
|
35
35
|
self.info = 'DC Power Flow'
|
36
36
|
self.type = 'PF'
|
37
37
|
|
38
|
+
self.ug = RParam(info='Gen connection status',
|
39
|
+
name='ug', tex_name=r'u_{g}',
|
40
|
+
model='StaticGen', src='u',
|
41
|
+
no_parse=True)
|
42
|
+
|
38
43
|
# --- routine data ---
|
39
44
|
self.x = RParam(info="line reactance",
|
40
45
|
name='x', tex_name='x',
|
@@ -169,6 +174,7 @@ class DCPF0(RoutineBase):
|
|
169
174
|
except Exception as e:
|
170
175
|
logger.error(f"Failed to unpack results from {self.class_name}.\n{e}")
|
171
176
|
return False
|
177
|
+
self.system.report()
|
172
178
|
return True
|
173
179
|
else:
|
174
180
|
msg = f"{self.class_name} failed in "
|