Skip to content

Commit ebe4d66

Browse files
authored
Merge branch 'main' into node_naming
2 parents 8594034 + 2d6c914 commit ebe4d66

File tree

10 files changed

+190
-47
lines changed

10 files changed

+190
-47
lines changed

HISTORY.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,13 @@
1-
# History
1+
History
2+
=======
3+
## 0.1.0 - 2025-05-01
4+
5+
First PyGridSim release to PyPI: https://pypi.org/project/pygridsim/
6+
7+
* Add Proper Packaging - [PR #1](https://github.com/DAI-Lab/PyGridSim/pull/1) by @sarahmish
8+
* Add Type Checking, Exports, Enum Config File, Generators/PV - [PR #3](https://github.com/DAI-Lab/PyGridSim/pull/3) by @amzhao16
9+
* Pass Github Actions - [PR #5](https://github.com/DAI-Lab/PyGridSim/pull/5) by @amzhao16
10+
* Add Naming Consistency, Customization Tests - [PR #6](https://github.com/DAI-Lab/PyGridSim/pull/6) by @amzhao16
11+
* Support User Query Functions - [PR #7](https://github.com/DAI-Lab/PyGridSim/pull/7) by @amzhao16
12+
* Enhance Results.py - [PR #8](https://github.com/DAI-Lab/PyGridSim/pull/8) by @amzhao16
13+
* Update README - [PR #11](https://github.com/DAI-Lab/PyGridSim/pull/11) by @amzhao16

README.md

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,37 @@
44
</p>
55

66
<!-- Uncomment these lines after releasing the package to PyPI for version and downloads badges -->
7-
<!--[![PyPI Shield](https://img.shields.io/pypi/v/pygridsim.svg)](https://pypi.python.org/pypi/pygridsim)-->
8-
<!--[![Downloads](https://pepy.tech/badge/pygridsim)](https://pepy.tech/project/pygridsim)-->
9-
[![Github Actions Shield](https://img.shields.io/github/workflow/status/amzhao/PyGridSim/Run%20Tests)](https://github.com/amzhao/PyGridSim/actions)
10-
[![Coverage Status](https://codecov.io/gh/amzhao/PyGridSim/branch/master/graph/badge.svg)](https://codecov.io/gh/amzhao/PyGridSim)
11-
12-
7+
[![Development Status](https://img.shields.io/badge/Development%20Status-2%20--%20Pre--Alpha-yellow)](https://pypi.org/search/?c=Development+Status+%3A%3A+2+-+Pre-Alpha)
8+
[![PyPI Shield](https://img.shields.io/pypi/v/pygridsim.svg)](https://pypi.python.org/pypi/pygridsim)
9+
[![Downloads](https://pepy.tech/badge/pygridsim)](https://pepy.tech/project/pygridsim)
10+
[![Run Tests](https://github.com/DAI-Lab/PyGridSim/actions/workflows/tests.yml/badge.svg)](https://github.com/DAI-Lab/PyGridSim/actions/workflows/tests.yml)
1311

1412
# PyGridSim
1513

16-
Package to simulate OpenDSS circuits on Python.
14+
PyGridSim is a package for simulating OpenDSS circuits. PyGridSim uses a functional interface to allow users to efficiently generate circuits of various scopes.
1715

18-
NOTE: README comes from dai cookie cutter, will be updated
19-
NOTE: will be moved to Dai lab repository
20-
21-
- Documentation: https://amzhao.github.io/PyGridSim
22-
- Homepage: https://github.com/amzhao/PyGridSim
16+
- Documentation: https://dtail.gitbook.io/pygridsim
17+
- Homepage: https://github.com/DAI-Lab/PyGridSim/
2318

2419
# Overview
2520

26-
PyGridSim aims to provide accessible access to tools like OpenDSS, AltDSS using Python. The goal is to create large-scale electrical circuits representing residential neighborhoods (and other scenarios) using an intuitive interface, without any background in OpenDSS software.
21+
PyGridSim allows users to create and customize circuits. Users can either fully specify each component they add to the circuit, or lean on library-provided parameter sets. PyGridSim supports the batch creation of every circuit component, emphasizing scalability and efficiently in building large circuits.
2722

28-
# Install
23+
# Installation
2924

3025
## Requirements
3126

32-
**PyGridSim** has been developed and tested on [Python 3.5, 3.6, 3.7 and 3.8](https://www.python.org/downloads/)
27+
**PyGridSim** has been developed and tested on [Python 3.10, 3.11 and 3.12](https://www.python.org/downloads/)
3328

3429
Also, although it is not strictly required, the usage of a [virtualenv](https://virtualenv.pypa.io/en/latest/)
3530
is highly recommended in order to avoid interfering with other software installed in the system
3631
in which **PyGridSim** is run.
3732

38-
These are the minimum commands needed to create a virtualenv using python3.6 for **PyGridSim**:
33+
These are the minimum commands needed to create a virtualenv using python3.10 for **PyGridSim**:
3934

4035
```bash
4136
pip install virtualenv
42-
virtualenv -p $(which python3.6) PyGridSim-venv
37+
virtualenv -p $(which python3.10) PyGridSim-venv
4338
```
4439

4540
Afterwards, you have to execute this command to activate the virtualenv:
@@ -75,23 +70,60 @@ git checkout stable
7570
make install
7671
```
7772

78-
## Install for Development
73+
# Quick Start
74+
Users of PyGridSim have the option between creating a fully customized circuit and using PyGridSim-provided parameters to build their circuit. Consider the simplest circuit: one source, one load, and a line connecting them. The following code snippet demonstrates how to model and print results on this circuit on PyGridSim with both methods.
75+
76+
## Customized Circuit Creation
77+
```python
78+
from pygridsim import PyGridSim
79+
circuit = PyGridSim()
80+
81+
# Add Custom Source and Load
82+
circuit.add_load_nodes(params={"kV": 0.12, "kW": 1, "kvar": 1})
83+
circuit.update_source(params={"kV": 0.5})
84+
85+
# Add Line
86+
circuit.add_lines([("source", "load0")], params={"length": 1})
87+
88+
# Solve and Print Results
89+
circuit.solve()
90+
print(circuit.results(["Voltages", "Losses"]))
91+
circuit.clear()
92+
```
93+
94+
Running this code yields the following printed output:
95+
```
96+
{'Voltages': {'source': 499.7123955784113, 'load0': 120.73408045756985}, 'Losses': {'Active Power Loss': 465617.30157676246, 'Reactive Power Loss': 969502.2898991327}}
97+
```
98+
99+
Note that the losses here are expressed in Watts, and the Voltages in Volts. The circuit observes some loss due to a much higher source voltage than load voltage, with most of the loss being reactive power loss. The circuit is created with a step-down transformer in the line by default, which enables the source and load to maintain isolated voltage levels at rest.
79100

80-
If you want to contribute to the project, a few more steps are required to make the project ready
81-
for development.
101+
## Defaults-Based Circuit Creation
102+
```python
103+
from pygridsim import PyGridSim
104+
circuit = PyGridSim()
82105

83-
Please head to the [Contributing Guide](https://amzhao.github.io/PyGridSim/contributing.html#get-started)
84-
for more details about this process.
106+
# Add Custom Source and Load
107+
circuit.add_load_nodes(load_type="house")
108+
circuit.update_source(source_type="turbine")
85109

86-
# Quickstart
110+
# Add Line
111+
circuit.add_lines([("source", "load0")], line_type="lv")
87112

88-
In this short tutorial we will guide you through a series of steps that will help you
89-
getting started with **PyGridSim**.
113+
# Solve and Print Results
114+
circuit.solve()
115+
print(circuit.results(["Voltages", "Losses"]))
116+
circuit.clear()
117+
```
118+
119+
The following output is printed:
120+
```
121+
{'Voltages': {'source': 2418.845494533779, 'load0': 169.53107121049976}, 'Losses': {'Active Power Loss': 351310.95859906287, 'Reactive Power Loss': 730351.7183868886}}
122+
```
90123

91-
TODO: Create a step by step guide here. Also figure out how to ensure prerequisites properly.
124+
The defaults-based ranges for source and load nodes are higher than the ones specified in the above customization example, explaining the higher voltages set for both nodes. We once again observe some loss in the system, with most of that being in reactive power loss.
92125

93-
# What's next?
126+
# Resources
94127

95128
For more details about **PyGridSim** and all its possibilities
96-
and features, please check the [documentation site](
97-
https://amzhao.github.io/PyGridSim/).
129+
and features, please check the [Gitbook page for PyGridSim](https://dtail.gitbook.io/pygridsim)

pygridsim/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
__author__ = 'Angela Zhao'
66
__email__ = '[email protected]'
7-
__version__ = '0.1.0.dev1'
7+
__version__ = '0.1.1.dev0'
88

99
from pygridsim.core import PyGridSim
1010

pygridsim/configs.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,10 @@
6666
"kW": defaults.INDUSTRIAL_GEN_KW,
6767
}
6868
}
69+
70+
NAME_TO_CONFIG = {
71+
"load": LOAD_CONFIGURATIONS,
72+
"source": SOURCE_CONFIGURATIONS,
73+
"generator": GENERATOR_CONFIGURATIONS,
74+
"line": LINE_CONFIGURATIONS
75+
}

pygridsim/core.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from altdss import altdss
33

44
from pygridsim.defaults import RESERVED_PREFIXES
5+
from pygridsim.configs import NAME_TO_CONFIG
56
from pygridsim.lines import _make_line
67
from pygridsim.parameters import _make_generator, _make_load_node, _make_pv, _make_source_node
78
from pygridsim.results import _export_results, _query_solution
@@ -24,12 +25,12 @@ def __init__(self):
2425
num_generators (int): Number generators in circuit so far.
2526
nickname_to_name (dict[str, str]): Map from nicknames to their internal names.
2627
"""
27-
self.num_loads = 0
28+
self.num_generators = 0
2829
self.num_lines = 0
29-
self.num_transformers = 0
30+
self.num_loads = 0
3031
self.num_pv = 0
31-
self.num_generators = 0
3232
self.nickname_to_name = {}
33+
3334
altdss.ClearAll()
3435
altdss('new circuit.MyCircuit')
3536

@@ -65,6 +66,7 @@ def add_load_nodes(self,
6566
list[OpenDSS object]:
6667
A list of OpenDSS objects representing the load nodes created.
6768
"""
69+
6870
params = params or dict()
6971
names = names or list()
7072
if len(names) > num:
@@ -229,7 +231,9 @@ def results(self, queries: list[str], export_path=""):
229231
230232
Args:
231233
queries (list[str]):
232-
A list of queries to the circuit ("Voltages", "Losses", "TotalPower")
234+
A list of queries to the circuit: one of ("Voltages", "Losses", "TotalPower")
235+
or partial queries ("RealLoss", "ReactiveLoss", "RealPower", "ReactivePower")
236+
that query one component of Losses/TotalPower
233237
export_path (str, optional):
234238
The file path to export results. If empty, results are not exported.
235239
Defaults to "".
@@ -254,6 +258,37 @@ def clear(self):
254258
None
255259
"""
256260
altdss.ClearAll()
257-
self.num_loads = 0
258-
self.num_lines = 0
259-
self.num_transformers = 0
261+
262+
def get_types(self, component: str, show_ranges: bool = False):
263+
"""Provides list of all supported Load Types
264+
265+
Args:
266+
component (str):
267+
Which component to get, one of (one of "load", "source", "pv", "line")
268+
show_ranges (bool, optional):
269+
Whether to show all configuration ranges in output.
270+
271+
Returns:
272+
list:
273+
A list containing all load types, if show_ranges False.
274+
A list of tuples showing all load types and configurations, if show_ranges True.
275+
"""
276+
component_simplified = component.lower().replace(" ", "")
277+
if (component_simplified[-1] == "s"):
278+
component_simplified = component_simplified[:-1]
279+
configuration = {}
280+
if component_simplified in NAME_TO_CONFIG:
281+
configuration = NAME_TO_CONFIG[component_simplified]
282+
else:
283+
raise KeyError(
284+
f"Invalid component input: expect one of {[name for name in NAME_TO_CONFIG]}")
285+
286+
if not show_ranges:
287+
return [component_type.value for component_type in configuration]
288+
289+
component_types = []
290+
for component_type in configuration:
291+
config_dict = configuration[component_type]
292+
component_types.append((component_type.value, config_dict))
293+
294+
return component_types

pygridsim/results.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
from altdss import altdss
88

9-
109
def _query_solution(query, name_to_nickname):
11-
match query:
12-
case "Voltages":
10+
query_fix = query.lower().replace(" ", "")
11+
vector_losses = altdss.Losses()
12+
vector_power = altdss.TotalPower()
13+
match query_fix:
14+
case "voltages":
1315
bus_vmags = {}
1416
for bus_name, bus_vmag in zip(altdss.BusNames(), altdss.BusVMag()):
1517
return_name = bus_name
@@ -18,14 +20,24 @@ def _query_solution(query, name_to_nickname):
1820
return_name += "/" + nickname
1921
bus_vmags[return_name] = float(bus_vmag)
2022
return bus_vmags
21-
case "Losses":
22-
vector_losses = altdss.Losses()
23+
case "losses" | "loss":
2324
losses = {}
2425
losses["Active Power Loss"] = vector_losses.real
2526
losses["Reactive Power Loss"] = vector_losses.imag
2627
return losses
27-
case "TotalPower":
28-
return altdss.TotalPower()
28+
case "totalpower" | "power":
29+
power = {}
30+
power["Active Power"] = vector_power.real
31+
power["Reactive Power"] = vector_power.imag
32+
return power
33+
case "activeloss" | "activepowerloss" | "realloss" | "realpowerloss":
34+
return vector_losses.real
35+
case "reactiveloss" | "reactivepowerloss":
36+
return vector_losses.imag
37+
case "activepower" | "realpower":
38+
return vector_power.real
39+
case "reactivepower":
40+
return vector_power.imag
2941
case _:
3042
return "Invalid"
3143

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.1.0.dev1
2+
current_version = 0.1.1.dev0
33
commit = True
44
tag = True
55
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<release>[a-z]+)(?P<candidate>\d+))?

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,6 @@
9090
test_suite='tests',
9191
tests_require=tests_require,
9292
url='https://github.com/amzhao/PyGridSim',
93-
version='0.1.0.dev1',
93+
version='0.1.1.dev0',
9494
zip_safe=False,
9595
)

sim.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Voltages": {
3+
"source": 9999.598243195385,
4+
"load0": 8294.334838925915
5+
},
6+
"Losses": {
7+
"Active Power Loss": 9059444.425434513,
8+
"Reactive Power Loss": 31084427.834760502
9+
}
10+
}

tests/test_circuit.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,19 @@ def test_012_node_naming(self):
193193
circuit.solve()
194194
print(circuit.results(["Voltages", "Losses"]))
195195

196+
def test_013_all_results(self):
197+
circuit = PyGridSim()
198+
circuit.update_source()
199+
circuit.add_load_nodes()
200+
circuit.add_generators(num=2, gen_type="small")
201+
circuit.add_lines([("source", "load0"), ("generator0", "load0")])
202+
circuit.solve()
203+
# Should be flexible with capitalization, spaces
204+
queries = ["Voltages", "losses", "Total Power"]
205+
# Add "partial" queries to just parts of losses/total power
206+
queries += ["realpowerloss", "reactive Loss", "Active Power", "reactivepower"]
207+
print(circuit.results(queries))
208+
196209

197210
class TestCustomizedCircuit(unittest.TestCase):
198211
"""
@@ -292,3 +305,25 @@ def test_106_transformer_parameters(self):
292305
circuit.solve()
293306
print(circuit.results(["Voltages", "Losses"]))
294307
circuit.clear()
308+
309+
310+
class TestTypeQueryFunctions(unittest.TestCase):
311+
312+
def setUp(self):
313+
"""Set up test fixtures, if any."""
314+
print("\nTest", self._testMethodName)
315+
316+
def tearDown(self):
317+
"""Tear down test fixtures, if any."""
318+
319+
def test_200_type_queries(self):
320+
circuit = PyGridSim()
321+
# should still work if plural, capitalized, spaces
322+
for component in ["load ", "sources", "Line", "GENERATOR"]:
323+
print(circuit.get_types(component))
324+
print(circuit.get_types(component, show_ranges=True))
325+
326+
def test_200_invalid_type_quer(self):
327+
circuit = PyGridSim()
328+
with self.assertRaises(KeyError):
329+
circuit.get_types("bad_component_name")

0 commit comments

Comments
 (0)