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.
Files changed (78) hide show
  1. docs/conf.py +96 -0
  2. esapp/__init__.py +26 -0
  3. esapp/apps/__init__.py +19 -0
  4. esapp/apps/dynamics.py +135 -0
  5. esapp/apps/gic.py +1178 -0
  6. esapp/apps/modes.py +35 -0
  7. esapp/apps/network.py +376 -0
  8. esapp/apps/static.py +501 -0
  9. esapp/dev/generate_components.py +266 -0
  10. esapp/gobject.py +123 -0
  11. esapp/grid.py +178613 -0
  12. esapp/indexable.py +231 -0
  13. esapp/saw/__init__.py +52 -0
  14. esapp/saw/_exceptions.py +148 -0
  15. esapp/saw/_helpers.py +109 -0
  16. esapp/saw/atc.py +451 -0
  17. esapp/saw/base.py +1552 -0
  18. esapp/saw/case_actions.py +403 -0
  19. esapp/saw/contingency.py +851 -0
  20. esapp/saw/fault.py +66 -0
  21. esapp/saw/general.py +829 -0
  22. esapp/saw/gic.py +380 -0
  23. esapp/saw/matrices.py +339 -0
  24. esapp/saw/modify.py +877 -0
  25. esapp/saw/oneline.py +302 -0
  26. esapp/saw/opf.py +132 -0
  27. esapp/saw/powerflow.py +241 -0
  28. esapp/saw/pv.py +169 -0
  29. esapp/saw/qv.py +155 -0
  30. esapp/saw/regions.py +193 -0
  31. esapp/saw/saw.py +55 -0
  32. esapp/saw/scheduled.py +173 -0
  33. esapp/saw/sensitivity.py +521 -0
  34. esapp/saw/timestep.py +339 -0
  35. esapp/saw/topology.py +409 -0
  36. esapp/saw/transient.py +472 -0
  37. esapp/saw/weather.py +196 -0
  38. esapp/utils/__init__.py +8 -0
  39. esapp/utils/b3d.py +234 -0
  40. esapp/utils/decorators.py +13 -0
  41. esapp/utils/exceptions.py +42 -0
  42. esapp/utils/map.py +204 -0
  43. esapp/utils/mathtools.py +451 -0
  44. esapp/utils/mesh.py +193 -0
  45. esapp/utils/misc.py +135 -0
  46. esapp/utils/plotwavelet.py +157 -0
  47. esapp/utils/shapes/Texas/Shape.cpg +1 -0
  48. esapp/utils/shapes/Texas/Shape.dbf +0 -0
  49. esapp/utils/shapes/Texas/Shape.prj +1 -0
  50. esapp/utils/shapes/Texas/Shape.shp +0 -0
  51. esapp/utils/shapes/Texas/Shape.shx +0 -0
  52. esapp/utils/shapes/US/Shape.cpg +1 -0
  53. esapp/utils/shapes/US/Shape.dbf +0 -0
  54. esapp/utils/shapes/US/Shape.prj +1 -0
  55. esapp/utils/shapes/US/Shape.shp +0 -0
  56. esapp/utils/shapes/US/Shape.shp.ea.iso.xml +254 -0
  57. esapp/utils/shapes/US/Shape.shx +0 -0
  58. esapp/utils/shapes/US/Shape.xml +531 -0
  59. esapp/workbench.py +1167 -0
  60. esapp-0.1.0.dist-info/METADATA +173 -0
  61. esapp-0.1.0.dist-info/RECORD +78 -0
  62. esapp-0.1.0.dist-info/WHEEL +5 -0
  63. esapp-0.1.0.dist-info/licenses/LICENSE +201 -0
  64. esapp-0.1.0.dist-info/top_level.txt +3 -0
  65. tests/config_test.example.py +18 -0
  66. tests/conftest.py +626 -0
  67. tests/test_apps_network_gic.py +247 -0
  68. tests/test_exceptions.py +376 -0
  69. tests/test_grid_components.py +192 -0
  70. tests/test_indexable_data_access.py +366 -0
  71. tests/test_integration_analysis.py +290 -0
  72. tests/test_integration_contingency.py +323 -0
  73. tests/test_integration_powerflow.py +356 -0
  74. tests/test_integration_saw_powerworld.py +331 -0
  75. tests/test_integration_workbench.py +341 -0
  76. tests/test_saw_core_methods.py +3119 -0
  77. tests/test_workbench.py +554 -0
  78. 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)