diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..8b50edf --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,15 @@ +* PyGridSim version: +* Python version: +* Operating System: + +### Description + +Describe what you were trying to get done. +Tell us what happened, what went wrong, and what you expected to happen. + +### What I Did + +``` +Paste the command(s) you ran and the output. +If there was a crash, please include the traceback here. +``` diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..7a7b8c1 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,61 @@ +name: Run Tests + +on: + push: + branches: [ '*' ] + pull_request: + branches: [ main ] + +jobs: + docs: + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + os: [ubuntu-latest] + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install package + run: | + python -m pip install --upgrade pip setuptools + pip install .[dev] + - name: make docs + run: make docs + + lint: + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install package and dependencies + run: pip install .[dev] + - name: make lint + run: make lint + + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install package and dependencies + run: pip install .[test] + - name: make test + run: make test diff --git a/Makefile b/Makefile index d643ddc..38bf8d4 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ install-test: clean-build clean-pyc ## install the package and test dependencies .PHONY: test test: ## run tests quickly with the default Python - python -m pytest --basetemp=${ENVTMPDIR} --cov=pygridsim --cov-report xml + python -m pytest --cov=pygridsim --cov-report xml .PHONY: lint lint: ## check style with flake8 and isort diff --git a/docs/conf.py b/docs/conf.py index eee45a4..5f8aa3b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ # relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. -import sphinx_rtd_theme # For read the docs theme +import sphinx_rtd_theme # For read the docs theme import pygridsim @@ -31,12 +31,12 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - 'm2r', + 'm2r2', 'sphinx.ext.autodoc', 'sphinx.ext.githubpages', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', - 'autodocsumm', + 'sphinx.ext.autosummary', ] autodoc_default_options = { @@ -76,7 +76,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/pygridsim/__init__.py b/pygridsim/__init__.py index adb2fc4..ab40042 100644 --- a/pygridsim/__init__.py +++ b/pygridsim/__init__.py @@ -6,4 +6,6 @@ __email__ = 'amzhao@mit.edu' __version__ = '0.1.0.dev1' -from pygridsim.core import PyGridSim as PyGridSim \ No newline at end of file +from pygridsim.core import PyGridSim + +__all__ = ['PyGridSim'] diff --git a/pygridsim/configs.py b/pygridsim/configs.py index 226698f..cc8bd3e 100644 --- a/pygridsim/configs.py +++ b/pygridsim/configs.py @@ -1,20 +1,20 @@ -from pygridsim.enums import LoadType, LineType, GeneratorType, SourceType import pygridsim.defaults as defaults +from pygridsim.enums import GeneratorType, LineType, LoadType, SourceType LOAD_CONFIGURATIONS = { LoadType.HOUSE: { - "kV": defaults.HOUSE_KV, - "kW": defaults.HOUSE_KW, + "kV": defaults.HOUSE_KV, + "kW": defaults.HOUSE_KW, "kvar": defaults.HOUSE_KVAR }, LoadType.COMMERCIAL: { "kV": defaults.COMMERCIAL_KV, - "kW": defaults.COMMERCIAL_KW, + "kW": defaults.COMMERCIAL_KW, "kvar": defaults.COMMERCIAL_KVAR }, LoadType.INDUSTRIAL: { - "kV": defaults.INDUSTRIAL_KV, - "kW": defaults.INDUSTRIAL_KW, + "kV": defaults.INDUSTRIAL_KV, + "kW": defaults.INDUSTRIAL_KW, "kvar": defaults.INDUSTRIAL_KVAR } } @@ -54,15 +54,15 @@ GENERATOR_CONFIGURATIONS = { GeneratorType.SMALL: { - "kV": defaults.SMALL_GEN_KV, - "kW": defaults.SMALL_GEN_KW, + "kV": defaults.SMALL_GEN_KV, + "kW": defaults.SMALL_GEN_KW, }, GeneratorType.LARGE: { - "kV": defaults.LARGE_GEN_KV, - "kW": defaults.LARGE_GEN_KW, + "kV": defaults.LARGE_GEN_KV, + "kW": defaults.LARGE_GEN_KW, }, GeneratorType.INDUSTRIAL: { - "kV": defaults.INDUSTRIAL_GEN_KV, - "kW": defaults.INDUSTRIAL_GEN_KW, + "kV": defaults.INDUSTRIAL_GEN_KV, + "kW": defaults.INDUSTRIAL_GEN_KW, } -} \ No newline at end of file +} diff --git a/pygridsim/core.py b/pygridsim/core.py index ca6a488..7444482 100644 --- a/pygridsim/core.py +++ b/pygridsim/core.py @@ -1,192 +1,211 @@ # -*- coding: utf-8 -*- from altdss import altdss -from pygridsim.parameters import _make_load_node, _make_source_node, _make_generator, _make_pv -from pygridsim.results import _query_solution, _export_results + 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 """Main module.""" + class PyGridSim: - def __init__(self): - """Initialize OpenDSS engine. - - Instantiate an OpenDSS circuit that user can build circuit components on. Stores numbers of circuit components - to ensure unique naming of repeat circuit components - - Attributes: - 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. - """ - self.num_loads = 0 - self.num_lines = 0 - self.num_transformers = 0 - self.num_pv = 0 - self.num_generators = 0 - altdss.ClearAll() - altdss('new circuit.MyCircuit') - - def add_load_nodes(self, load_type: str = "house", params: dict[str, int] = None, num: int = 1): - """Adds Load Node(s) to circuit. - - Allows the user to add num load nodes, either with customized parameters or using a default load_type. - - Args: - load_type (str, optional): - Load type as a string, one of "house", "commercial", "industrial". Defaults to "house". - params (dict[str, int], optional): - 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. - - Returns: - list[OpenDSS object]: - A list of OpenDSS objects representing the load nodes created. - """ - params = params or dict() - load_nodes = [] - for _ in range(num): - _make_load_node(params, load_type, self.num_loads) - self.num_loads += 1 - - return load_nodes - - def update_source(self, source_type: str = "turbine", params: dict[str, int] = None): - """Adds or updates source node in system. - - If a Vsource node does not exist, it is created. Otherwise, its parameters are updated based on the provided values. - - Args: - source_type (str, optional): - The type of the source (one of "turbine", "powerplant", "lvsub", "mvsub", "hvsub", "shvsub"). Defaults to "turbine". - params (dict[str, int], optional): - A dictionary of parameters to configure the source node. Defaults to None. - - Returns: - OpenDSS object: - The OpenDSS object representing the source node. - """ - params = params or dict() - return _make_source_node(params, source_type) - - def add_PVSystem(self, load_nodes: list[str], params: dict[str, int] = None, num_panels: int = 1): - """Adds a photovoltaic (PV) system to the specified load nodes. - - Adds PV system with num_panels to each of the listed load nodes. Can be customized with parameters. - - Args: - load_nodes (list[str]): - A list of node names where the PV system will be connected. - params (dict[str, int], optional): - A dictionary of additional parameters for the PV system. Defaults to None. - num_panels (int, optional): - The number of PV panels in the system. Defaults to 1. - - Returns: - list[DSS objects]: - A list of OpenDSS objects representing the PV systems created. - """ - params = params or dict() - if not load_nodes: - raise ValueError("Need to enter load nodes to add PVSystem to") - - PV_nodes = [] - for load in load_nodes: - PV_nodes.append(_make_pv(load, params, num_panels, self.num_pv)) - self.num_pv += 1 - - return PV_nodes - - def add_generator(self, num: int = 1, gen_type: str = "small", params: dict[str, int] = None): - """Adds generator(s) to the system. - - Args: - num (int, optional): - The number of generator units to add. Defaults to 1. - gen_type (str, optional): - 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. - - Returns: - list[DSS objects]: - A list of OpenDSS objects representing the generators created. - """ - params = params or dict() - generators = [] - for _ in range(num): - generators.append(_make_generator(params, gen_type, count=self.num_generators)) - self.num_generators += 1 - - return generators - - - def add_lines(self, connections: list[tuple], line_type: str = "lv", params: dict[str, int] = None, transformer: bool = True): - """Adds lines to the system. - - Adds electrical lines according to the given connections. Users can specify the parameters of the lines or otherwise use given line type options. - - Args: - connections (list[tuple]): - A list of tuples defining the connections between nodes. - line_type (str, optional): - The type of line (one of "lv", "mv", "hv"). Defaults to "lv". - params (dict[str, int], optional): - A dictionary of parameters to configure the lines. Defaults to None. - transformer (bool, optional): - Whether to include a transformer in the connection. Defaults to True. - - Returns: - None - """ - params = params or dict() - for src, dst in connections: - _make_line(src, dst, line_type, self.num_lines, params, transformer) - self.num_lines += 1 - - def solve(self): - """Solves the OpenDSS circuit. - - Initializes "solve" mode in OpenDSS, which then allows the user to query results on the circuit. - - Returns: - None - """ - altdss.Solution.Solve() - - def results(self, queries: list[str], export_path = ""): - """Gets simulation results based on specified queries. - - Allows the user to query for many results at once by providing a list of desired queries. - - Args: - queries (list[str]): - A list of queries to the circuit ("Voltages", "Losses", "TotalPower") - export_path (str, optional): - The file path to export results. If empty, results are not exported. Defaults to "". - - Returns: - dict: - A dictionary containing the fetched simulation results. - """ - results = {} - for query in queries: - results[query] = _query_solution(query) - - if (export_path): - _export_results(results, export_path) - - return results - - def clear(self): - """Clears the OpenDSS circuit. - - Returns: - None - """ - altdss.ClearAll() - self.num_loads = 0 - self.num_lines = 0 - self.num_transformers = 0 \ No newline at end of file + def __init__(self): + """Initialize OpenDSS engine. + + Instantiate an OpenDSS circuit that user can build circuit components on. + Stores numbers of circuit components to ensure unique naming of repeat circuit components. + + Attributes: + 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. + """ + self.num_loads = 0 + self.num_lines = 0 + self.num_transformers = 0 + self.num_pv = 0 + self.num_generators = 0 + altdss.ClearAll() + altdss('new circuit.MyCircuit') + + def add_load_nodes(self, + load_type: str = "house", + params: dict[str, int] = None, + num: int = 1): + """Adds Load Node(s) to circuit. + + Allows the user to add num load nodes, + either with customized parameters or using a default load_type. + + Args: + load_type (str, optional): + Load type as a string, one of "house", "commercial", "industrial". + Defaults to "house". + params (dict[str, int], optional): + 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. + + Returns: + list[OpenDSS object]: + A list of OpenDSS objects representing the load nodes created. + """ + params = params or dict() + load_nodes = [] + for _ in range(num): + _make_load_node(params, load_type, self.num_loads) + self.num_loads += 1 + + return load_nodes + + def update_source(self, source_type: str = "turbine", params: dict[str, int] = None): + """Adds or updates source node in system. + + If a Vsource node does not exist, it is created. + Otherwise, its parameters are updated based on the provided values. + + Args: + source_type (str, optional): + The type of the source + ("turbine", "powerplant", "lvsub", "mvsub", "hvsub", "shvsub"). + Defaults to "turbine". + params (dict[str, int], optional): + A dictionary of parameters to configure the source node. Defaults to None. + + Returns: + OpenDSS object: + The OpenDSS object representing the source node. + """ + params = params or dict() + return _make_source_node(params, source_type) + + def add_PVSystem(self, + load_nodes: list[str], + params: dict[str, int] = None, + num_panels: int = 1): + """Adds a photovoltaic (PV) system to the specified load nodes. + + Adds PV system with num_panels to each of the listed load nodes. + Can be customized with parameters. + + Args: + load_nodes (list[str]): + A list of node names where the PV system will be connected. + params (dict[str, int], optional): + A dictionary of additional parameters for the PV system. Defaults to None. + num_panels (int, optional): + The number of PV panels in the system. Defaults to 1. + + Returns: + list[DSS objects]: + A list of OpenDSS objects representing the PV systems created. + """ + params = params or dict() + if not load_nodes: + raise ValueError("Need to enter load nodes to add PVSystem to") + + PV_nodes = [] + for load in load_nodes: + PV_nodes.append(_make_pv(load, params, num_panels, self.num_pv)) + self.num_pv += 1 + + return PV_nodes + + def add_generator(self, num: int = 1, gen_type: str = "small", params: dict[str, int] = None): + """Adds generator(s) to the system. + + Args: + num (int, optional): + The number of generator units to add. Defaults to 1. + gen_type (str, optional): + 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. + + Returns: + list[DSS objects]: + A list of OpenDSS objects representing the generators created. + """ + params = params or dict() + generators = [] + for _ in range(num): + generators.append(_make_generator(params, gen_type, count=self.num_generators)) + self.num_generators += 1 + + return generators + + def add_lines(self, + connections: list[tuple], + line_type: str = "lv", + params: dict[str, int] = None, + transformer: bool = True): + """Adds lines to the system. + + Adds electrical lines according to the given connections. + Users can specify the parameters of the lines or otherwise use given line type options. + + Args: + connections (list[tuple]): + A list of tuples defining the connections between nodes. + line_type (str, optional): + The type of line (one of "lv", "mv", "hv"). Defaults to "lv". + params (dict[str, int], optional): + A dictionary of parameters to configure the lines. Defaults to None. + transformer (bool, optional): + Whether to include a transformer in the connection. Defaults to True. + + Returns: + None + """ + params = params or dict() + for src, dst in connections: + _make_line(src, dst, line_type, self.num_lines, params, transformer) + self.num_lines += 1 + + def solve(self): + """Solves the OpenDSS circuit. + + Initializes "solve" mode in OpenDSS, which allows user to query results on the circuit. + + Returns: + None + """ + altdss.Solution.Solve() + + def results(self, queries: list[str], export_path=""): + """Gets simulation results based on specified queries. + + Allows the user to query for many results at once by providing a list of desired queries. + + Args: + queries (list[str]): + A list of queries to the circuit ("Voltages", "Losses", "TotalPower") + export_path (str, optional): + The file path to export results. If empty, results are not exported. + Defaults to "". + + Returns: + dict: + A dictionary containing the fetched simulation results. + """ + results = {} + for query in queries: + results[query] = _query_solution(query) + + if (export_path): + _export_results(results, export_path) + + return results + + def clear(self): + """Clears the OpenDSS circuit. + + Returns: + None + """ + altdss.ClearAll() + self.num_loads = 0 + self.num_lines = 0 + self.num_transformers = 0 diff --git a/pygridsim/defaults.py b/pygridsim/defaults.py index 7a67a33..eb807e9 100644 --- a/pygridsim/defaults.py +++ b/pygridsim/defaults.py @@ -2,6 +2,7 @@ Set any defaults (i.e. default source voltage, default node load etc.) """ from altdss import Connection + """ Overall Defaults, used for load, sources, lines, etc. https://www.anker.com/blogs/home-power-backup/electricity-usage-how-much-energy-does-an-average-house-use @@ -12,11 +13,11 @@ """ Load Nodes kW: around 30 kWH a day, divide by 24 hours -kVar is like around 0.2 or 0.1 of what kVar is +kVar is like around 0.2 or 0.1 of what kVar is """ HOUSE_KV = [.12, .24] HOUSE_KW = [1, 1.5] -HOUSE_KVAR = [0.5, 1] # unclear +HOUSE_KVAR = [0.5, 1] COMMERCIAL_KV = [.24, .48] COMMERCIAL_KW = [10, 50] @@ -30,23 +31,23 @@ Source Nodes (including other form of sources, like PVSystem) """ IMPEDANCE = 0.0001 -TURBINE_BASE_KV = [1,3] +TURBINE_BASE_KV = [1, 3] POWER_PLANT_KV = [10, 20] LV_SUBSTATION_BASE_KV = [0.2, 0.4] MV_SUBSTATION_BASE_KV = [6, 35] HV_SUBSTATION_BASE_KV = [66, 500] SHV_SUBSTATION_BASE_KV = [500, 1000] -SOLAR_PANEL_BASE_KV = [0.2, 0.4] # per solar panel +SOLAR_PANEL_BASE_KV = [0.2, 0.4] # per solar panel """ Generator default values (small, large, industrial) """ SMALL_GEN_KV = [0.2, 0.6] LARGE_GEN_KV = [1, 35] INDUSTRIAL_GEN_KV = [35, 100] -SMALL_GEN_KW = [2,5] -LARGE_GEN_KW = [5,10] -INDUSTRIAL_GEN_KW = [10,20] +SMALL_GEN_KW = [2, 5] +LARGE_GEN_KW = [5, 10] +INDUSTRIAL_GEN_KW = [10, 20] """ Units: KM @@ -73,4 +74,4 @@ VALID_SOURCE_PARAMS = ["kV", "phases", "frequency"] + IMPEDANCE_PARAMS VALID_LINE_TRANSFORMER_PARAMS = ["length", "XHL", "Conns"] VALID_PV_PARAMS = ["kV", "phases"] -VALID_GENERATOR_PARAMS = ["kV", "kW", "phases"] \ No newline at end of file +VALID_GENERATOR_PARAMS = ["kV", "kW", "phases"] diff --git a/pygridsim/enums.py b/pygridsim/enums.py index b6ed6d3..6498cf9 100644 --- a/pygridsim/enums.py +++ b/pygridsim/enums.py @@ -1,5 +1,6 @@ from enum import Enum + class SourceType(Enum): TURBINE = "turbine" POWER_PLANT = "powerplant" @@ -8,17 +9,20 @@ class SourceType(Enum): HV_SUBSTATION = "hvsub" SHV_SUBSTATION = "shvsub" + class LineType(Enum): LV_LINE = "lv" MV_LINE = "mv" HV_LINE = "hv" + class LoadType(Enum): HOUSE = "house" COMMERCIAL = "commercial" INDUSTRIAL = "industrial" + class GeneratorType(Enum): SMALL = "small" LARGE = "large" - INDUSTRIAL = "industrial" \ No newline at end of file + INDUSTRIAL = "industrial" diff --git a/pygridsim/lines.py b/pygridsim/lines.py index f7ac3a8..6fadf35 100644 --- a/pygridsim/lines.py +++ b/pygridsim/lines.py @@ -1,10 +1,11 @@ -from altdss import altdss -from altdss import Transformer -from pygridsim.configs import LINE_CONFIGURATIONS +from altdss import Transformer, altdss +from dss.enums import LineUnits + import pygridsim.defaults as defaults +from pygridsim.configs import LINE_CONFIGURATIONS from pygridsim.enums import LineType -from pygridsim.parameters import _get_param, _random_param, _check_valid_params, _get_enum_obj -from dss.enums import LineUnits +from pygridsim.parameters import _check_valid_params, _get_enum_obj, _get_param, _random_param + def _get_kv(node_name): if node_name == "source" and node_name in altdss.Vsource: @@ -16,12 +17,14 @@ def _get_kv(node_name): else: raise KeyError("Invalid src or dst name") -def _make_line(src, dst, line_type, count, params = {}, transformer = True): + +def _make_line(src, dst, line_type, count, params={}, transformer=True): _check_valid_params(params, defaults.VALID_LINE_TRANSFORMER_PARAMS) line_type_obj = _get_enum_obj(LineType, line_type) line = altdss.Line.new('line' + str(count)) line.Phases = defaults.PHASES - line.Length = _get_param(params, "length", _random_param(LINE_CONFIGURATIONS[line_type_obj]["length"])) + rand_length = _random_param(LINE_CONFIGURATIONS[line_type_obj]["length"]) + line.Length = _get_param(params, "length", rand_length) line.Bus1 = src line.Bus2 = dst line.Units = LineUnits.km @@ -36,9 +39,10 @@ def _make_line(src, dst, line_type, count, params = {}, transformer = True): transformer: Transformer = altdss.Transformer.new('transformer' + str(count)) transformer.Phases = defaults.PHASES transformer.Windings = defaults.NUM_WINDINGS - transformer.XHL = _get_param(params, "XHL", defaults.XHL) + transformer.XHL = _get_param(params, "XHL", defaults.XHL) transformer.Buses = [src, dst] - transformer.Conns = _get_param(params, "Conns", [defaults.PRIMARY_CONN, defaults.SECONDARY_CONN]) + transformer.Conns = _get_param(params, "Conns", + [defaults.PRIMARY_CONN, defaults.SECONDARY_CONN]) transformer.kVs = [_get_kv(src), _get_kv(dst)] diff --git a/pygridsim/parameters.py b/pygridsim/parameters.py index 0904af3..78200bb 100644 --- a/pygridsim/parameters.py +++ b/pygridsim/parameters.py @@ -1,13 +1,15 @@ """ Helper functions to parse the parameters used for loads and sources """ -from altdss import altdss -from altdss import Load, PVSystem, Generator -from pygridsim.enums import LoadType, SourceType, GeneratorType -from pygridsim.configs import LOAD_CONFIGURATIONS, SOURCE_CONFIGURATIONS, GENERATOR_CONFIGURATIONS -import pygridsim.defaults as defaults import random +from altdss import Generator, Load, PVSystem, altdss + +import pygridsim.defaults as defaults +from pygridsim.configs import GENERATOR_CONFIGURATIONS, LOAD_CONFIGURATIONS, SOURCE_CONFIGURATIONS +from pygridsim.enums import GeneratorType, LoadType, SourceType + + def _get_enum_obj(enum_class, enum_val): enum_obj = None enum_val_lower = enum_val.lower().replace(" ", "") @@ -19,6 +21,7 @@ def _get_enum_obj(enum_class, enum_val): return enum_obj + def _random_param(range): if type(range) is not list: return range @@ -26,12 +29,14 @@ def _random_param(range): [max, min] = range return random.random() * (max - min) + min + def _get_param(params, name, default): if name in params: return params[name] else: return default + def _check_valid_params(params, valid_params): # Invalid parameter handling for key in params: @@ -42,13 +47,14 @@ def _check_valid_params(params, valid_params): if key in ["kV", "BasekV"] and params[key] < 0: raise ValueError("KV cannot be less than 0") + def _make_load_node(load_params, load_type, count): _check_valid_params(load_params, defaults.VALID_LOAD_PARAMS) load_type_obj = _get_enum_obj(LoadType, load_type) - load : Load = altdss.Load.new('load' + str(count)) + load: Load = altdss.Load.new('load' + str(count)) load.Bus1 = 'load' + str(count) - load.Phases =_get_param(load_params, "phases", defaults.PHASES) + load.Phases = _get_param(load_params, "phases", defaults.PHASES) for attr in ["kV", "kW", "kvar"]: load_type_param = LOAD_CONFIGURATIONS[load_type_obj][attr] setattr(load, attr, _get_param(load_params, attr, _random_param(load_type_param))) @@ -56,6 +62,7 @@ def _make_load_node(load_params, load_type, count): load.Daily = 'default' return load + def _make_source_node(source_params, source_type): _check_valid_params(source_params, defaults.VALID_SOURCE_PARAMS) source_type_obj = _get_enum_obj(SourceType, source_type) @@ -72,20 +79,22 @@ def _make_source_node(source_params, source_type): return source + def _make_pv(load_node, params, num_panels, count): _check_valid_params(params, defaults.VALID_PV_PARAMS) - pv : PVSystem = altdss.PVSystem.new('pv' + str(count)) + pv: PVSystem = altdss.PVSystem.new('pv' + str(count)) pv.Bus1 = load_node pv.Phases = _get_param(params, "phases", defaults.PHASES) pv.kV = _get_param(params, "kV", _random_param(defaults.SOLAR_PANEL_BASE_KV) * num_panels) + def _make_generator(params, gen_type, count): _check_valid_params(params, defaults.VALID_GENERATOR_PARAMS) gen_type_obj = _get_enum_obj(GeneratorType, gen_type) - generator : Generator = altdss.Generator.new('generator' + str(count)) + generator: Generator = altdss.Generator.new('generator' + str(count)) generator.Bus1 = 'generator' + str(count) generator.Phases = _get_param(params, "phases", defaults.PHASES) for attr in ["kV", "kW"]: gen_type_param = GENERATOR_CONFIGURATIONS[gen_type_obj][attr] - setattr(generator, attr, _get_param(params, attr, _random_param(gen_type_param))) \ No newline at end of file + setattr(generator, attr, _get_param(params, attr, _random_param(gen_type_param))) diff --git a/pygridsim/results.py b/pygridsim/results.py index e9cb3da..a81d105 100644 --- a/pygridsim/results.py +++ b/pygridsim/results.py @@ -2,9 +2,11 @@ Defines the set of allowed queries (i.e. baseKV at every node) and provides helpers for the solve/results function. """ -from altdss import altdss import json +from altdss import altdss + + def _query_solution(query): match query: case "Voltages": @@ -23,6 +25,7 @@ def _query_solution(query): case _: return "Invalid" + def _export_results(results, path): with open(path, "w") as json_file: - json.dump(results, json_file, indent=4) \ No newline at end of file + json.dump(results, json_file, indent=4) diff --git a/setup.py b/setup.py index ae09880..30ac7f1 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ """The setup script.""" -from setuptools import setup, find_packages +from setuptools import find_packages, setup with open('README.md', encoding='utf-8') as readme_file: readme = readme_file.read() @@ -31,10 +31,14 @@ 'watchdog>=0.8.3', # docs - 'm2r>=0.2.0,<0.3', - 'Sphinx>=1.7.1,<3', + 'm2r2<4', + 'docutils>=0.12,<1', + 'nbsphinx>=0.5.0,<1', + 'sphinx_toolbox>=2.5,<4', + 'Sphinx>=3,<7', + 'markupsafe<3', + 'ipython>=6.5,<12', 'sphinx_rtd_theme>=0.2.4,<0.5', - 'autodocsumm>=0.1.10', # style check 'flake8>=3.7.7', @@ -81,7 +85,7 @@ keywords='pygridsim pygridsim PyGridSim', name='pygridsim', packages=find_packages(include=['pygridsim', 'pygridsim.*']), - python_requires='>=3.8, <3.13', + python_requires='>=3.10, <3.13', setup_requires=setup_requires, test_suite='tests', tests_require=tests_require, diff --git a/tests/test_circuit.py b/tests/test_circuit.py index fa27276..8460d5f 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -1,9 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from pygridsim.core import PyGridSim -from pygridsim.enums import LineType, LoadType, SourceType, GeneratorType import unittest +from pygridsim.core import PyGridSim +from pygridsim.enums import GeneratorType, LineType, LoadType, SourceType + """Tests for `pygridsim` package.""" @@ -20,7 +21,6 @@ def setUp(self): def tearDown(self): """Tear down test fixtures, if any.""" - #altdss.ClearAll() def test_000_basic(self): circuit = PyGridSim() @@ -39,13 +39,13 @@ def test_001_one_source_one_load(self): circuit.solve() print(circuit.results(["Voltages", "Losses"])) circuit.clear() - + def test_002_one_source_one_load_no_transformer(self): # doesn't throw error, but should have stranger output VMag circuit = PyGridSim() circuit.update_source(source_type="turbine") circuit.add_load_nodes(num=1, load_type="house") - circuit.add_lines([("source", "load0")],"MV", transformer=False) + circuit.add_lines([("source", "load0")], "MV", transformer=False) circuit.solve() print(circuit.results(["Voltages", "Losses"])) circuit.clear() @@ -61,7 +61,6 @@ def test_003_one_source_one_load_exhaustive(self): circuit.solve() circuit.clear() - def test_004_one_source_multi_load(self): circuit = PyGridSim() circuit.update_source(source_type="turbine") @@ -70,7 +69,7 @@ def test_004_one_source_multi_load(self): circuit.solve() print(circuit.results(["Voltages"])) circuit.clear() - + def test_005_bad_query(self): circuit = PyGridSim() circuit.update_source() @@ -87,7 +86,7 @@ def test_006_update_multiple_source(self): circuit.add_lines([("source", "load0")], "HV") circuit.solve() print(circuit.results(["Voltages"])) - + def test_007_export(self): circuit = PyGridSim() circuit.update_source() @@ -95,7 +94,7 @@ def test_007_export(self): circuit.add_lines([("source", "load0")]) circuit.solve() print(circuit.results(["Voltages", "Losses"], export_path="sim.json")) - + def test_008_PVsystem(self): circuit = PyGridSim() circuit.update_source() @@ -113,14 +112,14 @@ def test_009_generator(self): circuit.add_lines([("source", "load0"), ("generator0", "load0")]) circuit.solve() print(circuit.results(["Voltages", "Losses"])) - + def test_010_many_sources(self): circuit = PyGridSim() circuit.update_source(source_type="powerplant") circuit.add_load_nodes(num=3) circuit.add_PVSystem(load_nodes=["load1", "load2"], num_panels=10) circuit.add_generator(num=3, gen_type="small") - circuit.update_source(source_type="turbine") # change to a turbine source midway + circuit.update_source(source_type="turbine") circuit.add_generator(num=4, gen_type="large") circuit.add_lines([("source", "load0"), ("generator0", "load0"), ("generator5", "source")]) circuit.solve() @@ -139,7 +138,7 @@ def test_011_configs(self): # don't want loadtype input, just string with self.assertRaises(Exception): circuit.add_load_nodes(num=2, load_type=LoadType.HOUSE) - + # LINE CONFIG # works, because not case sensitive circuit.add_lines([("source", "load0")], line_type="HV") @@ -166,22 +165,20 @@ def test_011_configs(self): class TestCustomizedCircuit(unittest.TestCase): """ - Test with exact parameters entered. won't be the typical client use case, but should check that these matche exact values + Test with exact parameters entered. """ def setUp(self): """Set up test fixtures, if any.""" print("\nTest", self._testMethodName) - def tearDown(self): """Tear down test fixtures, if any.""" - pass def test_100_one_source_one_load(self): circuit = PyGridSim() circuit.update_source(params={"kV": 100, "R0": 0.1, "R1": 0.2, "X0": 0.3, "X1": 0.4}) - circuit.add_load_nodes(num=1, params={"kV": 10, "kW": 20, "kvar":1}) + circuit.add_load_nodes(num=1, params={"kV": 10, "kW": 20, "kvar": 1}) circuit.add_lines([("source", "load0")], params={"length": 20}) circuit.solve() print(circuit.results(["Voltages", "Losses"])) @@ -189,16 +186,18 @@ def test_100_one_source_one_load(self): def test_100_one_source_multi_load(self): """ - Creates 10 loads, some of which are connected to source. all loads and lines here have the same params + Creates 10 loads, some of which are connected to source. + All loads and lines here have the same params. """ circuit = PyGridSim() circuit.update_source(params={"kV": 100}) - circuit.add_load_nodes(num=10, params={"kV": 10, "kW": 20, "kvar":1}) - circuit.add_lines([("source", "load0"), ("source", "load4"), ("source", "load6")], params={"length": 20}) + circuit.add_load_nodes(num=10, params={"kV": 10, "kW": 20, "kvar": 1}) + circuit.add_lines([("source", "load0"), ("source", "load4"), ("source", "load6")], + params={"length": 20}) circuit.solve() print(circuit.results(["Voltages", "Losses"])) circuit.clear() - + def test_101_bad_parameter(self): """ Should error with a bad parameter and tell the user which parameter is bad @@ -209,7 +208,7 @@ def test_101_bad_parameter(self): with self.assertRaises(KeyError): circuit.add_load_nodes(num=4, params={"badParam": 100}) # add load nodes so we can test pv system erroring - circuit.add_load_nodes(num=2, params={"kV": 10, "kW": 20, "kvar":1}) + circuit.add_load_nodes(num=2, params={"kV": 10, "kW": 20, "kvar": 1}) with self.assertRaises(KeyError): circuit.add_generator(num=4, params={"badParam": 100}) with self.assertRaises(KeyError): @@ -220,14 +219,14 @@ def test_102_negative_inputs(self): Should error with negative kv or negative length """ circuit = PyGridSim() - + with self.assertRaises(Exception): # openDSS has its own exception for this case circuit.add_load_nodes(params={"kV": -1}) - + with self.assertRaises(ValueError): circuit.update_source(params={"kV": -1}) - + # properly add load and source, then create invalid line with self.assertRaises(ValueError): circuit.add_lines([("source", "load0")], params={"length": -100}) @@ -239,22 +238,8 @@ def test_103_invalid_nodes_in_line(self): with self.assertRaises(KeyError): # only has source, load0 for now but tries to add another one circuit.add_lines([("source", "load5")]) - + def test_104_non_int_parameters(self): circuit = PyGridSim() with self.assertRaises(TypeError): circuit.add_load_nodes(params={"kV": "stringInput"}) - - -class TestLargeCircuit(unittest.TestCase): - """ - Test very large circuit (i.e. to the size of a neighborhood) - """ - def setUp(self): - """Set up test fixtures, if any.""" - print("\nTest", self._testMethodName) - - - def tearDown(self): - """Tear down test fixtures, if any.""" - pass