esapp 0.1.0__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.
- docs/conf.py +96 -0
- esapp/__init__.py +26 -0
- esapp/apps/__init__.py +19 -0
- esapp/apps/dynamics.py +135 -0
- esapp/apps/gic.py +1178 -0
- esapp/apps/modes.py +35 -0
- esapp/apps/network.py +376 -0
- esapp/apps/static.py +501 -0
- esapp/dev/generate_components.py +266 -0
- esapp/gobject.py +123 -0
- esapp/grid.py +178613 -0
- esapp/indexable.py +231 -0
- esapp/saw/__init__.py +52 -0
- esapp/saw/_exceptions.py +148 -0
- esapp/saw/_helpers.py +109 -0
- esapp/saw/atc.py +451 -0
- esapp/saw/base.py +1552 -0
- esapp/saw/case_actions.py +403 -0
- esapp/saw/contingency.py +851 -0
- esapp/saw/fault.py +66 -0
- esapp/saw/general.py +829 -0
- esapp/saw/gic.py +380 -0
- esapp/saw/matrices.py +339 -0
- esapp/saw/modify.py +877 -0
- esapp/saw/oneline.py +302 -0
- esapp/saw/opf.py +132 -0
- esapp/saw/powerflow.py +241 -0
- esapp/saw/pv.py +169 -0
- esapp/saw/qv.py +155 -0
- esapp/saw/regions.py +193 -0
- esapp/saw/saw.py +55 -0
- esapp/saw/scheduled.py +173 -0
- esapp/saw/sensitivity.py +521 -0
- esapp/saw/timestep.py +339 -0
- esapp/saw/topology.py +409 -0
- esapp/saw/transient.py +472 -0
- esapp/saw/weather.py +196 -0
- esapp/utils/__init__.py +8 -0
- esapp/utils/b3d.py +234 -0
- esapp/utils/decorators.py +13 -0
- esapp/utils/exceptions.py +42 -0
- esapp/utils/map.py +204 -0
- esapp/utils/mathtools.py +451 -0
- esapp/utils/mesh.py +193 -0
- esapp/utils/misc.py +135 -0
- esapp/utils/plotwavelet.py +157 -0
- esapp/utils/shapes/Texas/Shape.cpg +1 -0
- esapp/utils/shapes/Texas/Shape.dbf +0 -0
- esapp/utils/shapes/Texas/Shape.prj +1 -0
- esapp/utils/shapes/Texas/Shape.shp +0 -0
- esapp/utils/shapes/Texas/Shape.shx +0 -0
- esapp/utils/shapes/US/Shape.cpg +1 -0
- esapp/utils/shapes/US/Shape.dbf +0 -0
- esapp/utils/shapes/US/Shape.prj +1 -0
- esapp/utils/shapes/US/Shape.shp +0 -0
- esapp/utils/shapes/US/Shape.shp.ea.iso.xml +254 -0
- esapp/utils/shapes/US/Shape.shx +0 -0
- esapp/utils/shapes/US/Shape.xml +531 -0
- esapp/workbench.py +1167 -0
- esapp-0.1.0.dist-info/METADATA +173 -0
- esapp-0.1.0.dist-info/RECORD +78 -0
- esapp-0.1.0.dist-info/WHEEL +5 -0
- esapp-0.1.0.dist-info/licenses/LICENSE +201 -0
- esapp-0.1.0.dist-info/top_level.txt +3 -0
- tests/config_test.example.py +18 -0
- tests/conftest.py +626 -0
- tests/test_apps_network_gic.py +247 -0
- tests/test_exceptions.py +376 -0
- tests/test_grid_components.py +192 -0
- tests/test_indexable_data_access.py +366 -0
- tests/test_integration_analysis.py +290 -0
- tests/test_integration_contingency.py +323 -0
- tests/test_integration_powerflow.py +356 -0
- tests/test_integration_saw_powerworld.py +331 -0
- tests/test_integration_workbench.py +341 -0
- tests/test_saw_core_methods.py +3119 -0
- tests/test_workbench.py +554 -0
- tests/test_workbench_unit.py +243 -0
docs/conf.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
|
|
5
|
+
# Ensure the project root is in the path
|
|
6
|
+
sys.path.insert(0, os.path.abspath(".."))
|
|
7
|
+
|
|
8
|
+
extensions = [
|
|
9
|
+
"sphinx.ext.autodoc",
|
|
10
|
+
"sphinx.ext.autosummary", # Re-enabled to allow automatic class listing
|
|
11
|
+
"sphinx.ext.viewcode",
|
|
12
|
+
"sphinx.ext.todo",
|
|
13
|
+
"sphinx.ext.mathjax",
|
|
14
|
+
"sphinx.ext.inheritance_diagram",
|
|
15
|
+
"sphinx.ext.autosectionlabel",
|
|
16
|
+
"sphinx.ext.intersphinx",
|
|
17
|
+
"sphinx.ext.napoleon",
|
|
18
|
+
"sphinx_copybutton",
|
|
19
|
+
"nbsphinx",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
autosummary_generate = True
|
|
23
|
+
|
|
24
|
+
autodoc_default_options = {
|
|
25
|
+
"member-order": "groupwise",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
autodoc_preserve_defaults = True
|
|
29
|
+
todo_include_todos = True
|
|
30
|
+
autosectionlabel_prefix_document = True
|
|
31
|
+
|
|
32
|
+
autoclass_content = "both"
|
|
33
|
+
autodoc_typehints = "none"
|
|
34
|
+
add_module_names = False
|
|
35
|
+
|
|
36
|
+
intersphinx_mapping = {
|
|
37
|
+
"python": ("docs.python.org", None),
|
|
38
|
+
"numpy": ("numpy.org", None),
|
|
39
|
+
"scipy": ("docs.scipy.org", None),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
napoleon_google_docstring = False
|
|
43
|
+
napoleon_numpy_docstring = True
|
|
44
|
+
napoleon_include_init_with_doc = False
|
|
45
|
+
napoleon_use_param = True
|
|
46
|
+
napoleon_use_rtype = True
|
|
47
|
+
napoleon_preprocess_types = True
|
|
48
|
+
napoleon_type_aliases = {
|
|
49
|
+
"np": "numpy",
|
|
50
|
+
"np.ndarray": "~numpy.ndarray",
|
|
51
|
+
"pd": "pandas",
|
|
52
|
+
"pd.DataFrame": "~pandas.DataFrame",
|
|
53
|
+
"optional": "typing.Optional",
|
|
54
|
+
"union": "typing.Union",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
exclude_patterns = [
|
|
58
|
+
"_build",
|
|
59
|
+
"**/*.cpg",
|
|
60
|
+
"**/*.dbf",
|
|
61
|
+
"**/*.prj",
|
|
62
|
+
"**/*.shp",
|
|
63
|
+
"**/*.shx",
|
|
64
|
+
"**/Shape.xml",
|
|
65
|
+
"**/Shape.shp.ea.iso.xml",
|
|
66
|
+
"**/PWRaw",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
nbsphinx_execute = 'never'
|
|
70
|
+
html_sourcelink_suffix = ''
|
|
71
|
+
master_doc = "index"
|
|
72
|
+
|
|
73
|
+
project = "ESA++"
|
|
74
|
+
copyright = "2026, Luke Lowery"
|
|
75
|
+
author = "Luke Lowery"
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
version = importlib.metadata.version("esapp")
|
|
79
|
+
except importlib.metadata.PackageNotFoundError:
|
|
80
|
+
version = "unknown"
|
|
81
|
+
release = version
|
|
82
|
+
|
|
83
|
+
html_theme = "sphinx_rtd_theme"
|
|
84
|
+
html_theme_options = {
|
|
85
|
+
"navigation_depth": 2,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
autodoc_mock_imports = [
|
|
89
|
+
"win32com",
|
|
90
|
+
"win32com.client",
|
|
91
|
+
"pythoncom",
|
|
92
|
+
"geopandas",
|
|
93
|
+
"shapely",
|
|
94
|
+
"fiona",
|
|
95
|
+
"pyproj",
|
|
96
|
+
]
|
esapp/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
esapp: A Pythonic Interface for PowerWorld Simulator
|
|
3
|
+
=====================================================
|
|
4
|
+
|
|
5
|
+
The ``esapp`` package provides a high-level, object-oriented interface for
|
|
6
|
+
interacting with PowerWorld Simulator's Automation Server (SimAuto). It aims
|
|
7
|
+
to simplify common power systems analysis tasks by providing a more Pythonic
|
|
8
|
+
and user-friendly API.
|
|
9
|
+
|
|
10
|
+
The main entry point is the :class:`~.GridWorkBench` class.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Please keep the docstring above up to date with all the imports.
|
|
14
|
+
from .saw import (
|
|
15
|
+
SAW,
|
|
16
|
+
PowerWorldError,
|
|
17
|
+
COMError,
|
|
18
|
+
CommandNotRespectedError,
|
|
19
|
+
Error,
|
|
20
|
+
SimAutoFeatureError,
|
|
21
|
+
PowerWorldPrerequisiteError,
|
|
22
|
+
PowerWorldAddonError,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Main Grid Work Bench Class
|
|
26
|
+
from .workbench import GridWorkBench
|
esapp/apps/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Specialized Applications (:mod:`esapp.apps`)
|
|
3
|
+
============================================
|
|
4
|
+
|
|
5
|
+
This package contains higher-level, specialized tools for advanced power
|
|
6
|
+
systems analysis tasks built on top of the core ``esapp`` components.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Applications
|
|
10
|
+
from .gic import GIC
|
|
11
|
+
from .network import Network, BranchType
|
|
12
|
+
from .modes import ForcedOscillation
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"GIC",
|
|
16
|
+
"Network",
|
|
17
|
+
"BranchType",
|
|
18
|
+
"ForcedOscillation",
|
|
19
|
+
]
|
esapp/apps/dynamics.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from numpy import nan, float32
|
|
2
|
+
from pandas import DataFrame, concat
|
|
3
|
+
|
|
4
|
+
# WorkBench Imports
|
|
5
|
+
from ..grid import TSContingency
|
|
6
|
+
from ..indexable import Indexable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Dynamics(Indexable):
|
|
10
|
+
"""
|
|
11
|
+
Research-focused transient stability simulation application.
|
|
12
|
+
|
|
13
|
+
This class provides specialized functions for dynamic simulation,
|
|
14
|
+
transient stability contingency solving, and result extraction.
|
|
15
|
+
These functions are intentionally untested as they are for highly
|
|
16
|
+
specific research and data analysis.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def fields(self, metric):
|
|
20
|
+
'''Get TS Formatted Fields for Requested Objects'''
|
|
21
|
+
objs = self[metric["Type"]] # TODO I don't need to retrieve from PW I have it local. Will Speed up
|
|
22
|
+
os = objs['ObjectID']
|
|
23
|
+
flist = [f"{str(os.loc[i])} | {metric['Dynamic']}" for i in range(len(os))]
|
|
24
|
+
return flist
|
|
25
|
+
|
|
26
|
+
# Set Run Time for list of contingencies
|
|
27
|
+
def setRuntime(self, sec):
|
|
28
|
+
ctgs = self[TSContingency]
|
|
29
|
+
ctgs["StartTime"] = 0
|
|
30
|
+
ctgs["EndTime"] = sec
|
|
31
|
+
self[TSContingency] = ctgs
|
|
32
|
+
|
|
33
|
+
# Create 'SimOnly' contingency if it does not exist
|
|
34
|
+
# TODO Add TSCtgElement that closes an already closed gen at t=0
|
|
35
|
+
def simonly(self):
|
|
36
|
+
try:
|
|
37
|
+
self.esa.change_and_confirm_params_multiple_element(
|
|
38
|
+
ObjectType="TSContingency",
|
|
39
|
+
command_df=DataFrame({"TSCTGName": ["SimOnly"]}),
|
|
40
|
+
)
|
|
41
|
+
except CommandNotRespectedError:
|
|
42
|
+
print("Failure to create 'SimOnly' Contingency")
|
|
43
|
+
else:
|
|
44
|
+
print("Contingency 'SimOnly' Initialized")
|
|
45
|
+
|
|
46
|
+
def solve(self, ctgs: list[str] = None):
|
|
47
|
+
# Unique List of Fields to Request From PW
|
|
48
|
+
|
|
49
|
+
# Prepare Memory
|
|
50
|
+
self.esa.clearram()
|
|
51
|
+
|
|
52
|
+
# Gen Obj Field list and Mark Fields for RAM storage
|
|
53
|
+
objFields = []
|
|
54
|
+
flatFields = []
|
|
55
|
+
for objects, fields in self.retrieve:
|
|
56
|
+
self.esa.saveinram(objects, fields)
|
|
57
|
+
for id in objects['ObjectID']:
|
|
58
|
+
objFields += [f"{id} | {f}" for f in fields]
|
|
59
|
+
flatFields += fields
|
|
60
|
+
|
|
61
|
+
# Sim Only (No Contingency)
|
|
62
|
+
if ctgs is None:
|
|
63
|
+
self.simonly()
|
|
64
|
+
ctgs = ["SimOnly"]
|
|
65
|
+
# Cast To List
|
|
66
|
+
if not isinstance(ctgs, list):
|
|
67
|
+
ctgs = [ctgs]
|
|
68
|
+
|
|
69
|
+
# Only Sims Requested
|
|
70
|
+
self.esa.skipallbut(ctgs)
|
|
71
|
+
|
|
72
|
+
# Set Runtime for Simulation
|
|
73
|
+
self.setRuntime(self.runtime)
|
|
74
|
+
|
|
75
|
+
# Execute Dynamic Simulation for Specified CTGs - High Compute Time
|
|
76
|
+
self.esa.TSSolveAll()
|
|
77
|
+
|
|
78
|
+
# Get Results
|
|
79
|
+
meta, df = (None, None)
|
|
80
|
+
for ctg in ctgs:
|
|
81
|
+
# Extract incoming Dataframe
|
|
82
|
+
metaSim, dfSim = self.esa.TSGetContingencyResults(ctg, objFields)
|
|
83
|
+
# Weird ESA bug where if items at end are open, they don't return data :(
|
|
84
|
+
# Happened Again, if last column is zero valued it removes the column. Awful
|
|
85
|
+
|
|
86
|
+
# Proposed Fix: Will Fill in any floating columns not present in data but in meta
|
|
87
|
+
fillIdx = metaSim.index[~metaSim.index.isin(dfSim.columns)]
|
|
88
|
+
dfSim[fillIdx] = 0.0
|
|
89
|
+
|
|
90
|
+
# Custom Fields, Add CTG, Append to Main
|
|
91
|
+
metaSim.drop(columns=["Label", "ColHeader"], inplace=True)
|
|
92
|
+
metaSim.rename(
|
|
93
|
+
columns={
|
|
94
|
+
"ObjectType": "Object",
|
|
95
|
+
"PrimaryKey": "ID-A",
|
|
96
|
+
"SecondaryKey": "ID-B",
|
|
97
|
+
'VariableName': "Metric"
|
|
98
|
+
},
|
|
99
|
+
inplace=True,
|
|
100
|
+
)
|
|
101
|
+
metaSim['Metric'] = flatFields
|
|
102
|
+
metaSim["Contingency"] = ctg
|
|
103
|
+
meta = concat(
|
|
104
|
+
[metaSim] if meta is None else [meta, metaSim],
|
|
105
|
+
axis=0,
|
|
106
|
+
ignore_index=True,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Won't work if first result is bad
|
|
110
|
+
if len(dfSim.columns) < 2:
|
|
111
|
+
dfSim = DataFrame(nan, columns=metaSim.index, index=[0])
|
|
112
|
+
dfSim.index.name = "time"
|
|
113
|
+
else:
|
|
114
|
+
# Trim Data Size, Index by Time, Append to Main
|
|
115
|
+
dfSim = dfSim.astype(float32)
|
|
116
|
+
dfSim.set_index("time", inplace=True)
|
|
117
|
+
|
|
118
|
+
#if df is not None:
|
|
119
|
+
#print(df.join(dfSim,how='outer'))
|
|
120
|
+
#TODO only working for same types of ctgs
|
|
121
|
+
df = concat(
|
|
122
|
+
[dfSim] if df is None else [df, dfSim], axis=1, ignore_index=True
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Clean Up -------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
# Hard Copy Before Wiping RAM
|
|
128
|
+
meta: DataFrame = meta.copy(deep=True)
|
|
129
|
+
df: DataFrame = df.copy(deep=True)
|
|
130
|
+
|
|
131
|
+
# Clear RAM in PW
|
|
132
|
+
self.esa.clearram()
|
|
133
|
+
|
|
134
|
+
# Return as meta/data tuple
|
|
135
|
+
return (meta, df)
|