ltbams 1.0.11__tar.gz → 1.0.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-1.0.11/ltbams.egg-info → ltbams-1.0.13}/PKG-INFO +4 -2
- {ltbams-1.0.11 → ltbams-1.0.13}/README.md +2 -1
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/_version.py +3 -3
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/core/matprocessor.py +183 -118
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/io/matpower.py +55 -20
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/io/psse.py +4 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/opt/exprcalc.py +11 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/grbopt.py +2 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/pypower.py +21 -4
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/routine.py +127 -15
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/shared.py +30 -2
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/system.py +51 -3
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/utils/paths.py +64 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/index.rst +4 -3
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/release-notes.rst +25 -10
- {ltbams-1.0.11 → ltbams-1.0.13/ltbams.egg-info}/PKG-INFO +4 -2
- {ltbams-1.0.11 → ltbams-1.0.13}/ltbams.egg-info/SOURCES.txt +1 -1
- {ltbams-1.0.11 → ltbams-1.0.13}/ltbams.egg-info/requires.txt +1 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ltbams.egg-info/top_level.txt +0 -1
- {ltbams-1.0.11 → ltbams-1.0.13}/pyproject.toml +3 -1
- {ltbams-1.0.11 → ltbams-1.0.13}/requirements-dev.txt +1 -1
- {ltbams-1.0.11 → ltbams-1.0.13}/requirements.txt +2 -1
- {ltbams-1.0.11 → ltbams-1.0.13}/setup.py +1 -1
- ltbams-1.0.13/tests/test_export_json.py +110 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_io.py +10 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_known_good.py +6 -6
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_matp.py +61 -34
- ltbams-1.0.13/tests/test_paths.py +89 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_rtn_dcopf.py +20 -0
- ltbams-1.0.11/tests/__init__.py +0 -0
- ltbams-1.0.11/tests/test_paths.py +0 -22
- {ltbams-1.0.11 → ltbams-1.0.13}/CONTRIBUTING.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/LICENSE +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/MANIFEST.in +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/__main__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/5bus/pjm5bus_demo.json +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/5bus/pjm5bus_demo.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/5bus/pjm5bus_ev.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/5bus/pjm5bus_jumper.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/hawaii40/Hawaii40.m +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee123/ieee123.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee123/ieee123_regcv1.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee14/ieee14.json +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee14/ieee14.raw +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee14/ieee14_conn.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee14/ieee14_uced.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee39/ieee39.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee39/ieee39_uced.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee39/ieee39_uced_esd1.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee39/ieee39_uced_pvd1.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/ieee39/ieee39_uced_vis.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/matpower/benchmark.json +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/matpower/case118.m +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/matpower/case14.m +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/matpower/case300.m +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/matpower/case39.m +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/matpower/case5.m +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/matpower/case_ACTIVSg2000.m +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/npcc/npcc.m +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/npcc/npcc_uced.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/pglib/pglib_opf_case39_epri__api.m +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/wecc/wecc.m +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cases/wecc/wecc_uced.xlsx +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/cli.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/core/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/core/common.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/core/documenter.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/core/model.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/core/param.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/core/service.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/core/symprocessor.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/core/var.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/extension/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/extension/eva.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/interface.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/io/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/io/json.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/io/pypower.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/io/xlsx.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/main.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/area.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/bus.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/cost.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/distributed/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/distributed/esd1.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/distributed/ev.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/distributed/pvd1.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/group.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/info.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/line.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/renewable/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/renewable/regc.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/reserve.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/shunt.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/static/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/static/gen.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/static/pq.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/timeslot.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/models/zone.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/opt/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/opt/constraint.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/opt/expression.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/opt/objective.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/opt/omodel.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/opt/optzbase.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/opt/param.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/opt/var.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/report.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/acopf.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/dcopf.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/dcopf2.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/dcpf.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/dopf.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/ed.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/pflow.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/rted.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/type.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/routines/uc.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/utils/__init__.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ams/utils/misc.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/Makefile +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/make.bat +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/_templates/autosummary/base.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/_templates/autosummary/class.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/_templates/autosummary/module.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/_templates/autosummary/module_toctree.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/api.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/conf.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/examples/index.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/genmodelref.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/genroutineref.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/copyright.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/formats/index.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/formats/matpower.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/formats/psse.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/formats/pypower.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/formats/xlsx.png +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/formats/xlsx.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/index.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/install.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/overview.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/testcase.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/getting_started/verification.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/images/curent.ico +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/images/dcopf_time.png +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/images/educ_pie.png +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/images/sponsors/CURENT_Logo_NameOnTrans.png +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/images/sponsors/CURENT_Logo_Transparent.png +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/images/sponsors/CURENT_Logo_Transparent_Name.png +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/images/sponsors/doe.png +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/modeling/example.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/modeling/index.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/modeling/model.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/modeling/routine.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/docs/source/modeling/system.rst +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ltbams.egg-info/dependency_links.txt +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/ltbams.egg-info/entry_points.txt +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/setup.cfg +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_1st_system.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_addressing.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_case.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_cli.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_export_csv.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_group.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_interface.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_jumper.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_model.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_omodel.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_report.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_repr.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_routine.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_rtn_acopf.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_rtn_dcopf2.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_rtn_ed.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_rtn_opf.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_rtn_pflow.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_rtn_pypower.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_rtn_rted.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_rtn_uc.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/tests/test_service.py +0 -0
- {ltbams-1.0.11 → ltbams-1.0.13}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ltbams
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.13
|
4
4
|
Summary: Python software for scheduling modeling and co-simulation with dynamics.
|
5
5
|
Home-page: https://github.com/CURENT/ams
|
6
6
|
Author: Jinning Wang
|
@@ -23,6 +23,7 @@ Requires-Dist: openpyxl
|
|
23
23
|
Requires-Dist: andes>=1.9.3
|
24
24
|
Requires-Dist: pybind11
|
25
25
|
Requires-Dist: cvxpy
|
26
|
+
Requires-Dist: cffi
|
26
27
|
Provides-Extra: dev
|
27
28
|
Requires-Dist: pytest; extra == "dev"
|
28
29
|
Requires-Dist: pytest-cov; extra == "dev"
|
@@ -60,6 +61,7 @@ Python Software for Power System Scheduling Modeling and Co-Simulation with Dyna
|
|
60
61
|
| Badges | | |
|
61
62
|
|---|---|---|
|
62
63
|
| Repo |  |  |
|
64
|
+
| Python | [](https://www.python.org/) |
|
63
65
|
| Version | [](https://pypi.org/project/ltbams/) | [](https://anaconda.org/conda-forge/ltbams) |
|
64
66
|
| Tag | [](https://github.com/CURENT/ams/tags) |  |
|
65
67
|
| Documentation | [](https://ltb.readthedocs.io/projects/ams/en/stable/?badge=stable) | [](https://ltb.readthedocs.io/projects/ams/en/develop/?badge=develop) |
|
@@ -217,7 +219,7 @@ sa
|
|
217
219
|
# Citing AMS
|
218
220
|
If you use AMS for research or consulting, please cite the following paper in your publication that uses AMS:
|
219
221
|
|
220
|
-
> J. Wang et al., "Dynamics-
|
222
|
+
> J. Wang et al., "Dynamics-Incorporated Modeling Framework for Stability Constrained Scheduling Under High-Penetration of Renewable Energy," in IEEE Transactions on Sustainable Energy, vol. 16, no. 3, pp. 1673-1685, July 2025, doi: 10.1109/TSTE.2025.3528027.
|
221
223
|
|
222
224
|
# Sponsors and Contributors
|
223
225
|
AMS is the scheduling simulation engine for the CURENT Largescale Testbed (LTB).
|
@@ -10,6 +10,7 @@ Python Software for Power System Scheduling Modeling and Co-Simulation with Dyna
|
|
10
10
|
| Badges | | |
|
11
11
|
|---|---|---|
|
12
12
|
| Repo |  |  |
|
13
|
+
| Python | [](https://www.python.org/) |
|
13
14
|
| Version | [](https://pypi.org/project/ltbams/) | [](https://anaconda.org/conda-forge/ltbams) |
|
14
15
|
| Tag | [](https://github.com/CURENT/ams/tags) |  |
|
15
16
|
| Documentation | [](https://ltb.readthedocs.io/projects/ams/en/stable/?badge=stable) | [](https://ltb.readthedocs.io/projects/ams/en/develop/?badge=develop) |
|
@@ -167,7 +168,7 @@ sa
|
|
167
168
|
# Citing AMS
|
168
169
|
If you use AMS for research or consulting, please cite the following paper in your publication that uses AMS:
|
169
170
|
|
170
|
-
> J. Wang et al., "Dynamics-
|
171
|
+
> J. Wang et al., "Dynamics-Incorporated Modeling Framework for Stability Constrained Scheduling Under High-Penetration of Renewable Energy," in IEEE Transactions on Sustainable Energy, vol. 16, no. 3, pp. 1673-1685, July 2025, doi: 10.1109/TSTE.2025.3528027.
|
171
172
|
|
172
173
|
# Sponsors and Contributors
|
173
174
|
AMS is the scheduling simulation engine for the CURENT Largescale Testbed (LTB).
|
@@ -8,11 +8,11 @@ import json
|
|
8
8
|
|
9
9
|
version_json = '''
|
10
10
|
{
|
11
|
-
"date": "2025-
|
11
|
+
"date": "2025-08-18T15:13:43-0700",
|
12
12
|
"dirty": false,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "1.0.
|
14
|
+
"full-revisionid": "1aab83cb951d517c906fe117499d482da8b6e66b",
|
15
|
+
"version": "1.0.13"
|
16
16
|
}
|
17
17
|
''' # END VERSION_JSON
|
18
18
|
|
@@ -3,18 +3,18 @@ Module for system matrix make.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import logging
|
6
|
-
import os
|
7
|
-
import sys
|
8
6
|
from typing import Optional
|
9
7
|
|
10
8
|
import numpy as np
|
11
9
|
|
12
10
|
from andes.thirdparty.npfunc import safe_div
|
13
|
-
from andes.
|
14
|
-
from andes.utils.misc import elapsed, is_notebook
|
11
|
+
from andes.utils.misc import elapsed
|
15
12
|
|
16
13
|
from ams.opt import Param
|
17
|
-
|
14
|
+
|
15
|
+
from ams.utils.paths import get_export_path
|
16
|
+
|
17
|
+
from ams.shared import pd, sps, _init_pbar, _update_pbar
|
18
18
|
|
19
19
|
logger = logging.getLogger(__name__)
|
20
20
|
|
@@ -71,35 +71,135 @@ class MParam(Param):
|
|
71
71
|
self.col_names = col_names
|
72
72
|
self.row_names = row_names
|
73
73
|
|
74
|
+
def load_npz(self, path=None):
|
75
|
+
"""
|
76
|
+
Load the FULL matrix from a npz file.
|
77
|
+
|
78
|
+
Parameters
|
79
|
+
----------
|
80
|
+
path : str, optional
|
81
|
+
Path of the npz file to load.
|
82
|
+
|
83
|
+
Returns
|
84
|
+
-------
|
85
|
+
MParam
|
86
|
+
The loaded MParam instance.
|
87
|
+
|
88
|
+
.. versionadded:: 1.0.13
|
89
|
+
"""
|
90
|
+
|
91
|
+
if path is None:
|
92
|
+
raise ValueError("Path to the npz file is required.")
|
93
|
+
|
94
|
+
data = sps.load_npz(path) if self.sparse else np.load(path)
|
95
|
+
|
96
|
+
if self.sparse:
|
97
|
+
self._v = data.tocsr()
|
98
|
+
logging.debug(f"Loading sparse matrix {self.name} from npz format.")
|
99
|
+
else:
|
100
|
+
self._v = data['v']
|
101
|
+
logging.warning(f"Loading dense matrix {self.name} from npz format.")
|
102
|
+
|
103
|
+
return self
|
104
|
+
|
105
|
+
def load_csv(self, path=None, chunksize=None, dtype=float):
|
106
|
+
"""
|
107
|
+
Load the matrix from an EXPORTED CSV file.
|
108
|
+
|
109
|
+
Parameters
|
110
|
+
----------
|
111
|
+
path : str, optional
|
112
|
+
Path of the csv file to load.
|
113
|
+
chunksize : int, optional
|
114
|
+
If specified, read the csv file in chunks of this size.
|
115
|
+
|
116
|
+
Returns
|
117
|
+
-------
|
118
|
+
MParam
|
119
|
+
The loaded MParam instance.
|
120
|
+
|
121
|
+
.. versionadded:: 1.0.13
|
122
|
+
"""
|
123
|
+
|
124
|
+
if path is None:
|
125
|
+
raise ValueError("Path to the csv file is required.")
|
126
|
+
|
127
|
+
if chunksize:
|
128
|
+
chunks = pd.read_csv(path, index_col=0, chunksize=chunksize, dtype=dtype)
|
129
|
+
df = pd.concat(chunks)
|
130
|
+
else:
|
131
|
+
df = pd.read_csv(path, index_col=0, dtype=dtype)
|
132
|
+
|
133
|
+
if self.sparse:
|
134
|
+
self._v = sps.csr_matrix(df.values)
|
135
|
+
logging.debug(f"Loading sparse matrix {self.name} from csv format.")
|
136
|
+
else:
|
137
|
+
self._v = df.values
|
138
|
+
self.col_names = df.columns.tolist()
|
139
|
+
self.row_names = df.index.tolist()
|
140
|
+
|
141
|
+
logging.debug(f"Loading matrix {self.name} from csv format.")
|
142
|
+
return self
|
143
|
+
|
144
|
+
def export_npz(self, path=None):
|
145
|
+
"""
|
146
|
+
Export the matrix to a npz file.
|
147
|
+
|
148
|
+
Parameters
|
149
|
+
----------
|
150
|
+
path : str, optional
|
151
|
+
Path of the npz file to export.
|
152
|
+
|
153
|
+
Returns
|
154
|
+
-------
|
155
|
+
str
|
156
|
+
The exported npz file name
|
157
|
+
|
158
|
+
.. versionadded:: 1.0.13
|
159
|
+
"""
|
160
|
+
|
161
|
+
path, file_name = get_export_path(self.owner.system,
|
162
|
+
self.name,
|
163
|
+
path=path,
|
164
|
+
fmt='npz')
|
165
|
+
|
166
|
+
if sps.issparse(self._v):
|
167
|
+
sps.save_npz(path, self._v.tocsr())
|
168
|
+
logging.debug(f"Saving sparse matrix {self.name} to npz format.")
|
169
|
+
elif isinstance(self._v, np.ndarray):
|
170
|
+
np.savez(path, v=self._v) # Save with a key 'v' inside the NPZ archive
|
171
|
+
logging.warning(f"Saving dense matrix {self.name} to npz format.")
|
172
|
+
else:
|
173
|
+
raise TypeError(f"Unsupported matrix type: {type(self._v)}")
|
174
|
+
|
175
|
+
return file_name
|
176
|
+
|
74
177
|
def export_csv(self, path=None):
|
75
178
|
"""
|
76
179
|
Export the matrix to a CSV file.
|
77
180
|
|
181
|
+
In the exported CSV, columns are the bus idxes, and Line idxes are
|
182
|
+
used as row indexes.
|
183
|
+
|
78
184
|
Parameters
|
79
185
|
----------
|
80
186
|
path : str, optional
|
81
|
-
Path
|
187
|
+
Path of the csv file to export.
|
82
188
|
|
83
189
|
Returns
|
84
190
|
-------
|
85
191
|
str
|
86
|
-
The
|
192
|
+
The exported csv file name
|
87
193
|
"""
|
88
194
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
else:
|
94
|
-
file_name = os.path.splitext(self.owner.system.files.fullname)[0]
|
95
|
-
file_name += f'_{self.name}'
|
96
|
-
path = os.path.join(os.getcwd(), file_name + '.csv')
|
97
|
-
else:
|
98
|
-
file_name = os.path.splitext(os.path.basename(path))[0]
|
195
|
+
path, file_name = get_export_path(self.owner.system,
|
196
|
+
self.name,
|
197
|
+
path=path,
|
198
|
+
fmt='csv')
|
99
199
|
|
100
200
|
pd.DataFrame(data=self.v, columns=self.col_names, index=self.row_names).to_csv(path)
|
101
201
|
|
102
|
-
return file_name
|
202
|
+
return file_name
|
103
203
|
|
104
204
|
@property
|
105
205
|
def v(self):
|
@@ -150,7 +250,6 @@ class MatProcessor:
|
|
150
250
|
def __init__(self, system):
|
151
251
|
self.system = system
|
152
252
|
self.initialized = False
|
153
|
-
self.pbar = None
|
154
253
|
|
155
254
|
self.Cft = MParam(name='Cft', tex_name=r'C_{ft}',
|
156
255
|
info='Line connectivity matrix',
|
@@ -183,10 +282,10 @@ class MatProcessor:
|
|
183
282
|
|
184
283
|
self.PTDF = MParam(name='PTDF', tex_name=r'P_{TDF}',
|
185
284
|
info='Power transfer distribution factor',
|
186
|
-
v=None, sparse=
|
285
|
+
v=None, sparse=True, owner=self)
|
187
286
|
self.LODF = MParam(name='LODF', tex_name=r'O_{TDF}',
|
188
287
|
info='Line outage distribution factor',
|
189
|
-
v=None, sparse=
|
288
|
+
v=None, sparse=True, owner=self)
|
190
289
|
|
191
290
|
def build(self, force=False):
|
192
291
|
"""
|
@@ -271,6 +370,8 @@ class MatProcessor:
|
|
271
370
|
row = np.array([system.Bus.idx2uid(x) for x in on_gen_bus])
|
272
371
|
col = np.array([idx_gen.index(x) for x in on_gen_idx])
|
273
372
|
self.Cg._v = sps.csr_matrix((np.ones(len(on_gen_idx)), (row, col)), (nb, ng))
|
373
|
+
self.Cg.col_names = idx_gen
|
374
|
+
self.Cg.row_names = system.Bus.idx.v
|
274
375
|
return self.Cg._v
|
275
376
|
|
276
377
|
def build_cl(self):
|
@@ -298,6 +399,8 @@ class MatProcessor:
|
|
298
399
|
row = np.array([system.Bus.idx2uid(x) for x in on_load_bus])
|
299
400
|
col = np.array([system.PQ.idx2uid(x) for x in on_load_idx])
|
300
401
|
self.Cl._v = sps.csr_matrix((np.ones(len(on_load_idx)), (row, col)), (nb, npq))
|
402
|
+
self.Cl.col_names = idx_load
|
403
|
+
self.Cl.row_names = system.Bus.idx.v
|
301
404
|
return self.Cl._v
|
302
405
|
|
303
406
|
def build_csh(self):
|
@@ -325,6 +428,8 @@ class MatProcessor:
|
|
325
428
|
row = np.array([system.Bus.idx2uid(x) for x in on_shunt_bus])
|
326
429
|
col = np.array([system.Shunt.idx2uid(x) for x in on_shunt_idx])
|
327
430
|
self.Csh._v = sps.csr_matrix((np.ones(len(on_shunt_idx)), (row, col)), (nb, nsh))
|
431
|
+
self.Csh.col_names = idx_shunt
|
432
|
+
self.Csh.row_names = system.Bus.idx.v
|
328
433
|
return self.Csh._v
|
329
434
|
|
330
435
|
def build_cft(self):
|
@@ -357,6 +462,10 @@ class MatProcessor:
|
|
357
462
|
col_line = np.array([system.Line.idx2uid(x) for x in on_line_idx + on_line_idx])
|
358
463
|
self.Cft._v = sps.csr_matrix((data_line, (row_line, col_line)), (nb, nl))
|
359
464
|
self.CftT._v = self.Cft._v.T
|
465
|
+
self.Cft.col_names = idx_line
|
466
|
+
self.Cft.row_names = system.Bus.idx.v
|
467
|
+
self.CftT.col_names = system.Bus.idx.v
|
468
|
+
self.CftT.row_names = idx_line
|
360
469
|
return self.Cft._v
|
361
470
|
|
362
471
|
def build_bf(self):
|
@@ -384,6 +493,8 @@ class MatProcessor:
|
|
384
493
|
t = system.Bus.idx2uid(system.Line.get(src='bus2', attr='v', idx=idx_line))
|
385
494
|
ir = np.r_[range(nl), range(nl)] # double set of row indices
|
386
495
|
self.Bf._v = sps.csr_matrix((np.r_[b, -b], (ir, np.r_[f, t])), (nl, nb))
|
496
|
+
self.Bf.col_names = system.Bus.idx.v
|
497
|
+
self.Bf.row_names = system.Line.idx.v
|
387
498
|
return self.Bf._v
|
388
499
|
|
389
500
|
def build_bbus(self):
|
@@ -396,6 +507,8 @@ class MatProcessor:
|
|
396
507
|
DC bus admittance matrix.
|
397
508
|
"""
|
398
509
|
self.Bbus._v = self.Cft._v * self.Bf._v
|
510
|
+
self.Bbus.col_names = self.system.Bus.idx.v
|
511
|
+
self.Bbus.row_names = self.system.Bus.idx.v
|
399
512
|
return self.Bbus._v
|
400
513
|
|
401
514
|
def build_pfinj(self):
|
@@ -411,6 +524,8 @@ class MatProcessor:
|
|
411
524
|
b = self._calc_b()
|
412
525
|
phi = self.system.Line.get(src='phi', attr='v', idx=idx_line)
|
413
526
|
self.Pfinj._v = b * (-phi)
|
527
|
+
# NOTE: leave the row_names empty for the vector
|
528
|
+
self.Pfinj.col_names = self.system.Line.idx.v
|
414
529
|
return self.Pfinj._v
|
415
530
|
|
416
531
|
def build_pbusinj(self):
|
@@ -423,6 +538,8 @@ class MatProcessor:
|
|
423
538
|
Bus power injection vector.
|
424
539
|
"""
|
425
540
|
self.Pbusinj._v = self.Cft._v * self.Pfinj._v
|
541
|
+
# NOTE: leave the row_names empty for the vector
|
542
|
+
self.Pbusinj.col_names = self.system.Bus.idx.v
|
426
543
|
return self.Pbusinj._v
|
427
544
|
|
428
545
|
def _calc_b(self):
|
@@ -453,7 +570,7 @@ class MatProcessor:
|
|
453
570
|
return b
|
454
571
|
|
455
572
|
def build_ptdf(self, line=None, no_store=False,
|
456
|
-
incremental=False, step=1000, no_tqdm=
|
573
|
+
incremental=False, step=1000, no_tqdm=True,
|
457
574
|
permc_spec=None, use_umfpack=True):
|
458
575
|
"""
|
459
576
|
Build the Power Transfer Distribution Factor (PTDF) matrix and optionally store it in `MParam.PTDF`.
|
@@ -461,11 +578,8 @@ class MatProcessor:
|
|
461
578
|
PTDF[m, n] represents the increased line flow on line `m` for a 1 p.u. power injection at bus `n`.
|
462
579
|
It is similar to the Generation Shift Factor (GSF).
|
463
580
|
|
464
|
-
|
465
|
-
|
466
|
-
For large cases, use `incremental=True` to calculate the sparse PTDF in chunks, which will be stored
|
467
|
-
as a `scipy.sparse.lil_matrix`. In this mode, the PTDF is calculated in chunks, and a progress bar
|
468
|
-
will be shown unless `no_tqdm=True`.
|
581
|
+
For large cases, use `incremental=True` to calculate the sparse PTDF in chunks. In this mode, the
|
582
|
+
PTDF is calculated in chunks, and thus more memory friendly.
|
469
583
|
|
470
584
|
Parameters
|
471
585
|
----------
|
@@ -487,7 +601,7 @@ class MatProcessor:
|
|
487
601
|
|
488
602
|
Returns
|
489
603
|
-------
|
490
|
-
PTDF :
|
604
|
+
PTDF : scipy.sparse.lil_matrix
|
491
605
|
Power transfer distribution factor.
|
492
606
|
|
493
607
|
References
|
@@ -507,13 +621,18 @@ class MatProcessor:
|
|
507
621
|
|
508
622
|
if line is None:
|
509
623
|
luid = system.Line.idx2uid(system.Line.idx.v)
|
624
|
+
self.PTDF.row_names = system.Line.idx.v
|
510
625
|
elif isinstance(line, (int, str)):
|
511
626
|
try:
|
512
627
|
luid = [system.Line.idx2uid(line)]
|
628
|
+
self.PTDF.row_names = [line]
|
513
629
|
except ValueError:
|
514
630
|
raise ValueError(f"Line {line} not found.")
|
515
631
|
elif isinstance(line, list):
|
516
632
|
luid = system.Line.idx2uid(line)
|
633
|
+
self.PTDF.row_names = line
|
634
|
+
|
635
|
+
self.PTDF.col_names = system.Bus.idx.v
|
517
636
|
|
518
637
|
# build other matrices if not built
|
519
638
|
if not self.initialized:
|
@@ -528,15 +647,7 @@ class MatProcessor:
|
|
528
647
|
|
529
648
|
if incremental:
|
530
649
|
# initialize progress bar
|
531
|
-
|
532
|
-
self.pbar = tqdm_nb(total=100, unit='%', file=sys.stdout,
|
533
|
-
disable=no_tqdm)
|
534
|
-
else:
|
535
|
-
self.pbar = tqdm(total=100, unit='%', ncols=80, ascii=True,
|
536
|
-
file=sys.stdout, disable=no_tqdm)
|
537
|
-
|
538
|
-
self.pbar.update(0)
|
539
|
-
last_pc = 0
|
650
|
+
pbar = _init_pbar(total=100, unit='%', no_tqdm=no_tqdm)
|
540
651
|
|
541
652
|
H = sps.lil_matrix((nline, system.Bus.n))
|
542
653
|
|
@@ -549,23 +660,13 @@ class MatProcessor:
|
|
549
660
|
use_umfpack=use_umfpack).T
|
550
661
|
H[start:end, noslack] = sol
|
551
662
|
|
552
|
-
|
553
|
-
perc = np.round(min((end / nline) * 100, 100), 2)
|
663
|
+
_update_pbar(pbar, end, nline)
|
554
664
|
|
555
|
-
perc_diff = perc - last_pc
|
556
|
-
if perc_diff >= 1:
|
557
|
-
self.pbar.update(perc_diff)
|
558
|
-
last_pc = perc
|
559
|
-
|
560
|
-
# finish progress bar
|
561
|
-
self.pbar.update(100 - last_pc)
|
562
|
-
# removed `pbar` so that System object can be serialized
|
563
|
-
self.pbar.close()
|
564
|
-
self.pbar = None
|
565
665
|
else:
|
566
|
-
H =
|
567
|
-
|
568
|
-
|
666
|
+
H = sps.lil_matrix((nline, nbus))
|
667
|
+
sol = np.linalg.solve(Bbus.todense()[np.ix_(noslack, noref)].T,
|
668
|
+
Bf.todense()[np.ix_(luid, noref)].T).T
|
669
|
+
H[:, noslack] = sol
|
569
670
|
|
570
671
|
# reshape results into 1D array if only one line
|
571
672
|
if isinstance(line, (int, str)):
|
@@ -577,7 +678,7 @@ class MatProcessor:
|
|
577
678
|
return H
|
578
679
|
|
579
680
|
def build_lodf(self, line=None, no_store=False,
|
580
|
-
incremental=False, step=1000, no_tqdm=
|
681
|
+
incremental=False, step=1000, no_tqdm=True):
|
581
682
|
"""
|
582
683
|
Build the Line Outage Distribution Factor matrix and store it in the
|
583
684
|
MParam `LODF`.
|
@@ -608,7 +709,7 @@ class MatProcessor:
|
|
608
709
|
|
609
710
|
Returns
|
610
711
|
-------
|
611
|
-
LODF :
|
712
|
+
LODF : scipy.sparse.lil_matrix
|
612
713
|
Line outage distribution factor.
|
613
714
|
|
614
715
|
References
|
@@ -636,81 +737,45 @@ class MatProcessor:
|
|
636
737
|
# build PTDF if not built
|
637
738
|
if self.PTDF._v is None:
|
638
739
|
ptdf = self.build_ptdf(no_store=True, incremental=incremental, step=step)
|
639
|
-
if incremental and isinstance(self.PTDF._v, np.ndarray):
|
640
|
-
ptdf = sps.lil_matrix(self.PTDF._v)
|
641
740
|
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
self.
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
# NOTE:
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
h_chunk = H_chunk.diagonal(-luidi[0])
|
662
|
-
rden = safe_div(np.ones(H_chunk.shape),
|
663
|
-
np.tile(np.ones_like(h_chunk) - h_chunk, (nbranch, 1)))
|
664
|
-
H_chunk = H_chunk.multiply(rden).tolil()
|
665
|
-
# NOTE: use lil_matrix to set diagonal values as -1
|
666
|
-
rsid = sps.diags(H_chunk.diagonal(-luidi[0])) + sps.eye(H_chunk.shape[1])
|
667
|
-
if H_chunk.shape[0] > rsid.shape[0]:
|
668
|
-
Rsid = sps.lil_matrix(H_chunk.shape)
|
669
|
-
Rsid[luidi, :] = rsid
|
670
|
-
else:
|
671
|
-
Rsid = rsid
|
672
|
-
H_chunk = H_chunk - Rsid
|
673
|
-
LODF[:, [luid.index(i) for i in luidi]] = H_chunk
|
674
|
-
|
675
|
-
# show progress in percentage
|
676
|
-
perc = np.round(min((luid.index(luidi[-1]) / nline) * 100, 100), 2)
|
677
|
-
|
678
|
-
perc_diff = perc - last_pc
|
679
|
-
if perc_diff >= 1:
|
680
|
-
self.pbar.update(perc_diff)
|
681
|
-
last_pc = perc
|
682
|
-
|
683
|
-
# finish progress bar
|
684
|
-
self.pbar.update(100 - last_pc)
|
685
|
-
# removed `pbar` so that System object can be serialized
|
686
|
-
self.pbar.close()
|
687
|
-
self.pbar = None
|
688
|
-
else:
|
689
|
-
H = ptdf @ self.Cft._v[:, luid]
|
690
|
-
h = np.diag(H, -luid[0])
|
691
|
-
LODF = safe_div(H,
|
692
|
-
np.tile(np.ones_like(h) - h, (nbranch, 1)))
|
693
|
-
# # NOTE: reset the diagonal elements to -1
|
694
|
-
rsid = np.diag(np.diag(LODF, -luid[0])) + np.eye(nline, nline)
|
695
|
-
if LODF.shape[0] > rsid.shape[0]:
|
696
|
-
Rsid = np.zeros_like(LODF)
|
697
|
-
Rsid[luid, :] = rsid
|
741
|
+
# initialize progress bar
|
742
|
+
pbar = _init_pbar(total=100, unit='%', no_tqdm=no_tqdm)
|
743
|
+
|
744
|
+
LODF = sps.lil_matrix((nbranch, nline))
|
745
|
+
|
746
|
+
# NOTE: for LODF, we are doing it columns by columns
|
747
|
+
# reshape luid to list of list by step
|
748
|
+
luidp = [luid[i:i + step] for i in range(0, len(luid), step)]
|
749
|
+
for luidi in luidp:
|
750
|
+
H_chunk = ptdf @ self.Cft._v[:, luidi]
|
751
|
+
h_chunk = H_chunk.diagonal(-luidi[0])
|
752
|
+
rden = safe_div(np.ones(H_chunk.shape),
|
753
|
+
np.tile(np.ones_like(h_chunk) - h_chunk, (nbranch, 1)))
|
754
|
+
H_chunk = H_chunk.multiply(rden).tolil()
|
755
|
+
# NOTE: use lil_matrix to set diagonal values as -1
|
756
|
+
rsid = sps.diags(H_chunk.diagonal(-luidi[0])) + sps.eye(H_chunk.shape[1])
|
757
|
+
if H_chunk.shape[0] > rsid.shape[0]:
|
758
|
+
Rsid = sps.lil_matrix(H_chunk.shape)
|
759
|
+
Rsid[luidi, :] = rsid
|
698
760
|
else:
|
699
761
|
Rsid = rsid
|
700
|
-
|
762
|
+
H_chunk = H_chunk - Rsid
|
763
|
+
LODF[:, [luid.index(i) for i in luidi]] = H_chunk
|
764
|
+
|
765
|
+
_update_pbar(pbar, luid.index(luidi[-1]), nline)
|
701
766
|
|
702
767
|
# reshape results into 1D array if only one line
|
703
768
|
if isinstance(line, (int, str)):
|
704
769
|
LODF = LODF[:, 0]
|
705
770
|
|
706
|
-
if (not no_store)
|
771
|
+
if (not no_store) and (line is None):
|
707
772
|
self.LODF._v = LODF
|
708
773
|
return LODF
|
709
774
|
|
710
775
|
def build_otdf(self, line=None):
|
711
776
|
"""
|
712
|
-
Build the Outrage Transfer Distribution Factor (OTDF) matrix for
|
713
|
-
k outage: $OTDF_k = PTDF + LODF[:, k] @ PTDF[k, ]$.
|
777
|
+
Build the Outrage Transfer Distribution Factor (OTDF) matrix for
|
778
|
+
**line k** outage: $OTDF_k = PTDF + LODF[:, k] @ PTDF[k, ]$.
|
714
779
|
|
715
780
|
OTDF_k[m, n] means the increased line flow on line `m` when there is
|
716
781
|
1 p.u. power injection at bus `n` when line `k` is outage.
|
@@ -726,7 +791,7 @@ class MatProcessor:
|
|
726
791
|
|
727
792
|
Returns
|
728
793
|
-------
|
729
|
-
OTDF :
|
794
|
+
OTDF : scipy.sparse.csr_matrix
|
730
795
|
Line outage distribution factor.
|
731
796
|
|
732
797
|
References
|
@@ -751,4 +816,4 @@ class MatProcessor:
|
|
751
816
|
luid = self.system.Line.idx2uid(line)
|
752
817
|
|
753
818
|
otdf = ptdf + lodf[:, luid] @ ptdf[luid, :]
|
754
|
-
return otdf
|
819
|
+
return otdf.tocsr()
|