Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 60 additions & 9 deletions pygridsim/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from altdss import altdss

from pygridsim.configs import NAME_TO_CONFIG
from pygridsim.defaults import RESERVED_PREFIXES
from pygridsim.lines import _make_line
from pygridsim.parameters import _make_generator, _make_load_node, _make_pv, _make_source_node
from pygridsim.results import _export_results, _query_solution
Expand All @@ -17,23 +18,34 @@ def __init__(self):
Stores numbers of circuit components to ensure unique naming of repeat circuit components.

Attributes:
num_generators (int): Number of generators created so far.
num_lines (int): Number of lines created so far.
num_loads (int): Number of load nodes created so far.
num_pv (int): Number of PVSystems create so far.
num_loads (int): Number of loads in circuit so far.
num_lines (int): Number of lines in circuit so far.
num_transformers (int): Number of transformers in circuit so far.
num_pv (int): Number of PV systems in circuit so far.
num_generators (int): Number generators in circuit so far.
nickname_to_name (dict[str, str]): Map from nicknames to their internal names.
"""
self.num_generators = 0
self.num_lines = 0
self.num_loads = 0
self.num_pv = 0
self.nickname_to_name = {}

altdss.ClearAll()
altdss('new circuit.MyCircuit')

def _check_naming(self, name):
if name in self.nickname_to_name:
raise ValueError("Provided name already assigned to a node")
if any(name.startswith(prefix) for prefix in RESERVED_PREFIXES):
raise ValueError(
"Cannot name nodes of the format 'component + __', ambiguity with internal names")

def add_load_nodes(self,
load_type: str = "house",
params: dict[str, int] = None,
num: int = 1):
num: int = 1,
names: list[str] = None):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add explicit typing:

params: Optional[dict[str, int]] = None,
names: Optional[list[str]] = None

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you recommend doing this with the "from typing import Optional" class? I'm not used to declaring typing with optional, and am getting "Call expression not allowed in type expressionPylancereportInvalidTypeForm" when doing it naively.

"""Adds Load Node(s) to circuit.

Allows the user to add num load nodes,
Expand All @@ -47,15 +59,26 @@ def add_load_nodes(self,
Load parameters for these manual additions. Defaults to empty dictionary.
num (int, optional):
The number of loads to create with these parameters. Defaults to 1.
names (list(str), optional):
Up to num names to assign as shortcuts to the loads

Returns:
list[OpenDSS object]:
A list of OpenDSS objects representing the load nodes created.
"""

params = params or dict()
names = names or list()
if len(names) > num:
raise ValueError("Specified more names of loads than number of nodes")

load_nodes = []
for _ in range(num):
for i in range(num):
if (len(names) > i):
self._check_naming(names[i])
internal_name = "load" + str(self.num_loads)
self.nickname_to_name[names[i]] = internal_name

_make_load_node(params, load_type, self.num_loads)
self.num_loads += 1

Expand Down Expand Up @@ -107,12 +130,19 @@ def add_PVSystems(self, load_nodes: list[str],

PV_nodes = []
for load in load_nodes:
if (load in self.nickname_to_name):
load = self.nickname_to_name[load]

PV_nodes.append(_make_pv(load, params, num_panels, self.num_pv))
self.num_pv += 1

return PV_nodes

def add_generators(self, num: int = 1, gen_type: str = "small", params: dict[str, int] = None):
def add_generators(self,
num: int = 1,
gen_type: str = "small",
params: dict[str, int] = None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit add typing same as above

names: list[str] = None):
"""Adds generator(s) to the system.

Args:
Expand All @@ -122,14 +152,25 @@ def add_generators(self, num: int = 1, gen_type: str = "small", params: dict[str
The type of generator (one of "small", "large", "industrial"). Defaults to "small".
params (dict[str, int], optional):
A dictionary of parameters to configure the generator. Defaults to None.
names (list(str), optional):
Up to num names to assign as shortcuts to the generators

Returns:
list[DSS objects]:
A list of OpenDSS objects representing the generators created.
"""
params = params or dict()
names = names or list()
if len(names) > num:
raise ValueError("Specified more names of generators than number of nodes")

generators = []
for _ in range(num):
for i in range(num):
if (len(names) > i):
self._check_naming(names[i])
internal_name = "generator" + str(self.num_generators)
self.nickname_to_name[names[i]] = internal_name

generators.append(_make_generator(params, gen_type, count=self.num_generators))
self.num_generators += 1

Expand Down Expand Up @@ -160,6 +201,13 @@ def add_lines(self,
"""
params = params or dict()
for src, dst in connections:
if (src in self.nickname_to_name):
src = self.nickname_to_name[src]
if (dst in self.nickname_to_name):
dst = self.nickname_to_name[dst]
if (src == dst):
raise ValueError("Tried to make a line between equivalent src and dst")

_make_line(src, dst, line_type, self.num_lines, params, transformer)
self.num_lines += 1

Expand All @@ -173,6 +221,9 @@ def solve(self):
"""
altdss.Solution.Solve()

def _get_name_to_nickname(self):
return {v: k for k, v in self.nickname_to_name.items()}

def results(self, queries: list[str], export_path=""):
"""Gets simulation results based on specified queries.

Expand All @@ -193,7 +244,7 @@ def results(self, queries: list[str], export_path=""):
"""
results = {}
for query in queries:
results[query] = _query_solution(query)
results[query] = _query_solution(query, self._get_name_to_nickname())

if (export_path):
_export_results(results, export_path)
Expand Down
2 changes: 2 additions & 0 deletions pygridsim/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@
VALID_LINE_TRANSFORMER_PARAMS = ["length", "XHL", "Conns"]
VALID_PV_PARAMS = ["kV", "phases"]
VALID_GENERATOR_PARAMS = ["kV", "kW", "phases"]

RESERVED_PREFIXES = ["load", "generator", "pv", "source"]
8 changes: 6 additions & 2 deletions pygridsim/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@
from altdss import altdss


def _query_solution(query):
def _query_solution(query, name_to_nickname):
query_fix = query.lower().replace(" ", "")
vector_losses = altdss.Losses()
vector_power = altdss.TotalPower()
match query_fix:
case "voltages":
bus_vmags = {}
for bus_name, bus_vmag in zip(altdss.BusNames(), altdss.BusVMag()):
bus_vmags[bus_name] = float(bus_vmag)
return_name = bus_name
if bus_name in name_to_nickname:
nickname = name_to_nickname[bus_name]
return_name += "/" + nickname
bus_vmags[return_name] = float(bus_vmag)
return bus_vmags
case "losses" | "loss":
losses = {}
Expand Down
33 changes: 32 additions & 1 deletion tests/test_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,38 @@ def test_011_configs(self):
with self.assertRaises(Exception):
circuit.update_source(source_type=SourceType.TURBINE)

def test_012_all_results(self):
def test_012_node_naming(self):
circuit = PyGridSim()
circuit.update_source()
# Create 4 nodes, only name 2 of them
circuit.add_load_nodes(num=4, load_type="house", names=["0", "1"])
circuit.add_generators(num=2, gen_type="small", names=["G0", "G1"])

# Add PVSystems to one of the nodes with shortcut
circuit.add_PVSystems(load_nodes=["load2", "0"])
# Can use original or abbreviated name
circuit.add_lines(connections=[("G0", "0"), ("generator1", "load1")])

with self.assertRaises(ValueError):
# Tries to assign an already assigned name
circuit.add_load_nodes(num=1, names=["G1"])

with self.assertRaises(ValueError):
# Tries to assign name to internal name load1, errors because adds ambiguity
circuit.add_load_nodes(num=1, names=["load1"])

with self.assertRaises(ValueError):
# Attempt to name 2 load nodes, but only initiating 1
circuit.add_load_nodes(names=["640", "641"])

with self.assertRaises(ValueError):
# Trying to create a line between a node and itself (nickname)
circuit.add_lines(connections=[("load0", "0")])

circuit.solve()
print(circuit.results(["Voltages", "Losses"]))

def test_013_all_results(self):
circuit = PyGridSim()
circuit.update_source()
circuit.add_load_nodes()
Expand Down