diff --git a/.github/workflows/github_test_action.yml b/.github/workflows/github_test_action.yml index f3062d5..8e8cfe6 100644 --- a/.github/workflows/github_test_action.yml +++ b/.github/workflows/github_test_action.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12'] # Reminder: when removing support of an old python version here, then don't forget to remove # it also in pyproject.toml 'requires-python' steps: @@ -113,20 +113,14 @@ jobs: PYTHONPATH=$PYTHONPATH:$GITHUB_WORKSPACE python -m pytest -W error --nbmake -n=auto --nbmake-timeout=900 "./tutorials" docs_check: + needs: build + name: Sphinx docs check runs-on: ubuntu-latest - strategy: - matrix: - python-version: [ '3.9' ] steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Check docs for Python ${{ matrix.python-version }} - uses: e2nIEE/sphinx-action@master + - name: Check sphinx build + uses: ammaraskar/sphinx-action@7.4.7 with: - pre-build-command: "python -m pip install --upgrade pip; - python -m pip install .[docs];" + pre-build-command: "python -m pip install uv && uv pip install .[docs] --system --link-mode=copy" build-command: "sphinx-build -b html . _build -W" docs-folder: "doc/" diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml index fd1b0ea..94afa22 100644 --- a/.github/workflows/test_release.yml +++ b/.github/workflows/test_release.yml @@ -17,7 +17,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12'] os: [ ubuntu-latest, windows-latest ] steps: @@ -40,14 +40,14 @@ jobs: run: | if ( '${{ matrix.python-version }}' -eq '3.9' ) { python -m pip install pypower } if ( '${{ matrix.python-version }}' -ne '3.9' ) { python -m pip install numba } - if ( '${{ matrix.python-version }}' -eq '3.8' -or '${{ matrix.python-version }}' -eq '3.10' ) { python -m pip install lightsim2grid } + if ( '${{ matrix.python-version }}' -eq '3.10' ) { python -m pip install lightsim2grid } - name: Install specific dependencies (Ubuntu) if: matrix.os == 'ubuntu-latest' run: | if ${{ matrix.python-version == '3.9' }}; then python -m pip install pypower; fi if ${{ matrix.python-version != '3.9' }}; then python -m pip install numba; fi - if ${{ matrix.python-version == '3.8' || matrix.python-version == '3.10' }}; then python -m pip install lightsim2grid; fi + if ${{ matrix.python-version == '3.10' }}; then python -m pip install lightsim2grid; fi - name: List all installed packages run: | diff --git a/.github/workflows/upload_release.yml b/.github/workflows/upload_release.yml index ecf0592..7606059 100644 --- a/.github/workflows/upload_release.yml +++ b/.github/workflows/upload_release.yml @@ -6,9 +6,16 @@ name: upload # Controls when the action will run. on: # Allows you to run this workflow manually from the Actions tab - push: - branches: - - master + workflow_dispatch: + inputs: + upload_server: + description: 'upload server' + required: true + default: 'testpypi' + type: choice + options: + - 'testpypi' + - 'pypi' # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -19,10 +26,12 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v4 + # Sets up python3 - uses: actions/setup-python@v5 with: python-version: '3.10' + # Installs and upgrades pip, installs other dependencies and installs the package from setup.py - name: Install dependencies run: | @@ -30,6 +39,19 @@ jobs: python3 -m pip install --upgrade pip # Install twine python3 -m pip install build setuptools wheel twine + + # Upload to TestPyPI + - name: Build and Upload to TestPyPI + if: ${{ inputs.upload_server == 'testpypi' }} + run: | + python3 -m build + python3 -m twine check dist/* --strict + python3 -m twine upload dist/* + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TESTPYPI }} + TWINE_REPOSITORY: testpypi + # Upload to PyPI - name: Build and Upload to PyPI run: | @@ -40,10 +62,15 @@ jobs: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI }} TWINE_REPOSITORY: pypi + + # Wait some time - name: Sleep for 300s to make release available + if: ${{ inputs.upload_server == 'pypi' }} uses: juliangruber/sleep-action@v2 with: time: 300s + + # Run an installation for testing - name: Install simbench from PyPI run: | python3 -m pip install simbench diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6651259..acd89b5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,12 +1,21 @@ Change Log ============= +[1.6.0] - 2024-04-09 +---------------------- +- [CHANGED] support pandapower geojson, released with pandapower version 3.0.0 +- [ADDED] support pandapower parameters step_dependency_table and tap_dependency_table, released with pandapower version 3.0.0 +- [CHANGED] drop python 3.8 support +- [FIXED] fix ValueError raise according to GitHub issue #56 +- [CHANGED] rename parameter csv2pp parameter `no_generic_coord` by `fill_bus_geo_by_generic_data` + [1.5.3] - 2024-04-23 ---------------------- - [FIXED] Bringing together develop and master with all changes of the release process - [ADDED] generalization and test for auxiliary function :code:`to_numeric_ignored_errors()` [1.5.2] - 2024-04-09 +---------------------- - [CHANGED] readded copyright notice to setup.py and updated date - [CHANGED] added prune to MANIFEST.in to exclude doc and tutorials from wheel builds - [CHANGED] removed gitlab ci files from MANIFEST.in to keep them out of wheel builds diff --git a/MANIFEST.in b/MANIFEST.in index f6ab14c..d91d0d0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,5 +4,6 @@ global-include *.csv global-include *.json global-include *.txt +prune .git* prune doc* prune tutorials* diff --git a/doc/about/installation.rst b/doc/about/installation.rst index b864b1e..db2fef1 100644 --- a/doc/about/installation.rst +++ b/doc/about/installation.rst @@ -7,7 +7,7 @@ Installation Guide Installing Python ---------------------------- -simbench is tested with Python 3.5, 3.6 and 3.7. We recommend the Anaconda Distribution, which provides a Python distribution that already includes a lot of modules for scientific computing that are needed. Of course it is also possible to use simbench with other distributions besides Anaconda. Anyway, it is important that the following package is included: +simbench is tested with multiple up-to-date Python versions. We recommend the Miniconda Distribution, which provides a Python distribution that already includes a lot of modules for scientific computing that are needed. Of course it is also possible to use simbench with other distributions besides Anaconda. Anyway, it is important that the following package is included: - pandapower @@ -20,23 +20,15 @@ The easiest way to install simbench is through pip: 1. Open a command prompt (e.g. start–>cmd on windows systems) -2. If you already work with the pandapower development version from GitHub, but did not yet register it to pip: - - a. Navigate your command prompt into your pandapower folder (with the command cd ). - - b. Register pandapower to pip, to not install pandapower a second time, via typing: - - :code:`pip install -e .` - -3. Install simbench by running: +2. Install simbench by running: :code:`pip install simbench` -Installing simbench without pip +Installing simbench without internet connection -------------------------------------------------------- -If you don't have internet access on your system or don't want to use pip for some other reason, simbench can also be installed without using pip: +If you don't have internet access on your system and already downloaded the repository (step 1), simbench can also be installed without from local files: 1. Download and unzip the current simbench distribution from PyPi under "Download files". @@ -46,7 +38,9 @@ If you don't have internet access on your system or don't want to use pip for so 3. Install simbench by running : - :code:`python setup.py install` + :code:`pip install -e .` + + This registers your local pandapower installation with pip, the option -e ensures the edits in the files have a direct impact on the pandapower installation. Development Version @@ -66,7 +60,7 @@ To install the latest version of simbench from github, simply follow these steps :code:`pip install -e .` - This registers your local simbench installation with pip. + This registers your local pandapower installation with pip, the option -e ensures the edits in the files have a direct impact on the pandapower installation. Test your installation diff --git a/doc/index.rst b/doc/index.rst index 604dd49..94beec8 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -31,7 +31,7 @@ More information about pandapower can be found on `www.pandapower.org =2.12.1" + "pandapower>=3.0.0" ] keywords = [ "benchmark grid", "power system", "network", "grid planning", "grid operation", "grid generation methodology", "comparability", "reproducibility", "electricity", "energy", "engineering", "simulation", "simbench", "time series", "future scenarios" @@ -72,4 +71,4 @@ find = {} addopts = ["--strict-markers"] markers = [ "slow: marks tests as slow (deselect with '-m \"not slow\"'), e.g. in run_fast_tests" -] \ No newline at end of file +] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 1fb6481..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# pip only: -pandapower>=2.12.1 \ No newline at end of file diff --git a/simbench/converter/auxiliary.py b/simbench/converter/auxiliary.py index c6d2fef..d2115a7 100644 --- a/simbench/converter/auxiliary.py +++ b/simbench/converter/auxiliary.py @@ -33,6 +33,9 @@ def to_numeric_ignored_errors(data, **kwargs): """Wrapper function for pandas.to_numeric(). Needed to emulate previous behavior with deprecated errors="ignore". + The issue covered by this function is discussed at https://github.com/pandas-dev/pandas/issues/54467. + `df2 = df1.apply(pd.to_numeric, errors='coerce')` and `df2=df2.fillna(df1)` is an alternative way. + Parameters ---------- data : Series|DataFrame diff --git a/simbench/converter/csv_data_manipulation.py b/simbench/converter/csv_data_manipulation.py index 148c24e..88b8e72 100644 --- a/simbench/converter/csv_data_manipulation.py +++ b/simbench/converter/csv_data_manipulation.py @@ -154,10 +154,12 @@ def _add_phys_type_and_vm_va_setpoints_to_element_tables(csv_data): def _extend_coordinates_to_node_shape(csv_data): """ Extends the Coordinates table to the shape of Nodes to enable copying simply to bus_geodata. """ - bus_geodata = pd.DataFrame([], index=csv_data["Node"].index, columns=["x", "y"]) + geo = pd.Series(None, index=csv_data["Node"].index, name="geo", dtype=object) with_coord = ~csv_data["Node"]["coordID"].isnull() idx_in_coordID = idx_in_2nd_array(csv_data["Node"]["coordID"].loc[with_coord].values, csv_data["Coordinates"]["id"].values) - bus_geodata.loc[with_coord, ["x", "y"]] = csv_data["Coordinates"].loc[idx_in_coordID, - ["x", "y"]].values - csv_data["Coordinates"] = bus_geodata + geo.loc[with_coord] = [f'{{"type":"Point", "coordinates":[{x}, {y}]}}' for x, y in zip( + csv_data["Coordinates"].loc[idx_in_coordID, "x"].values, + csv_data["Coordinates"].loc[idx_in_coordID, "y"].values, + )] + csv_data["Node"]["geo"] = geo diff --git a/simbench/converter/csv_pp_converter.py b/simbench/converter/csv_pp_converter.py index e0c77ab..89c7f7c 100644 --- a/simbench/converter/csv_pp_converter.py +++ b/simbench/converter/csv_pp_converter.py @@ -3,7 +3,10 @@ # contributors (see AUTHORS file for details). All rights reserved. # This is the csv_pp_converter for the simbench project. -# pandapower 2.0.1 <-> simbench format (reasled status from 25.04.2019) + +# OPTIONAL IMPROVEMENTS for compatibility with future pandapower changes: constructing the +# dataframes net[element_table] by using the create_buses(), create_lines(), ... functions +# which where fast enough or not available at the time SimBench was developed. import os import pandas as pd @@ -28,7 +31,7 @@ from simbench.converter.pp_net_manipulation import _extend_pandapower_net_columns, \ _add_dspf_calc_type_and_phys_type_columns, _add_vm_va_setpoints_to_buses, \ _prepare_res_bus_table, replace_branch_switches, create_branch_switches, _add_coordID, \ - _set_vm_setpoint_to_trafos + _set_vm_setpoint_to_trafos, _set_dependency_table_parameters from simbench.converter.csv_data_manipulation import * from simbench.converter.csv_data_manipulation import _extend_coordinates_to_node_shape, \ _sort_switch_nodes_and_prepare_element_and_et, \ @@ -42,7 +45,7 @@ __author__ = 'smeinecke' -def csv2pp(path, sep=';', add_folder_name=None, nrows=None, no_generic_coord=False): +def csv2pp(path, sep=';', add_folder_name=None, nrows=None, fill_bus_geo_by_generic_data=True): """ Conversion function from simbench csv format to pandapower. @@ -58,8 +61,9 @@ def csv2pp(path, sep=';', add_folder_name=None, nrows=None, no_generic_coord=Fal **nrows** (int, None) - number of rows to be read for profiles. If None, all rows will be read. - **no_generic_coord** (bool, False) - if True, no generic coordinates are created in case of - missing geodata. + **fill_bus_geo_by_generic_data** (bool, False) - if False, no generic coordinates are + created in case of missing geo data. If True, generic coordinates are create when at least + one bus misses geo data. OUTPUT: **net** (pandapowerNet) - the created pandapower net from csv files data @@ -79,17 +83,12 @@ def csv2pp(path, sep=';', add_folder_name=None, nrows=None, no_generic_coord=Fal csv_data = read_csv_data(path, sep, nrows=nrows) # run net creation - net = csv_data2pp(csv_data) - - # ensure geodata - if not no_generic_coord and any(pd.isnull(net.bus_geodata.x) | pd.isnull(net.bus_geodata.y)): - del net.bus_geodata - create_generic_coordinates(net) + net = csv_data2pp(csv_data, fill_bus_geo_by_generic_data=fill_bus_geo_by_generic_data) return net -def csv_data2pp(csv_data): +def csv_data2pp(csv_data, fill_bus_geo_by_generic_data=False): """ Internal functionality of csv2pp, but with a given dict of csv_data as input instead of csv files. """ # --- initializations @@ -127,9 +126,21 @@ def csv_data2pp(csv_data): create_branch_switches(net) net.bus.loc[net.bus.type == "multi_auxiliary", "type"] = "auxiliary" _set_vm_setpoint_to_trafos(net, csv_data) + _set_dependency_table_parameters(net) _csv_types_to_pp2(net) ensure_bus_index_columns_as_int(net) + # --- ensure geodata + if not fill_bus_geo_by_generic_data: + if n_missing_geo_data := sum(pd.isnull(net.bus.geo) | (net.bus.geo == "")): + if n_missing_geo_data != len(net.bus): + logger.info(f"Due to {fill_bus_geo_by_generic_data=}, new generic geo data are " + f"created and overwrite existing bus geo data (" + f"{len(net.bus)-n_missing_geo_data} buses had geo data, " + f"{n_missing_geo_data} buses missed geo data).") + net.bus["geo"] = None + create_generic_coordinates(net) + return net @@ -222,23 +233,23 @@ def pp2csv_data(net1, export_pp_std_types=False, drop_inactive_elements=True, csv_data = _init_csv_tables(['elements', 'profiles', 'types', 'res_elements']) aux_nodes_are_reserved = reserved_aux_node_names is not None + if ("step_dependency_table" in net1.trafo.columns and net1.trafo.step_dependency_table.any()) \ + or \ + ("step_dependency_table" in net1.shunt.columns and net1.shunt.step_dependency_table.any()): + logger.warning("'step_dependency_table' is not supported in SimBench's csv data format.") + # --- net data preparation for converting _extend_pandapower_net_columns(net) if drop_inactive_elements: # attention: trafo3ws are not considered in current version of drop_inactive_elements() pp.drop_inactive_elements(net, respect_switches=False) - check_results = pp.deviation_from_std_type(net) - if check_results: + dev_from_std = pp.deviation_from_std_type(net) + if dev_from_std: logger.warning("There are deviations from standard types in elements: " + - str(["%s" % elm for elm in check_results.keys()]) + ". Only the standard " + + str(["%s" % elm for elm in dev_from_std.keys()]) + ". Only the standard " + "type values are converted to csv.") convert_parallel_branches(net) - if net.bus.shape[0] and not net.bus_geodata.shape[0] or ( - net.bus_geodata.shape[0] != net.bus.shape[0]): - logger.info("Since there are no or incomplete bus_geodata, generic geodata are assumed.") - net.bus_geodata = net.bus_geodata.iloc[0:0] - create_generic_coordinates(net) - merge_busbar_coordinates(net) + merge_busbar_coordinates(net, True) move_slack_gens_to_ext_grid(net) scaling_is_not_1 = [] @@ -319,7 +330,8 @@ def _log_nan_col(csv_data, tablename, col): def _is_pp_type(data): - return "name" in data.keys() # used instead of isinstance(data, pp.auxiliary.pandapowerNet) + return isinstance(data, pp.auxiliary.pandapowerNet) + # return bool("name" in data.keys()) def convert_node_type(data): @@ -679,7 +691,7 @@ def _rename_and_split_input_tables(data): split_ppelm_into_type_and_elm = ["dcline"] if _is_pp_type(data) else [] input_elm_col = "pp" if _is_pp_type(data) else "csv" output_elm_col = "csv" if _is_pp_type(data) else "pp" - corr_df = _csv_table_pp_dataframe_correspondings(pd.DataFrame) + corr_df = _csv_table_pp_dataframe_correspondings(pd.DataFrame, not _is_pp_type(data)) corr_df["comb_str"] = corr_df["csv"] + "*" + corr_df["pp"] # all elements, which need to be converted to multiple output element tables, (dupl) need to be @@ -726,7 +738,7 @@ def _get_split_gen_val(element): def _rename_and_multiply_columns(data): """ Renames the columns of all dataframes as needed in output data. """ - to_rename_and_multiply_tuples = _get_parameters_to_rename_and_multiply() + to_rename_and_multiply_tuples = _get_parameters_to_rename_and_multiply(True) for corr_str, tuples in to_rename_and_multiply_tuples.items(): # --- remove "type" from data if "std_type" exists too if "std_type" in data[corr_str].columns and "type" in data[corr_str].columns and \ @@ -756,7 +768,7 @@ def _rename_and_multiply_columns(data): data[corr_str].loc[:, col] *= factors -def _get_parameters_to_rename_and_multiply(): +def _get_parameters_to_rename_and_multiply(drop_bus_geodata): """ Returns a dict of tuples and a dict of dataframes where csv column names are assigned to pandapower columns names which differ. """ # --- create dummy_net to get pp columns @@ -770,8 +782,8 @@ def _get_parameters_to_rename_and_multiply(): dummy_net[elm]["va_degree"] = np.nan # --- get corresponding tables and dataframes - corr_strings = _csv_table_pp_dataframe_correspondings(str) - csv_tablenames_, pp_dfnames = _csv_table_pp_dataframe_correspondings(list) + corr_strings = _csv_table_pp_dataframe_correspondings(str, drop_bus_geodata) + csv_tablenames_, pp_dfnames = _csv_table_pp_dataframe_correspondings(list, drop_bus_geodata) # --- initialize tuples_dict tuples_dict = dict.fromkeys(corr_strings, [("id", "name", None)]) @@ -803,7 +815,7 @@ def _replace_name_index(data): indices. This function replaces the assignment of the input data. """ node_names = {"node", "nodeA", "nodeB", "nodeHV", "nodeMV", "nodeLV"} bus_names = {"bus", "from_bus", "to_bus", "hv_bus", "mv_bus", "lv_bus"} - corr_strings = _csv_table_pp_dataframe_correspondings(str) + corr_strings = _csv_table_pp_dataframe_correspondings(str, True) corr_strings.remove("Measurement*measurement") # already done in convert_measurement() if _is_pp_type(data): @@ -843,8 +855,9 @@ def _copy_data(input_data, output_data): """ Copies the data from output_data[corr_strings] into input_data[element_table]. This function handles that some corr_strings are not in output_data.keys() and copies all columns which exists in both, output_data[corr_strings] and input_data[element_table]. """ - corr_strings = _csv_table_pp_dataframe_correspondings(str) - output_names = _csv_table_pp_dataframe_correspondings(list)[int(_is_pp_type(output_data))] + out_is_pp = _is_pp_type(output_data) + corr_strings = _csv_table_pp_dataframe_correspondings(str, out_is_pp) + output_names = _csv_table_pp_dataframe_correspondings(list, out_is_pp)[int(out_is_pp)] for corr_str, output_name in zip(corr_strings, output_names): if corr_str in input_data.keys() and input_data[corr_str].shape[0]: @@ -869,7 +882,7 @@ def _copy_data(input_data, output_data): output_data[output_name] = pd.concat([output_data[output_name], input_data[ corr_str][cols_to_copy]], ignore_index=True).reindex_axis(output_data[ output_name].columns, axis=1) - if "std_types" in corr_str and _is_pp_type(output_data): + if "std_types" in corr_str and out_is_pp: output_data[output_name].index = input_data[corr_str]["std_type"] _inscribe_fix_values(output_data, output_name) diff --git a/simbench/converter/format_information.py b/simbench/converter/format_information.py index 3104140..c129cdb 100644 --- a/simbench/converter/format_information.py +++ b/simbench/converter/format_information.py @@ -52,7 +52,7 @@ def csv_tablenames(which): return csv_tablenames -def _csv_table_pp_dataframe_correspondings(type_): +def _csv_table_pp_dataframe_correspondings(type_, drop_bus_geodata): csv_tablenames_ = csv_tablenames(['elements', 'types', 'res_elements']) # corresponding pandapower dataframe names pp_dfnames = ['ext_grid', 'line', 'load', 'shunt', 'bus', 'measurement', 'gen', 'sgen', @@ -63,6 +63,9 @@ def _csv_table_pp_dataframe_correspondings(type_): csv_tablenames_ += ['ExternalNet', 'ExternalNet', 'PowerPlant', 'PowerPlant', 'RES', 'RES', 'ExternalNet', 'ExternalNet', 'Line'] pp_dfnames += ['gen', 'sgen', 'ext_grid', 'sgen', 'ext_grid', 'gen', 'ward', 'xward', 'dcline'] + if drop_bus_geodata: + csv_tablenames_.remove("Coordinates") + pp_dfnames.remove("bus_geodata") assert len(csv_tablenames_) == len(pp_dfnames) if type_ is list: return csv_tablenames_, pp_dfnames diff --git a/simbench/converter/pp_net_manipulation.py b/simbench/converter/pp_net_manipulation.py index 31a4ff9..3489f57 100644 --- a/simbench/converter/pp_net_manipulation.py +++ b/simbench/converter/pp_net_manipulation.py @@ -2,6 +2,7 @@ # Institute for Energy Economics and Energy System Technology (IEE) Kassel and individual # contributors (see AUTHORS file for details). All rights reserved. +from io import StringIO import numpy as np import pandas as pd import pandapower as pp @@ -181,8 +182,20 @@ def convert_parallel_branches(net, multiple_entries=True, elm_to_convert=["line" net["switch"] = net["switch"].drop(switch_dupl) -def merge_busbar_coordinates(net): +def convert_geojson_to_bus_geodata_xy(net): + def notnone(val): + return isinstance(val, str) and bool(len(val)) + net.bus_geodata = pd.DataFrame(np.nan, index=net.bus.index, columns=["x", "y"]) + idxs = net.bus.index[net.bus.geo.apply(notnone)] + if len(idxs): + net.bus_geodata.loc[idxs, ["x", "y"]] = np.r_[[pd.read_json(StringIO(net.bus.geo.at[ + i])).coordinates.values for i in idxs]] + + +def merge_busbar_coordinates(net, on_bus_geodata): """ merges x and y coordinates of busbar node connected via bus-bus switches """ + if on_bus_geodata: + convert_geojson_to_bus_geodata_xy(net) bb_nodes_set = set(net.bus.index[net.bus.type == "b"]) bb_nodes = sorted(bb_nodes_set) all_connected_buses = set() @@ -191,8 +204,11 @@ def merge_busbar_coordinates(net): continue connected_nodes = pp.get_connected_buses(net, bb_node, consider=("t", "s")) if len(connected_nodes): - net.bus_geodata.loc[list(connected_nodes), "x"] = net.bus_geodata.x.at[bb_node] - net.bus_geodata.loc[list(connected_nodes), "y"] = net.bus_geodata.y.at[bb_node] + if on_bus_geodata: + net.bus_geodata.loc[list(connected_nodes), "x"] = net.bus_geodata.x.at[bb_node] + net.bus_geodata.loc[list(connected_nodes), "y"] = net.bus_geodata.y.at[bb_node] + else: + net.bus.loc[list(connected_nodes), "geo"] = net.bus.at[bb_node, "geo"] all_connected_buses |= connected_nodes @@ -392,6 +408,13 @@ def _set_vm_setpoint_to_trafos(net, csv_data): idx_node].values +def _set_dependency_table_parameters(net): + for et in ["trafo", "trafo3w", "shunt"]: + param = "step_dependency_table" if et == "shunt" else "tap_dependency_table" + if param in net[et].columns: + net[et][param] = False + net[et][param] = net[et][param].astype(bool) + def _prepare_res_bus_table(net): """ Adds columns to be converted to csv_data. """ if net.res_bus.shape[0]: @@ -423,11 +446,12 @@ def replace_branch_switches(net, reserved_aux_node_names=None): else: # if replace_branch_switches() is called out of pp2csv_data(), this else statement is given subnets = net.bus.zone[idx_bus].values - geodata = net.bus_geodata.loc[idx_bus, ["x", "y"]].values if net["bus_geodata"].shape[0] else \ - np.empty((len(idx_bus), 2)) aux_buses = pp.create_buses( net, n_branch_switches, net.bus.vn_kv[idx_bus].values, name=names.values, type="auxiliary", - geodata=geodata, zone=subnets) + zone=subnets) + if "bus_geodata" in net.keys() and net["bus_geodata"].shape[0]: + net.bus_geodata = pd.concat([net.bus_geodata, pd.DataFrame(net.bus_geodata.loc[ + idx_bus, ["x", "y"]].values, index=aux_buses, columns=["x", "y"])]) for col in ["min_vm_pu", "max_vm_pu", "substation", "voltLvl"]: if col in net.bus.columns: net.bus.loc[aux_buses, col] = net.bus[col][idx_bus].values @@ -509,9 +533,6 @@ def create_branch_switches(net): net.bus = net.bus.drop(aux_bus_df["aux_buses"]) idx_in_res_bus = aux_bus_df["aux_buses"][aux_bus_df["aux_buses"].isin(net.res_bus.index)] net.res_bus = net.res_bus.drop(idx_in_res_bus) - idx_in_bus_geodata = aux_bus_df["aux_buses"][aux_bus_df["aux_buses"].isin( - net.bus_geodata.index)] - net.bus_geodata = net.bus_geodata.drop(idx_in_bus_geodata) def _add_coordID(net, highest_existing_coordinate_number): diff --git a/simbench/networks/profiles.py b/simbench/networks/profiles.py index 61b2239..82a1e63 100644 --- a/simbench/networks/profiles.py +++ b/simbench/networks/profiles.py @@ -233,7 +233,7 @@ def get_absolute_profiles_from_relative_profiles( missing = list(applied_profiles[~applied_profiles.isin(relative_profiles.columns)]) if len(missing): raise ValueError("These profiles are set to be applied but are missing in the profiles " - "data: " + str(missings)) + "data: " + str(missing)) isna = applied_profiles.isnull() is_not_na_pos = np.arange(len(isna), dtype=int)[~isna.values] relative_profiles_vals[:, is_not_na_pos] = \ diff --git a/simbench/test/converter/test_csv_pp_converter.py b/simbench/test/converter/test_csv_pp_converter.py index 651e8fb..29a2f11 100644 --- a/simbench/test/converter/test_csv_pp_converter.py +++ b/simbench/test/converter/test_csv_pp_converter.py @@ -155,7 +155,7 @@ def test_convert_parallel_branches(): def test_test_network(): - net = csv2pp(test_network_path, no_generic_coord=True) + net = csv2pp(test_network_path, fill_bus_geo_by_generic_data=False) # test min/max ratio for elm in pp.pp_elements(bus=False, branch_elements=False, other_elements=False): @@ -219,9 +219,9 @@ def test_example_simple(): avoid_duplicates_in_column(net, i, 'name') # --- create geodata - net.bus_geodata["x"] = [0, 1, 2, 3, 4, 5, 5, 3.63] - net.bus_geodata["y"] = [0]*5+[-5, 5, 2.33] - merge_busbar_coordinates(net) + net.bus["geo"] = list(map(lambda xy: f'{{"type":"Point", "coordinates":[{xy[0]}, {xy[1]}]}}', + zip([0., 1., 2., 3., 4., 5., 5., 3.63], [0.]*5+[-5., 5., 2.33]))) + merge_busbar_coordinates(net, False) # --- convert csv_data = pp2csv_data(net, export_pp_std_types=True, drop_inactive_elements=True) @@ -256,7 +256,7 @@ def test_example_simple(): logger.error("dtype adjustment of %s failed." % key) # drop result table rows if pp_is_27lower and "res_" in key: - if not key == "res_bus": + if key != "res_bus": net[key] = net[key].iloc[0:0] else: net[key].loc[:, ["p_mw", "q_mvar"]] = np.nan diff --git a/simbench/test/converter/test_network_output_folder/LineType.csv b/simbench/test/converter/test_network_output_folder/LineType.csv index f23c8ad..0042ae1 100644 --- a/simbench/test/converter/test_network_output_folder/LineType.csv +++ b/simbench/test/converter/test_network_output_folder/LineType.csv @@ -1,3 +1,3 @@ id;r;x;b;iMax;type -HV OHL 1;0.05;0.4;3;500;ohl -MV OHL 1;0.25;0.2;1;500;ohl +HV OHL 1;0.05;0.4;2.9999999999999996;500.0;ohl +MV OHL 1;0.25;0.2;0.9999999999999999;500.0;ohl diff --git a/simbench/test/converter/test_network_output_folder/TransformerType.csv b/simbench/test/converter/test_network_output_folder/TransformerType.csv index 9f87716..c7c9a3d 100644 --- a/simbench/test/converter/test_network_output_folder/TransformerType.csv +++ b/simbench/test/converter/test_network_output_folder/TransformerType.csv @@ -1,2 +1,2 @@ id;sR;vmHV;vmLV;va0;vmImp;pCu;pFe;iNoLoad;tapable;tapside;dVm;dVa;tapNeutr;tapMin;tapMax -T1;25;110;20;150;12;70;30;0;1;HV;0;0;0;-5;5 +T1;25.0;110.0;20.0;150.0;12.0;70.00000000000001;30.0;0.0;1;HV;0.0;0;0;-5;5 diff --git a/simbench/test/converter/test_pp_net_manipulation.py b/simbench/test/converter/test_pp_net_manipulation.py index 60a0bb7..a60387e 100644 --- a/simbench/test/converter/test_pp_net_manipulation.py +++ b/simbench/test/converter/test_pp_net_manipulation.py @@ -51,10 +51,8 @@ def _net_to_test(): pp.create_switch(net, lv_buses[2], l0, "l") pp.create_switch(net, lv_buses[3], l1, "l") -# create_generic_coordinates(net) - net.bus_geodata["x"] = [1, 2, 1, 3, 0, 1, 2] - net.bus_geodata["y"] = [0, 1, -1, 0, 0, 1, 0] - net.bus_geodata.index = list(range(6))+[8] + net.bus["geo"] = list(map(lambda xy: f'{{"type":"Point", "coordinates":[{xy[0]}, {xy[1]}]}}', + zip([1., 2., 1., 3., 0., 1., 2], [0., 1., -1., 0., 0., 1., 0]))) return net @@ -65,8 +63,6 @@ def test_branch_switch_changes(): net1 = deepcopy(net_orig) replace_branch_switches(net1) - net1.bus_geodata = net1.bus_geodata.astype({coord: net_orig.bus_geodata.dtypes[ - coord] for coord in ["x", "y"]}) assert net_orig.switch.shape == net1.switch.shape assert (net_orig.switch.bus == net1.switch.bus).all() diff --git a/simbench/test/networks/test_extract_grids_from_csv.py b/simbench/test/networks/test_extract_grids_from_csv.py index e0005ba..4393f34 100644 --- a/simbench/test/networks/test_extract_grids_from_csv.py +++ b/simbench/test/networks/test_extract_grids_from_csv.py @@ -393,14 +393,7 @@ def _test_net_validity(net, sb_code_params, shortened, input_path=None): assert net.measurement.shape[0] > 1 # bus_geodata - assert net.bus.shape[0] == net.bus_geodata.shape[0] - # check_that_all_buses_connected_by_switches_have_same_geodata - # for bus_group in bus_groups_connected_by_switches(net): - # first_bus = list(bus_group)[0] - # assert np.all(np.isclose(net.bus_geodata.x.loc[bus_group].astype(float), - # net.bus_geodata.x.loc[first_bus].astype(float))) \ - # and np.all(np.isclose(net.bus_geodata.y.loc[bus_group].astype(float), - # net.bus_geodata.y.loc[first_bus].astype(float))) + assert not net.bus.geo.isnull().any() # --- test data content # substation diff --git a/tutorials/EHVHV_powerflow_expl.ipynb b/tutorials/EHVHV_powerflow_expl.ipynb new file mode 100644 index 0000000..b97ea51 --- /dev/null +++ b/tutorials/EHVHV_powerflow_expl.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"markdown","metadata":{},"source":["# Power Flow Example for the EHV and HV grids\n","\n","This tutorial is a quick answer related to multiple more or less similar questions regarding \"Making the complete SimBench grid run\".\n","\n","First of all, during the research project, most use cases were identified to be covered by power system data that models one or two voltage levels at the same time. Accordingly, the SimBench codes include those combinations of the base grids of SimBench (1 EHV grid, 2 HV grids, 4 MV grids, and 6 LV grids) which were expected most relevant for users' application. For reasons of completeness, the complete data sets \"1-complete_data-mixed-all-0-sw\" (no option to perform a proper power flow), \"1-EHVHVMVLV-mixed-all-0-sw\" (not tested for proper power flow results), and related data sets for futures scenarios 1 and 2 were provided, as well.\n","\n","In contrast, this tutorial provides a simple example of a combined power flow for the German EHV and the two HV Grids provided by SimBench."]},{"cell_type":"markdown","metadata":{},"source":["First get the SimBench grid of the code \"1-EHVHV-mixed-all-0-no_sw\"."]},{"cell_type":"code","execution_count":5,"metadata":{},"outputs":[],"source":["import pandas as pd\n","import matplotlib.pyplot as plt\n","import pandapower as pp\n","import simbench as sb\n","\n","net = sb.get_simbench_net(\"1-EHVHV-mixed-all-0-no_sw\")"]},{"cell_type":"markdown","metadata":{},"source":["Note that the nuclear power units (which were active at the time of the project and are thus included in the scenario 0) were set as slack elements, i.e. in `pandapower` as external grid elements (`ext_grid`). For simplicity of the code, we replace these to `gen` elements.\n","\n","On top of this, we need to assume some kind of simultaneity factor of the loads and sgens (`scaling`). Let's assume `0.35`. Accordingly, we adjust the active power of generators so that the total electric power consumption equals the total feed-in (we start with a DC power flow which disregards branch losses)."]},{"cell_type":"code","execution_count":6,"metadata":{},"outputs":[],"source":["# --- convert ext_grids to gen elements and set some assumed active power setpoints\n","nuclear_gens = pp.toolbox.replace_ext_grid_by_gen(net, add_cols_to_keep=[\"slack_weight\"])\n","net.gen.at[net.gen.index[-1], \"slack\"] = True\n","gens_with_p_2bset = pd.Index(nuclear_gens).difference([342])\n","net.gen.loc[gens_with_p_2bset, \"p_mw\"] = net.gen.loc[gens_with_p_2bset, \"max_p_mw\"]\n","\n","# --- set scalings\n","scaling = {\n"," \"load\": 0.35,\n"," \"sgen\": 0.35,\n","}\n","gen_xg_scaling = ((net.load.p_mw *scaling[\"load\"]).sum() - (net.sgen.p_mw *scaling[\"sgen\"]).sum()) / (\n"," net.gen.p_mw.sum() + net.ext_grid.max_p_mw.sum())\n","scaling[\"gen\"] = gen_xg_scaling\n","\n","# --- apply the assumed and calculated scaling values\n","for et, scale in scaling.items():\n"," net[et][\"scaling\"] = scale"]},{"cell_type":"markdown","metadata":{},"source":["## DC Power Flow Results"]},{"cell_type":"code","execution_count":7,"metadata":{},"outputs":[{"name":"stderr","output_type":"stream","text":["hp.pandapower.toolbox.result_info - INFO: Max voltage in vm_pu:\n","hp.pandapower.toolbox.result_info - INFO: 1.092 at busidx 0 (EHV Bus 1)\n","hp.pandapower.toolbox.result_info - INFO: Min voltage in vm_pu:\n","hp.pandapower.toolbox.result_info - INFO: 1.0 at busidx 3748 (HV2 Bus 361)\n","hp.pandapower.toolbox.result_info - INFO: Max loading trafo in %:\n","hp.pandapower.toolbox.result_info - INFO: 51.08204436767962 loading at trafo 34 (EHV Trafo 35)\n","hp.pandapower.toolbox.result_info - INFO: 51.08204436767962 loading at trafo 35 (EHV Trafo 36)\n","hp.pandapower.toolbox.result_info - INFO: Max loading line in %:\n","hp.pandapower.toolbox.result_info - INFO: 92.73344755050144 loading at line 824 (EHV Line 825)\n","hp.pandapower.toolbox.result_info - INFO: 85.93217745798063 loading at line 768 (EHV Line 769)\n"]}],"source":["# --- perform DC power flow\n","pp.rundcpp(net, distributed_slack=True)\n","\n","# --- log some results\n","pp.toolbox.lf_info(net)"]},{"cell_type":"markdown","metadata":{},"source":["The log shows that the DC power flow results of this simple example include no line or transformer overloadings (max. line loading is around 93 %)."]},{"cell_type":"markdown","metadata":{},"source":["## AC Power Flow Results\n","\n","In addition to a realistic load case (here equal scaling factors are applied all over the grid), several adjustments for the volt-var control are required for realistic AC power flows.\n","Again, only tap controllers for EHV-HV transformers and a distributed slack (to balance the branch losses) are applied here."]},{"cell_type":"code","execution_count":8,"metadata":{},"outputs":[],"source":["ehv_hv_trafos = sb.voltlvl_idx(net, \"trafo\", [3], \"lv_bus\")\n","pp.control.ContinuousTapControl(net, ehv_hv_trafos, 1.0)\n","pp.runpp(net, run_control=True, distributed_slack=True)"]},{"cell_type":"markdown","metadata":{},"source":["After calculating the AC power flow, we can analyze the voltage range and branch with the highest loading, cf. log and plot. As expected, the loadings increase. However, even this very simple assumptions result in acceptable voltages and almost no branch overloadings.\n","In actual grid operations, security margins for n-1 security must be added, especially regarding the few highly loaded lines."]},{"cell_type":"code","execution_count":9,"metadata":{},"outputs":[{"name":"stderr","output_type":"stream","text":["hp.pandapower.toolbox.result_info - INFO: Max voltage in vm_pu:\n","hp.pandapower.toolbox.result_info - INFO: 1.1070887174089667 at busidx 56 (EHV Bus 57)\n","hp.pandapower.toolbox.result_info - INFO: Min voltage in vm_pu:\n","hp.pandapower.toolbox.result_info - INFO: 0.9933431959987652 at busidx 3477 (HV2 Bus 90)\n","hp.pandapower.toolbox.result_info - INFO: Max loading trafo in %:\n","hp.pandapower.toolbox.result_info - INFO: 48.65530642371104 loading at trafo 34 (EHV Trafo 35)\n","hp.pandapower.toolbox.result_info - INFO: 48.65530642371104 loading at trafo 35 (EHV Trafo 36)\n","hp.pandapower.toolbox.result_info - INFO: Max loading line in %:\n","hp.pandapower.toolbox.result_info - INFO: 101.5984172475295 loading at line 768 (EHV Line 769)\n","hp.pandapower.toolbox.result_info - INFO: 91.14251627977873 loading at line 824 (EHV Line 825)\n"]},{"data":{"image/png":"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1cjIyEBCQgLCwsLE9oCAAGRkZECtVkuYHZHpsagjIiLZUKvVGDp0KHeUIKvEoo6IiGRFqVQiIiJC6jSIHjqOqSMiIiKSARZ1RERERDLAoo6IiIhIBljUEREREckAJ0qQRSm6XIWqmroHusbJ0kqD/z6I5ipbBLRq/sDXISIielAs6shiFF2uQmRqttGuF7epwCjX2ZUYwcKOiIgkx6KOLEZ9D136c10Q5OH0h69z46YO565Ww9fNAfbN/vjaVSdLKxG3qeCBew6JiIiMgUUdWZwgDycEt3F9oGt09zdOLkREROaCEyWIiIiIZIBFHREREZEMsKgjIiIikgEWdUREREQywKKOiIiISAZY1BERERHJAIs6IiIiIhlgUUdEREQkAyzqiIiIiGSARR0RERGRDLCoIyIiIpIBFnVEREREMsCijoiIiEgGWNQRERERyQCLOiIiIiIZYFFHREREJAMs6oiIiIhkgEUdERERkQywqCMiIiKSARZ1RERERDLAoo6IiIhIBljUEREREckAizoiIiIiGWBRR0Qksbq6OsyaNQsBAQFwcHDAI488grlz50Kv14sxgiBgzpw58PHxgYODAyIiInDkyBEJsyYic8OijohIYu+//z5WrFiBpUuX4ujRo5g/fz4WLFiADz74QIyZP38+Fi1ahKVLl2L//v3w8vJCv379UFFRIWHmRGROWNQREUksLy8PQ4cOxaBBg+Dv74/Y2Fj0798f+fn5AG710qWnp2PmzJlQq9UIDg7GunXrcP36daxfv17i7InIXLCoIyKSWO/evbFz504cP34cAPDTTz9hz549GDhwIACgqKgIJSUl6N+/v/gclUqFPn36YO/evZLkTETmx1bqBIiIrN3f//53lJWV4bHHHoNSqYROp8N7772HF154AQBQUlICAPD09DR4nqenJ06fPn3X69bU1KCmpkY8Li8vN0H2RGQu2FNHRCSxTZs24dNPP8X69etx8OBBrFu3DqmpqVi3bp1BnEKhMDgWBKFB2+1SUlLg6uoqPvz8/EySPxGZBxZ1REQSmzZtGt588008//zzCAkJwejRozF16lSkpKQAALy8vAD8r8euXmlpaYPeu9slJSWhrKxMfJw9e9Z0L4KIJMfbr2QxanQ3YGN/HkXlx2Bj7yR1Oigqr4SN/XnU6G4AcJU6HbJg169fh42N4d/YSqVSXNIk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","text/plain":["
"]},"metadata":{},"output_type":"display_data"}],"source":["pp.lf_info(net)\n","\n","# --- plot voltages and loadings of the AC power flow results\n","fig, axs = plt.subplots(ncols=2)\n","net.res_bus.vm_pu.rename(\"bus\").plot(kind=\"box\", ax=axs[0])\n","branch_loadings = pd.concat([\n"," net.res_line.loading_percent, net.res_trafo.loading_percent],\n"," axis=1, keys=[\"line\", \"trafo\"])\n","branch_loadings.plot(kind=\"box\", ax=axs[1])\n","axs[0].set_ylabel(\"vm in pu\")\n","axs[1].set_ylabel(\"loading in %\")\n","plt.tight_layout()"]}],"metadata":{"kernelspec":{"display_name":"Python 3.10.11 ('base')","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.11"},"orig_nbformat":4,"vscode":{"interpreter":{"hash":"19d1d53a962d236aa061289c2ac16dc8e6d9648c89fe79f459ae9a3493bc67b4"}}},"nbformat":4,"nbformat_minor":2} diff --git a/tutorials/simbench_grids_basics_and_usage.ipynb b/tutorials/simbench_grids_basics_and_usage.ipynb index 517c7c6..2745c95 100644 --- a/tutorials/simbench_grids_basics_and_usage.ipynb +++ b/tutorials/simbench_grids_basics_and_usage.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -120,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -138,7 +138,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -157,7 +157,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -179,27 +179,26 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "This pandapower network includes the following parameter tables:\n", - " - bus (97 element)\n", - " - load (96 element)\n", - " - sgen (102 element)\n", - " - switch (204 element)\n", - " - ext_grid (1 elements)\n", - " - line (99 element)\n", - " - trafo (2 element)\n", - " - measurement (37 element)\n", - " - bus_geodata (97 element)\n", - " - substation (1 elements)\n", - " - loadcases (6 element)" + " - bus (97 elements)\n", + " - load (96 elements)\n", + " - sgen (102 elements)\n", + " - switch (204 elements)\n", + " - ext_grid (1 element)\n", + " - line (99 elements)\n", + " - trafo (2 elements)\n", + " - measurement (37 elements)\n", + " - substation (1 element)\n", + " - loadcases (6 elements)" ] }, - "execution_count": 46, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -255,7 +254,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -273,14 +272,14 @@ "# let's run a simple power flow calculation while assuming an outage of the first line in feeder 1\n", "outage_line = 1\n", "outage_line_switches = net.switch.index[(net.switch.element == outage_line) & (net.switch.et == \"l\")]\n", - "net.switch.closed.loc[outage_line_switches] = False\n", + "net.switch.loc[outage_line_switches, \"closed\"] = False\n", "\n", "# resupply feeder 1 via feeder 5\n", "feeder1_buses = net.bus.index[net.bus.subnet.str.contains(\"Feeder1\")]\n", "feeder5_buses = net.bus.index[net.bus.subnet.str.contains(\"Feeder5\")]\n", "loop_line_1_5 = net.line.index[net.line.from_bus.isin(feeder1_buses) & net.line.to_bus.isin(feeder5_buses)]\n", "loop_switches_1_5 = net.switch.index[(net.switch.element == loop_line_1_5[0]) & (net.switch.et == \"l\")]\n", - "net.switch.closed.loc[loop_switches_1_5] = True\n", + "net.switch.loc[loop_switches_1_5, \"closed\"] = True\n", "\n", "# run a simple power flow\n", "pp.runpp(net)\n", @@ -988,7 +987,7 @@ "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 3.10.11 ('base')", + "display_name": "base", "language": "python", "name": "python3" }, @@ -1003,11 +1002,6 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.11" - }, - "vscode": { - "interpreter": { - "hash": "19d1d53a962d236aa061289c2ac16dc8e6d9648c89fe79f459ae9a3493bc67b4" - } } }, "nbformat": 4,