From 3dba6f8bbbb6dc4c3871e623931ab96dd5f4d627 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Thu, 4 May 2023 17:21:52 -0600 Subject: [PATCH 01/17] Fix save paths bug not saving as geopackage --- reVX/least_cost_xmission/least_cost_paths.py | 6 +++--- tests/test_xmission_least_cost.py | 18 +++++++++++++----- tests/test_xmission_least_cost_paths.py | 16 ++++++++++++---- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/reVX/least_cost_xmission/least_cost_paths.py b/reVX/least_cost_xmission/least_cost_paths.py index 117c34959..ee601d8e9 100644 --- a/reVX/least_cost_xmission/least_cost_paths.py +++ b/reVX/least_cost_xmission/least_cost_paths.py @@ -249,7 +249,7 @@ def end_features(self): Returns ------- - geopandas.GeoDataFrame + pandas.DataFrame """ end_features = self._features.drop(index=self._start_feature_ind) end_features['start_index'] = self._start_feature_ind @@ -325,7 +325,7 @@ def process_least_cost_paths(self, capacity_class, barrier_mult=100, end_features = end_features.drop(columns=['row', 'col'], errors="ignore") lcp = future.result() - lcp = pd.concat((end_features, lcp), axis=1) + lcp = pd.concat((lcp, end_features), axis=1) least_cost_paths.append(lcp) logger.debug('Least cost path {} of {} complete!' .format(i + 1, len(futures))) @@ -344,7 +344,7 @@ def process_least_cost_paths(self, capacity_class, barrier_mult=100, save_paths=save_paths) end_features = self.end_features.drop(columns=['row', 'col'], errors="ignore") - lcp = pd.concat((end_features, lcp), axis=1) + lcp = pd.concat((lcp, end_features), axis=1) least_cost_paths.append(lcp) logger.debug('Least cost path {} of {} complete!' diff --git a/tests/test_xmission_least_cost.py b/tests/test_xmission_least_cost.py index c4a6e4ea2..ae557e726 100644 --- a/tests/test_xmission_least_cost.py +++ b/tests/test_xmission_least_cost.py @@ -183,7 +183,8 @@ def test_resolution(resolution): check_baseline(truth, test) -def test_cli(runner): +@pytest.mark.parametrize("save_paths", [False, True]) +def test_cli(runner, save_paths): """ Test CostCreator CLI """ @@ -201,7 +202,8 @@ def test_cli(runner): "cost_fpath": COST_H5, "features_fpath": FEATURES, "capacity_class": f'{capacity}MW', - "min_line_length": 5.76 + "min_line_length": 5.76, + "save_paths": save_paths } config_path = os.path.join(td, 'config.json') with open(config_path, 'w') as f: @@ -213,9 +215,15 @@ def test_cli(runner): .format(traceback.print_exception(*result.exc_info))) assert result.exit_code == 0, msg - test = '{}_{}MW_128.csv'.format(os.path.basename(td), capacity) - test = os.path.join(td, test) - test = pd.read_csv(test) + if save_paths: + test = '{}_{}MW_128.gpkg'.format(os.path.basename(td), capacity) + test = os.path.join(td, test) + test = gpd.read_file(test) + assert test.geometry is not None + else: + test = '{}_{}MW_128.csv'.format(os.path.basename(td), capacity) + test = os.path.join(td, test) + test = pd.read_csv(test) SupplyCurve._check_substation_conns(test, sc_cols='sc_point_gid') check_baseline(truth, test) diff --git a/tests/test_xmission_least_cost_paths.py b/tests/test_xmission_least_cost_paths.py index 87f568f78..2bab4978e 100644 --- a/tests/test_xmission_least_cost_paths.py +++ b/tests/test_xmission_least_cost_paths.py @@ -111,7 +111,8 @@ def test_parallel(max_workers): check(truth, test) -def test_cli(runner): +@pytest.mark.parametrize("save_paths", [False, True]) +def test_cli(runner, save_paths): """ Test CostCreator CLI """ @@ -129,6 +130,7 @@ def test_cli(runner): "cost_fpath": COST_H5, "features_fpath": FEATURES, "capacity_class": f'{capacity}MW', + "save_paths": save_paths } config_path = os.path.join(td, 'config.json') with open(config_path, 'w') as f: @@ -145,9 +147,15 @@ def test_cli(runner): capacity_class = xmission_config._parse_cap_class(capacity) cap = xmission_config['power_classes'][capacity_class] kv = xmission_config.capacity_to_kv(capacity_class) - test = '{}_{}MW_{}kV.csv'.format(os.path.basename(td), cap, kv) - test = os.path.join(td, test) - test = pd.read_csv(test) + if save_paths: + test = '{}_{}MW_{}kV.gpkg'.format(os.path.basename(td), cap, kv) + test = os.path.join(td, test) + test = gpd.read_file(test) + assert test.geometry is not None + else: + test = '{}_{}MW_{}kV.csv'.format(os.path.basename(td), cap, kv) + test = os.path.join(td, test) + test = pd.read_csv(test) check(truth, test) LOGGERS.clear() From 78b0f47bb681e64d15523807319b0a4f04aca4e8 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Thu, 4 May 2023 17:55:35 -0600 Subject: [PATCH 02/17] Added save_path tests for reinforcement cost calculations --- tests/test_xmission_least_cost.py | 22 ++++++++++++++++------ tests/test_xmission_least_cost_paths.py | 19 ++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/tests/test_xmission_least_cost.py b/tests/test_xmission_least_cost.py index ae557e726..235d12c54 100644 --- a/tests/test_xmission_least_cost.py +++ b/tests/test_xmission_least_cost.py @@ -203,7 +203,7 @@ def test_cli(runner, save_paths): "features_fpath": FEATURES, "capacity_class": f'{capacity}MW', "min_line_length": 5.76, - "save_paths": save_paths + "save_paths": save_paths, } config_path = os.path.join(td, 'config.json') with open(config_path, 'w') as f: @@ -230,7 +230,8 @@ def test_cli(runner, save_paths): LOGGERS.clear() -def test_reinforcement_cli(runner, ri_ba): +@pytest.mark.parametrize("save_paths", [False, True]) +def test_reinforcement_cli(runner, ri_ba, save_paths): """ Test Reinforcement cost routines and CLI """ @@ -262,8 +263,10 @@ def test_reinforcement_cli(runner, ri_ba): "balancing_areas_fpath": ri_ba_path, "capacity_class": f'{capacity}MW', "barrier_mult": 100, - "min_line_length": 0 + "min_line_length": 0, + "save_paths": save_paths, } + config_path = os.path.join(td, 'config.json') with open(config_path, 'w') as f: json.dump(config, f) @@ -274,9 +277,15 @@ def test_reinforcement_cli(runner, ri_ba): .format(traceback.print_exception(*result.exc_info))) assert result.exit_code == 0, msg - test = '{}_{}MW_128.csv'.format(os.path.basename(td), capacity) - test = os.path.join(td, test) - test = pd.read_csv(test) + if save_paths: + test = '{}_{}MW_128.gpkg'.format(os.path.basename(td), capacity) + test = os.path.join(td, test) + test = gpd.read_file(test) + assert test.geometry is not None + else: + test = '{}_{}MW_128.csv'.format(os.path.basename(td), capacity) + test = os.path.join(td, test) + test = pd.read_csv(test) assert len(test) == 13 assert set(test.trans_gid.unique()) == {69130} @@ -284,6 +293,7 @@ def test_reinforcement_cli(runner, ri_ba): assert "poi_lat" in test assert "poi_lon" in test + assert "ba_str" in test assert len(test.poi_lat.unique()) == 1 assert len(test.poi_lon.unique()) == 1 diff --git a/tests/test_xmission_least_cost_paths.py b/tests/test_xmission_least_cost_paths.py index 2bab4978e..ce66c22aa 100644 --- a/tests/test_xmission_least_cost_paths.py +++ b/tests/test_xmission_least_cost_paths.py @@ -130,7 +130,7 @@ def test_cli(runner, save_paths): "cost_fpath": COST_H5, "features_fpath": FEATURES, "capacity_class": f'{capacity}MW', - "save_paths": save_paths + "save_paths": save_paths, } config_path = os.path.join(td, 'config.json') with open(config_path, 'w') as f: @@ -161,7 +161,8 @@ def test_cli(runner, save_paths): LOGGERS.clear() -def test_reinforcement_cli(runner, ba_regions_and_network_nodes): +@pytest.mark.parametrize("save_paths", [False, True]) +def test_reinforcement_cli(runner, ba_regions_and_network_nodes, save_paths): """ Test Reinforcement cost routines and CLI """ @@ -208,6 +209,7 @@ def test_reinforcement_cli(runner, ba_regions_and_network_nodes): "transmission_lines_fpath": ALLCONNS_FEATURES, "capacity_class": f"{capacity}MW", "barrier_mult": 100, + "save_paths": save_paths, } config_path = os.path.join(td, 'config.json') with open(config_path, 'w') as f: @@ -223,9 +225,15 @@ def test_reinforcement_cli(runner, ba_regions_and_network_nodes): capacity_class = xmission_config._parse_cap_class(capacity) cap = xmission_config['power_classes'][capacity_class] kv = xmission_config.capacity_to_kv(capacity_class) - test = '{}_{}MW_{}kV.csv'.format(os.path.basename(td), cap, kv) - test = os.path.join(td, test) - test = pd.read_csv(test) + if save_paths: + test = '{}_{}MW_{}kV.gpkg'.format(os.path.basename(td), cap, kv) + test = os.path.join(td, test) + test = gpd.read_file(test) + assert test.geometry is not None + else: + test = '{}_{}MW_{}kV.csv'.format(os.path.basename(td), cap, kv) + test = os.path.join(td, test) + test = pd.read_csv(test) assert "reinforcement_poi_lat" in test assert "reinforcement_poi_lon" in test @@ -233,6 +241,7 @@ def test_reinforcement_cli(runner, ba_regions_and_network_nodes): assert "poi_lon" not in test assert len(test["reinforcement_poi_lat"].unique()) == 4 assert len(test["reinforcement_poi_lon"].unique()) == 4 + assert "ba_str" in test assert len(test) == 69 assert np.isclose(test.reinforcement_cost_per_mw.min(), 3332.695, From 4df799ecdd1c2138c8a4b95994ef0fca8f129859 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 8 May 2023 09:48:35 -0600 Subject: [PATCH 03/17] Add a `allow_connections_within_states` input --- reVX/config/least_cost_xmission.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/reVX/config/least_cost_xmission.py b/reVX/config/least_cost_xmission.py index 800a7b16e..91257b541 100644 --- a/reVX/config/least_cost_xmission.py +++ b/reVX/config/least_cost_xmission.py @@ -306,6 +306,14 @@ def barrier_mult(self): """ return self.get('barrier_mult', self._default_barrier_mult) + @property + def allow_connections_within_states(self): + """ + Boolean flag to allow substations to connect to endpoints + outside their BA but within their own state. + """ + return self.get("allow_connections_within_states", False) + @property def save_paths(self): """ From 09b6df81071e74020e0748e5d7a596ee6c60f1b6 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 8 May 2023 10:08:11 -0600 Subject: [PATCH 04/17] Add `allow_connections_within_states` logic --- reVX/least_cost_xmission/least_cost_paths.py | 43 ++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/reVX/least_cost_xmission/least_cost_paths.py b/reVX/least_cost_xmission/least_cost_paths.py index ee601d8e9..2e48d5554 100644 --- a/reVX/least_cost_xmission/least_cost_paths.py +++ b/reVX/least_cost_xmission/least_cost_paths.py @@ -541,7 +541,8 @@ def process_least_cost_paths(self, capacity_class, barrier_mult=100, @classmethod def run(cls, cost_fpath, features_fpath, network_nodes_fpath, transmission_lines_fpath, capacity_class, xmission_config=None, - barrier_mult=100, indices=None, save_paths=False): + barrier_mult=100, indices=None, + allow_connections_within_states=False, save_paths=False): """ Find the reinforcement line paths between the network node and the substations for the given tie-line capacity class @@ -580,6 +581,10 @@ def run(cls, cost_fpath, features_fpath, network_nodes_fpath, max_workers : int, optional Number of workers to use for processing. If 1 run in serial, if ``None`` use all available cores. By default, ``None``. + allow_connections_within_states : bool, optional + Allow substations to connect to network nodes outside of + their own BA, as long as all connections stay within the + same state. By default, ``False``. save_paths : bool, optional Flag to save reinforcement line path as a multi-line geometry. By default, ``False``. @@ -621,7 +626,15 @@ def run(cls, cost_fpath, features_fpath, network_nodes_fpath, network_node = (network_nodes.iloc[index:index + 1] .reset_index(drop=True)) ba_str = network_node["ba_str"].values[0] - node_substations = substations[substations["ba_str"] == ba_str] + if allow_connections_within_states: + state_nodes = network_nodes[network_nodes["state"] + == network_node["state"].values[0]] + allowed_bas = set(state_nodes["ba_str"]) + else: + allowed_bas = {ba_str} + + node_substations = substations[substations["ba_str"] + .isin(allowed_bas)] node_substations = node_substations.reset_index(drop=True) logger.debug('Working on {} substations in BA {}' .format(len(node_substations), ba_str)) @@ -638,7 +651,8 @@ def run(cls, cost_fpath, features_fpath, network_nodes_fpath, logger.info('{} paths were computed in {:.4f} hours' .format(len(least_cost_paths), (time.time() - ts) / 3600)) - return pd.concat(least_cost_paths, ignore_index=True) + costs = pd.concat(least_cost_paths, ignore_index=True) + return min_reinforcement_costs(costs) def _rasterize_transmission(transmission_lines, xmission_config, cost_shape, @@ -675,3 +689,26 @@ def _rasterize_transmission_layer(transmission_lines, cost_shape, fill=0, transform=cost_transform) return out + + +def min_reinforcement_costs(table): + """Filter table down to cheapest reinforcement per substation. + + Parameters + ---------- + table : pd.DataFrame | gpd.GeoDataFrame + Table containing costs for reinforced transmission. Must contain + a `gid` column identifying each substation with its own unique + ID and a `reinforcement_cost_per_mw` column with the + reinforcement costs to minimize. + + Returns + ------- + pd.DataFrame | gpd.GeoDataFrame + Table with a single entry for each `gid` with the least + `reinforcement_cost_per_mw`. + """ + + grouped = table.groupby('gid') + table = table.loc[grouped["reinforcement_cost_per_mw"].idxmin()] + return table.reset_index(drop=True) From cdcb090cfe80ab2191adafcafce50da673fccd1f Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 8 May 2023 10:08:28 -0600 Subject: [PATCH 05/17] Fix test --- reVX/least_cost_xmission/least_cost_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reVX/least_cost_xmission/least_cost_paths.py b/reVX/least_cost_xmission/least_cost_paths.py index 2e48d5554..116477fd9 100644 --- a/reVX/least_cost_xmission/least_cost_paths.py +++ b/reVX/least_cost_xmission/least_cost_paths.py @@ -534,7 +534,7 @@ def process_least_cost_paths(self, capacity_class, barrier_mult=100, barrier_mult=barrier_mult, save_paths=save_paths) feats = self._features.drop(columns=['row', 'col']) - least_cost_paths = pd.concat((feats, lcp), axis=1) + least_cost_paths = pd.concat((lcp, feats), axis=1) return least_cost_paths.drop("index", axis="columns", errors="ignore") From f7acfa84a282dff6245ee0e76937e365f2a9f0d6 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 8 May 2023 10:08:56 -0600 Subject: [PATCH 06/17] Minor updates to merge logic --- reVX/least_cost_xmission/least_cost_xmission_cli.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/reVX/least_cost_xmission/least_cost_xmission_cli.py b/reVX/least_cost_xmission/least_cost_xmission_cli.py index 2789adecd..5aa8558a4 100644 --- a/reVX/least_cost_xmission/least_cost_xmission_cli.py +++ b/reVX/least_cost_xmission/least_cost_xmission_cli.py @@ -26,6 +26,7 @@ ReinforcedXmission) from reVX.least_cost_xmission.config import (TRANS_LINE_CAT, LOAD_CENTER_CAT, SINK_CAT, SUBSTATION_CAT) +from reVX.least_cost_xmission.least_cost_paths import min_reinforcement_costs TRANS_CAT_TYPES = [TRANS_LINE_CAT, LOAD_CENTER_CAT, SINK_CAT, SUBSTATION_CAT] @@ -294,6 +295,9 @@ def merge_output(ctx, split_to_geojson, out_file, out_dir, drop, # noqa logger.info('Simplifying geometries by {}'.format(simplify_geo)) df.geometry = df.geometry.simplify(simplify_geo) + if all(col in df for col in ["gid", "reinforcement_cost_per_mw"]): + df = min_reinforcement_costs(df) + if not split_to_geojson: out_file = ('combo_{}'.format(files[0]) if out_file is None else out_file) @@ -349,7 +353,8 @@ def merge_reinforcement_costs(ctx, cost_fpath, reinforcement_cost_fpath, logger.info("Merging reinforcement costs into transmission costs...") - r_cols = ["reinforcement_dist_km", "reinforcement_cost_per_mw"] + r_cols = ["ba_str", "reinforcement_poi_lat", "reinforcement_poi_lon", + "reinforcement_dist_km", "reinforcement_cost_per_mw"] costs[r_cols] = r_costs.loc[costs["trans_gid"], r_cols].values logger.info("Writing output to {!r}".format(out_file)) From 96efa8fd1354f935cb718be646ce729eec0231d7 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 8 May 2023 10:09:10 -0600 Subject: [PATCH 07/17] Add `allow_connections_within_states` to cli --- .../least_cost_paths_cli.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/reVX/least_cost_xmission/least_cost_paths_cli.py b/reVX/least_cost_xmission/least_cost_paths_cli.py index 8cecf43a5..66d71780d 100644 --- a/reVX/least_cost_xmission/least_cost_paths_cli.py +++ b/reVX/least_cost_xmission/least_cost_paths_cli.py @@ -163,6 +163,9 @@ def from_config(ctx, config, verbose): show_default=True, default=None, help=("Number of workers to use for processing, if 1 run in " "serial, if None use all available cores")) +@click.option('--state_connections', '-acws', is_flag=True, + help='Flag to allow substations ot connect to any endpoints ' + 'within their state. Default is not verbose.') @click.option('--save_paths', '-paths', is_flag=True, help="Flag to save least cost path as a multi-line geometry") @click.option('--out_dir', '-o', type=STR, default='./', @@ -176,7 +179,8 @@ def from_config(ctx, config, verbose): @click.pass_context def local(ctx, cost_fpath, features_fpath, capacity_class, network_nodes_fpath, transmission_lines_fpath, xmission_config, start_index, step_index, - barrier_mult, max_workers, save_paths, out_dir, log_dir, verbose): + barrier_mult, max_workers, state_connections, save_paths, out_dir, + log_dir, verbose): """ Run Least Cost Paths on local hardware """ @@ -197,14 +201,16 @@ def local(ctx, cost_fpath, features_fpath, capacity_class, network_nodes_fpath, features = gpd.read_file(network_nodes_fpath) features, *__ = LeastCostPaths._map_to_costs(cost_fpath, features) indices = features.index[start_index::step_index] + kwargs = {"xmission_config": xmission_config, + "barrier_mult": barrier_mult, + "indices": indices, + "allow_connections_within_states": state_connections, + "save_paths": save_paths} least_costs = ReinforcementPaths.run(cost_fpath, features_fpath, network_nodes_fpath, transmission_lines_fpath, capacity_class, - xmission_config=xmission_config, - barrier_mult=barrier_mult, - indices=indices, - save_paths=save_paths) + **kwargs) else: features = gpd.read_file(features_fpath) features, *__ = LeastCostPaths._map_to_costs(cost_fpath, features) @@ -328,6 +334,9 @@ def get_node_cmd(config, start_index=0): '-log {}'.format(SLURM.s(config.log_directory)), ] + if config.allow_connections_within_states: + args.append('-acws') + if config.save_paths: args.append('-paths') From 777ce6447adce44447e8f89251ba9885ba7b94d1 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 8 May 2023 11:23:39 -0600 Subject: [PATCH 08/17] Added test for `allow_connections_within_states` --- .../least_cost_paths_cli.py | 1 + tests/test_xmission_least_cost_paths.py | 23 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/reVX/least_cost_xmission/least_cost_paths_cli.py b/reVX/least_cost_xmission/least_cost_paths_cli.py index 66d71780d..40bb85a89 100644 --- a/reVX/least_cost_xmission/least_cost_paths_cli.py +++ b/reVX/least_cost_xmission/least_cost_paths_cli.py @@ -74,6 +74,7 @@ def run_local(ctx, config): start_index=0, step_index=1, barrier_mult=config.barrier_mult, max_workers=config.execution_control.max_workers, + state_connections=config.allow_connections_within_states, save_paths=config.save_paths, out_dir=config.dirout, log_dir=config.log_directory, diff --git a/tests/test_xmission_least_cost_paths.py b/tests/test_xmission_least_cost_paths.py index ce66c22aa..5a38aa0c0 100644 --- a/tests/test_xmission_least_cost_paths.py +++ b/tests/test_xmission_least_cost_paths.py @@ -59,7 +59,9 @@ def ba_regions_and_network_nodes(): ba_str, shapes = zip(*[("p{}".format(int(v)), shape(p)) for p, v in s if int(v) != 0]) - ri_ba = gpd.GeoDataFrame({"ba_str": ba_str}, crs=profile['crs'], + state = ["Rhode Island"] * len(ba_str) + ri_ba = gpd.GeoDataFrame({"ba_str": ba_str, "state": state}, + crs=profile['crs'], geometry=list(shapes)) ri_network_nodes = ri_ba.copy() @@ -162,7 +164,9 @@ def test_cli(runner, save_paths): @pytest.mark.parametrize("save_paths", [False, True]) -def test_reinforcement_cli(runner, ba_regions_and_network_nodes, save_paths): +@pytest.mark.parametrize("state_conns", [False, True]) +def test_reinforcement_cli(runner, ba_regions_and_network_nodes, save_paths, + state_conns): """ Test Reinforcement cost routines and CLI """ @@ -210,6 +214,7 @@ def test_reinforcement_cli(runner, ba_regions_and_network_nodes, save_paths): "capacity_class": f"{capacity}MW", "barrier_mult": 100, "save_paths": save_paths, + "allow_connections_within_states": state_conns } config_path = os.path.join(td, 'config.json') with open(config_path, 'w') as f: @@ -239,17 +244,23 @@ def test_reinforcement_cli(runner, ba_regions_and_network_nodes, save_paths): assert "reinforcement_poi_lon" in test assert "poi_lat" not in test assert "poi_lon" not in test - assert len(test["reinforcement_poi_lat"].unique()) == 4 - assert len(test["reinforcement_poi_lon"].unique()) == 4 assert "ba_str" in test assert len(test) == 69 assert np.isclose(test.reinforcement_cost_per_mw.min(), 3332.695, atol=0.001) - assert np.isclose(test.reinforcement_cost_per_mw.max(), 569757.740, - atol=0.001) assert np.isclose(test.reinforcement_dist_km.min(), 1.918, atol=0.001) assert np.isclose(test.reinforcement_dist_km.max(), 80.353, atol=0.001) + if state_conns: + assert len(test["reinforcement_poi_lat"].unique()) == 3 + assert len(test["reinforcement_poi_lon"].unique()) == 3 + assert np.isclose(test.reinforcement_cost_per_mw.max(), 225129.798, + atol=0.001) + else: + assert len(test["reinforcement_poi_lat"].unique()) == 4 + assert len(test["reinforcement_poi_lon"].unique()) == 4 + assert np.isclose(test.reinforcement_cost_per_mw.max(), 569757.740, + atol=0.001) LOGGERS.clear() From be682b2fecfe66c1d8c4cd90641bfc058746589f Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 16 May 2023 10:17:37 -0600 Subject: [PATCH 09/17] Add `allow_connections_within_states` option to config --- reVX/config/least_cost_xmission.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/reVX/config/least_cost_xmission.py b/reVX/config/least_cost_xmission.py index 91257b541..8f320d83c 100644 --- a/reVX/config/least_cost_xmission.py +++ b/reVX/config/least_cost_xmission.py @@ -222,6 +222,14 @@ def barrier_mult(self): """ return self.get('barrier_mult', self._default_barrier_mult) + @property + def allow_connections_within_states(self): + """ + Boolean flag to allow supple curve points to connect to + substations outside their BA but within their own state. + """ + return self.get("allow_connections_within_states", False) + class LeastCostPathsConfig(AnalysisConfig): """Config framework for Least Cost Paths""" From bac0a665f8fb130b6b1dbdb47c2c726b1b284797 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 16 May 2023 10:18:17 -0600 Subject: [PATCH 10/17] Add `allow_connections_within_states` to cli --- .../least_cost_xmission_cli.py | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/reVX/least_cost_xmission/least_cost_xmission_cli.py b/reVX/least_cost_xmission/least_cost_xmission_cli.py index 5aa8558a4..bc9f9540b 100644 --- a/reVX/least_cost_xmission/least_cost_xmission_cli.py +++ b/reVX/least_cost_xmission/least_cost_xmission_cli.py @@ -145,6 +145,9 @@ def from_config(ctx, config, verbose): show_default=True, default=100, help=("Tranmission barrier multiplier, used when computing the " "least cost tie-line path")) +@click.option('--state_connections', '-acws', is_flag=True, + help='Flag to allow substations ot connect to any endpoints ' + 'within their state. Default is not verbose.') @click.option('--max_workers', '-mw', type=INT, show_default=True, default=None, help=("Number of workers to use for processing, if 1 run in " @@ -171,8 +174,8 @@ def from_config(ctx, config, verbose): def local(ctx, cost_fpath, features_fpath, balancing_areas_fpath, capacity_class, resolution, xmission_config, min_line_length, sc_point_start_index, sc_point_step_index, nn_sinks, - clipping_buffer, barrier_mult, max_workers, out_dir, log_dir, - verbose, save_paths, radius, simplify_geo): + clipping_buffer, barrier_mult, state_connections, max_workers, + out_dir, log_dir, verbose, save_paths, radius, simplify_geo): """ Run Least Cost Xmission on local hardware """ @@ -189,33 +192,25 @@ def local(ctx, cost_fpath, features_fpath, balancing_areas_fpath, sce = SupplyCurveExtent(cost_fpath, resolution=resolution) sc_point_gids = list(sce.points.index.values) sc_point_gids = sc_point_gids[sc_point_start_index::sc_point_step_index] + kwargs = {"resolution": resolution, + "xmission_config": xmission_config, + "min_line_length": min_line_length, + "sc_point_gids": sc_point_gids, + "clipping_buffer": clipping_buffer, + "barrier_mult": barrier_mult, + "max_workers": max_workers, + "save_paths": save_paths, + "simplify_geo": simplify_geo, + "radius": radius} if balancing_areas_fpath is not None: + kwargs["allow_connections_within_states"] = state_connections least_costs = ReinforcedXmission.run(cost_fpath, features_fpath, balancing_areas_fpath, - capacity_class, - resolution=resolution, - xmission_config=xmission_config, - min_line_length=min_line_length, - sc_point_gids=sc_point_gids, - clipping_buffer=clipping_buffer, - barrier_mult=barrier_mult, - max_workers=max_workers, - save_paths=save_paths, - simplify_geo=simplify_geo) + capacity_class, **kwargs) else: + kwargs["nn_sinks"] = nn_sinks least_costs = LeastCostXmission.run(cost_fpath, features_fpath, - capacity_class, - resolution=resolution, - xmission_config=xmission_config, - min_line_length=min_line_length, - sc_point_gids=sc_point_gids, - nn_sinks=nn_sinks, - clipping_buffer=clipping_buffer, - barrier_mult=barrier_mult, - max_workers=max_workers, - save_paths=save_paths, - radius=radius, - simplify_geo=simplify_geo) + capacity_class, **kwargs) if len(least_costs) == 0: logger.error('No paths found.') return @@ -399,6 +394,8 @@ def get_node_cmd(config, start_index=0): '-log {}'.format(SLURM.s(config.log_directory)), ] + if config.allow_connections_within_states: + args.append('-acws') if config.save_paths: args.append('--save_paths') if config.radius: @@ -441,6 +438,7 @@ def run_local(ctx, config): nn_sinks=config.nn_sinks, clipping_buffer=config.clipping_buffer, barrier_mult=config.barrier_mult, + state_connections=config.allow_connections_within_states, max_workers=config.execution_control.max_workers, out_dir=config.dirout, log_dir=config.log_directory, From 51b96d097bafbc886b6030d539fecdba67760218 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 16 May 2023 14:45:38 -0600 Subject: [PATCH 11/17] Added test for `state_connections` --- tests/test_xmission_least_cost.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_xmission_least_cost.py b/tests/test_xmission_least_cost.py index 235d12c54..a1f463e52 100644 --- a/tests/test_xmission_least_cost.py +++ b/tests/test_xmission_least_cost.py @@ -88,7 +88,8 @@ def ri_ba(): ba_str, shapes = zip(*[("p{}".format(int(v)), shape(p)) for p, v in s if int(v) != 0]) - return gpd.GeoDataFrame({"ba_str": ba_str}, crs=profile['crs'], + return gpd.GeoDataFrame({"ba_str": ba_str, "state": "Rhode Island"}, + crs=profile['crs'], geometry=list(shapes)) @@ -231,7 +232,8 @@ def test_cli(runner, save_paths): @pytest.mark.parametrize("save_paths", [False, True]) -def test_reinforcement_cli(runner, ri_ba, save_paths): +@pytest.mark.parametrize("state_connections", [False, True]) +def test_reinforcement_cli(runner, ri_ba, save_paths, state_connections): """ Test Reinforcement cost routines and CLI """ @@ -265,6 +267,7 @@ def test_reinforcement_cli(runner, ri_ba, save_paths): "barrier_mult": 100, "min_line_length": 0, "save_paths": save_paths, + "allow_connections_within_states": state_connections, } config_path = os.path.join(td, 'config.json') @@ -287,7 +290,7 @@ def test_reinforcement_cli(runner, ri_ba, save_paths): test = os.path.join(td, test) test = pd.read_csv(test) - assert len(test) == 13 + assert len(test) == 71 if state_connections else 13 assert set(test.trans_gid.unique()) == {69130} assert set(test.ba_str.unique()) == {"p4"} From b09810eaee64c7fe6127b7370e91cf5cb299eafe Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 16 May 2023 14:52:31 -0600 Subject: [PATCH 12/17] Updated handling of radius value --- .../least_cost_xmission.py | 86 ++++++++++++------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/reVX/least_cost_xmission/least_cost_xmission.py b/reVX/least_cost_xmission/least_cost_xmission.py index 2ad6bd560..a79d0b4df 100644 --- a/reVX/least_cost_xmission/least_cost_xmission.py +++ b/reVX/least_cost_xmission/least_cost_xmission.py @@ -355,7 +355,8 @@ def _clip_to_sc_point(self, sc_point, tie_line_voltage, nn_sinks=2, clipping_buffer : float, optional Buffer to increase clipping radius by, by default 1.05 radius : None | int, optional - Force clipping radius if set to an int + Force clipping radius if set to an int. Radius will be + expanded to include at least one connection feature. Returns ------- @@ -375,29 +376,13 @@ def _clip_to_sc_point(self, sc_point, tie_line_voltage, nn_sinks=2, radius = np.abs(self.sink_coords[pos] - np.array([row, col]) ).max() radius = int(np.ceil(radius * clipping_buffer)) - - if radius: - logger.debug('Using forced radius of {}'.format(radius)) - else: logger.debug('Radius to {} nearest sink is: {}' .format(nn_sinks, radius)) - row_min = max(row - radius, 0) - row_max = min(row + radius, self._shape[0]) - col_min = max(col - radius, 0) - col_max = min(col + radius, self._shape[1]) - logger.debug('Extracting all transmission features in the row ' - 'slice {}:{} and column slice {}:{}' - .format(row_min, row_max, col_min, col_max)) - - # Clip transmission features - mask = self.features['row'] >= row_min - mask &= self.features['row'] < row_max - mask &= self.features['col'] >= col_min - mask &= self.features['col'] < col_max - sc_features = self.features.loc[mask].copy(deep=True) - logger.debug('{} transmission features found in clipped area with ' - 'radius {}' - .format(len(sc_features), radius)) + else: + logger.debug('Using forced radius of {}'.format(radius)) + + sc_features = self._clip_to_radius(sc_point, radius, sc_features, + clipping_buffer) else: sc_features = self.features.copy(deep=True) @@ -426,6 +411,35 @@ def _clip_to_sc_point(self, sc_point, tie_line_voltage, nn_sinks=2, return sc_features, radius + def _clip_to_radius(self, sc_point, radius, sc_features, clipping_buffer): + """Clip features to radius. + + If no features are found within the initial radius, it is + expanded (multiplicatively by the clipping buffer) until at + least one connection feature is found. + """ + if radius is None or not len(sc_features): + return sc_features + + # Get pixel resolution and calculate buffer + with ExclusionLayers(self._cost_fpath) as ds: + resolution = ds.profile["transform"][0] + radius_m = radius * resolution + logger.debug('Clipping features to radius {}m'.format(radius_m)) + buffer = sc_point["geometry"].buffer(radius_m) + clipped_sc_features = sc_features.clip(buffer) + + while len(clipped_sc_features) <= 0: + radius_m *= clipping_buffer + logger.debug('Clipping features to radius {}m'.format(radius_m)) + buffer = sc_point["geometry"].buffer(radius_m) + clipped_sc_features = sc_features.clip(buffer) + + logger.debug('{} transmission features found in clipped area with ' + 'radius {}' + .format(len(clipped_sc_features), radius)) + return clipped_sc_features.copy(deep=True) + def process_sc_points(self, capacity_class, sc_point_gids=None, nn_sinks=2, clipping_buffer=1.05, barrier_mult=100, max_workers=None, save_paths=False, radius=None, @@ -456,7 +470,8 @@ def process_sc_points(self, capacity_class, sc_point_gids=None, nn_sinks=2, Flag to return least cost paths as a multi-line geometry, by default False radius : None | int, optional - Force clipping radius if set to an int + Force clipping radius if set to an int. Radius will be + expanded to include at least one connection feature. mp_delay : float, optional Delay in seconds between starting multi-process workers. Useful for reducing memory spike at working startup. @@ -550,7 +565,8 @@ def _process_multi_core(self, capacity_class, tie_line_voltage, Flag to return least cost paths as a multi-line geometry, by default False radius : None | int, optional - Force clipping radius if set to an int + Force clipping radius if set to an int. Radius will be + expanded to include at least one connection feature. mp_delay : float, optional Delay in seconds between starting multi-process workers. Useful for reducing memory spike at working startup. @@ -572,7 +588,7 @@ def _process_multi_core(self, capacity_class, tie_line_voltage, for _, sc_point in self.sc_points.iterrows(): gid = sc_point['sc_point_gid'] if gid in sc_point_gids: - sc_features, radius = self._clip_to_sc_point( + sc_features, sc_radius = self._clip_to_sc_point( sc_point, tie_line_voltage, nn_sinks=nn_sinks, clipping_buffer=clipping_buffer, radius=radius) if sc_features.empty: @@ -582,7 +598,7 @@ def _process_multi_core(self, capacity_class, tie_line_voltage, self._cost_fpath, sc_point.copy(deep=True), sc_features, capacity_class, - radius=radius, + radius=sc_radius, xmission_config=self._config, barrier_mult=barrier_mult, min_line_length=self._min_line_len, @@ -638,7 +654,8 @@ def _process_single_core(self, capacity_class, tie_line_voltage, Flag to return least cost paths as a multi-line geometry, by default False radius : None | int, optional - Force clipping radius if set to an int + Force clipping radius if set to an int. Radius will be + expanded to include at least one connection feature. simplify_geo : float | None, optional If float, simplify geometries using this value @@ -653,7 +670,7 @@ def _process_single_core(self, capacity_class, tie_line_voltage, for i, (_, sc_point) in enumerate(self.sc_points.iterrows(), start=1): gid = sc_point['sc_point_gid'] if gid in sc_point_gids: - sc_features, radius = self._clip_to_sc_point( + sc_features, sc_radius = self._clip_to_sc_point( sc_point, tie_line_voltage, nn_sinks=nn_sinks, clipping_buffer=clipping_buffer, radius=radius) if sc_features.empty: @@ -663,7 +680,7 @@ def _process_single_core(self, capacity_class, tie_line_voltage, self._cost_fpath, sc_point.copy(deep=True), sc_features, capacity_class, - radius=radius, + radius=sc_radius, xmission_config=self._config, barrier_mult=barrier_mult, min_line_length=self._min_line_len, @@ -722,7 +739,8 @@ def run(cls, cost_fpath, features_fpath, capacity_class, resolution=128, Flag to return least costs path as a multi-line geometry, by default False radius : None | int, optional - Force clipping radius if set to an int + Force clipping radius if set to an int. Radius will be + expanded to include at least one connection feature. simplify_geo : float | None, optional If float, simplify geometries using this value @@ -883,8 +901,12 @@ def run(cls, cost_fpath, features_fpath, balancing_areas_fpath, save_paths : bool, optional Flag to save reinforcement line path as a multi-line geometry. By default, ``False``. - simplify_geo : float | None, optional - If float, simplify geometries using this value. + radius : None | int, optional + Force clipping radius. Substations beyond this radius will + not be considered for connection with supply curve point. + Radius will be expanded to include at least one connection + feature. This value must be given in units of pixels + corresponding to the cost raster. Returns ------- From f091352f39ab7c8358f868911c93aa173b617f83 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 16 May 2023 14:53:10 -0600 Subject: [PATCH 13/17] Added logic for `allow_connections_within_states` --- .../least_cost_xmission.py | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/reVX/least_cost_xmission/least_cost_xmission.py b/reVX/least_cost_xmission/least_cost_xmission.py index a79d0b4df..d0363baa5 100644 --- a/reVX/least_cost_xmission/least_cost_xmission.py +++ b/reVX/least_cost_xmission/least_cost_xmission.py @@ -782,7 +782,8 @@ class ReinforcedXmission(LeastCostXmission): """ def __init__(self, cost_fpath, features_fpath, balancing_areas_fpath, - resolution=128, xmission_config=None, min_line_length=0): + resolution=128, xmission_config=None, min_line_length=0, + allow_connections_within_states=False): """ Parameters ---------- @@ -805,6 +806,10 @@ def __init__(self, cost_fpath, features_fpath, balancing_areas_fpath, By default, ``None``. min_line_length : int | float, optional Minimum line length in km. By default, ``0``. + allow_connections_within_states : bool, optional + Allow supply curve points to connect to substations outside + of their own BA, as long as all connections stay within the + same state. By default, ``False``. """ super().__init__(cost_fpath=cost_fpath, features_fpath=features_fpath, @@ -813,6 +818,7 @@ def __init__(self, cost_fpath, features_fpath, balancing_areas_fpath, min_line_length=min_line_length) self._ba = (gpd.read_file(balancing_areas_fpath) .to_crs(self.features.crs)) + self.allow_connections_within_states = allow_connections_within_states @staticmethod def _load_trans_feats(features_fpath): @@ -836,8 +842,23 @@ def _clip_to_sc_point(self, sc_point, tie_line_voltage, nn_sinks=2, point = self.sc_points.loc[sc_point.name:sc_point.name].centroid ba_str = point.apply(ba_mapper(self._ba)).values[0] - mask = self.features["ba_str"] == ba_str + if self.allow_connections_within_states: + state = self._ba[self._ba["ba_str"] == ba_str]["state"].values[0] + logger.debug(' - Clipping features to {!r}'.format(state)) + state_nodes = self._ba[self._ba["state"]== state] + allowed_bas = set(state_nodes["ba_str"]) + else: + allowed_bas = {ba_str} + logger.debug(" - Clipping features to allowed ba's: {}" + .format(allowed_bas)) + mask = self.features["ba_str"].isin(allowed_bas) sc_features = self.features.loc[mask].copy(deep=True) + logger.debug('{} transmission features found in clipped area ' + .format(len(sc_features))) + + if radius is not None: + sc_features = self._clip_to_radius(sc_point, radius, sc_features, + clipping_buffer) mask = self.features['max_volts'] >= tie_line_voltage sc_features = sc_features.loc[mask].copy(deep=True) @@ -857,8 +878,9 @@ def _clip_to_sc_point(self, sc_point, tie_line_voltage, nn_sinks=2, def run(cls, cost_fpath, features_fpath, balancing_areas_fpath, capacity_class, resolution=128, xmission_config=None, min_line_length=0, sc_point_gids=None, clipping_buffer=1.05, - barrier_mult=100, max_workers=None, save_paths=False, - simplify_geo=None): + barrier_mult=100, max_workers=None, simplify_geo=None, + allow_connections_within_states=False, save_paths=False, + radius=None): """ Find Least Cost Transmission connections between desired sc_points and substations in their balancing area. @@ -898,14 +920,20 @@ def run(cls, cost_fpath, features_fpath, balancing_areas_fpath, max_workers : int, optional Number of workers to use for processing. If 1 run in serial, if ``None`` use all available cores. By default, ``None``. + simplify_geo : float | None, optional + If float, simplify geometries using this value. + allow_connections_within_states : bool, optional + Allow supply curve points to connect to substations outside + of their own BA, as long as all connections stay within the + same state. By default, ``False``. save_paths : bool, optional Flag to save reinforcement line path as a multi-line geometry. By default, ``False``. radius : None | int, optional Force clipping radius. Substations beyond this radius will not be considered for connection with supply curve point. - Radius will be expanded to include at least one connection - feature. This value must be given in units of pixels + Radius will be expanded to include at least one connection + feature. This value must be given in units of pixels corresponding to the cost raster. Returns @@ -917,14 +945,15 @@ def run(cls, cost_fpath, features_fpath, balancing_areas_fpath, """ ts = time.time() lcx = cls(cost_fpath, features_fpath, balancing_areas_fpath, - resolution=resolution, xmission_config=xmission_config, - min_line_length=min_line_length) + resolution, xmission_config, min_line_length, + allow_connections_within_states) least_costs = lcx.process_sc_points(capacity_class, sc_point_gids=sc_point_gids, clipping_buffer=clipping_buffer, barrier_mult=barrier_mult, max_workers=max_workers, save_paths=save_paths, + radius=radius, simplify_geo=simplify_geo) logger.info('{} connections were made to {} SC points in {:.4f} ' From cc1d217435736b6aac7dcb0fd8bc497d86dec9a3 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Fri, 19 May 2023 10:37:53 -0600 Subject: [PATCH 14/17] Fix link to install instructions --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0fa419501..bda26fd43 100644 --- a/README.rst +++ b/README.rst @@ -57,7 +57,7 @@ as your package/environment manager. From your home directory ``/home/{user}/`` or another directory that you have permissions in, run the command ``git clone git@github.com:NREL/reVX.git`` and then go into your cloned repository: ``cd reVX`` #. Install reVX: - 1) Follow the installation commands installation process that we use for our automated test suite `here `_. Make sure that you call ``pip install -e .`` from within the cloned repository directory e.g. ``/home/{user}/reVX/`` + 1) Follow the installation commands installation process that we use for our automated test suite `here `_. Make sure that you call ``pip install -e .`` from within the cloned repository directory e.g. ``/home/{user}/reVX/`` - NOTE: If you install using pip and want to run `exclusion setbacks `_ you will need to install rtree manually: * ``conda install rtree`` From 8e157e9637905fda8f6d93a3ab8af6ea5414d772 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Fri, 19 May 2023 10:40:55 -0600 Subject: [PATCH 15/17] Added instructions for connections within states --- reVX/least_cost_xmission/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reVX/least_cost_xmission/README.md b/reVX/least_cost_xmission/README.md index 18861f839..6f95e44d4 100644 --- a/reVX/least_cost_xmission/README.md +++ b/reVX/least_cost_xmission/README.md @@ -107,6 +107,7 @@ In this methodology, total interconnection costs are comprised of two components - Each Balancing Area has exactly one "Network Node" (typically a city or highly populated area) - SC points **may only connect to substations** - Substations that a SC point connects to **must be in the same Balancing Area as the SC point** + - This assumption can be relaxed to allow connections within the same state. - Reinforcement costs are calculated based on the distance between the substation a SC point connected to and the Network Node in that Balancing Area - The path used to calculate reinforcement costs is traced along existing transmission lines **for as long as possible**. - The reinforcement cost is taken to be half (50%) of the total greenfield cost of the transmission line being traced. If a reinforcement path traces along multiple transmission lines, the corresponding greenfield costs are used for each segment. If multiple transmission lines are available in a single raster pixel, the cost for the highest-voltage line is used. Wherever there is no transmission line, a default greenfield cost assumption (specified by the user; typically 230 kV) is used. @@ -149,7 +150,7 @@ Next, compute the reinforcement paths on multiple nodes. Use the file below as a } ``` -Note that we are specifying ``"capacity_class": "400"`` to use the 230 kV (400MW capacity) greenfield costs for portions of the reinforcement paths that do no have existing transmission. If you would like to save the reinforcement path geometries, simply add `"save_paths": true` to the file, but note that this may increase your data product size significantly. +Note that we are specifying ``"capacity_class": "400"`` to use the 230 kV (400MW capacity) greenfield costs for portions of the reinforcement paths that do no have existing transmission. If you would like to save the reinforcement path geometries, simply add `"save_paths": true` to the file, but note that this may increase your data product size significantly. If you would like to allow substations to connect to endpoints within the same state, add `"allow_connections_within_states": true` to the file. After putting together your config file, simply call @@ -183,6 +184,7 @@ You should now have a file containing all of the reinforcement costs for the sub "name": "least_cost_transmission_1000MW" } ``` +If you would like to allow supply curve points to connect to substations within the same state, add `"allow_connections_within_states": true` to the file. Kickoff the execution using the following command: From 4f9697c8484cf831fcf8957876416e6565709259 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Fri, 19 May 2023 10:42:34 -0600 Subject: [PATCH 16/17] Linter fixes --- reVX/least_cost_xmission/least_cost_xmission.py | 2 +- tests/test_xmission_least_cost_paths.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/reVX/least_cost_xmission/least_cost_xmission.py b/reVX/least_cost_xmission/least_cost_xmission.py index d0363baa5..4f4c3ac1b 100644 --- a/reVX/least_cost_xmission/least_cost_xmission.py +++ b/reVX/least_cost_xmission/least_cost_xmission.py @@ -845,7 +845,7 @@ def _clip_to_sc_point(self, sc_point, tie_line_voltage, nn_sinks=2, if self.allow_connections_within_states: state = self._ba[self._ba["ba_str"] == ba_str]["state"].values[0] logger.debug(' - Clipping features to {!r}'.format(state)) - state_nodes = self._ba[self._ba["state"]== state] + state_nodes = self._ba[self._ba["state"] == state] allowed_bas = set(state_nodes["ba_str"]) else: allowed_bas = {ba_str} diff --git a/tests/test_xmission_least_cost_paths.py b/tests/test_xmission_least_cost_paths.py index 5a38aa0c0..bac44e844 100644 --- a/tests/test_xmission_least_cost_paths.py +++ b/tests/test_xmission_least_cost_paths.py @@ -255,12 +255,12 @@ def test_reinforcement_cli(runner, ba_regions_and_network_nodes, save_paths, assert len(test["reinforcement_poi_lat"].unique()) == 3 assert len(test["reinforcement_poi_lon"].unique()) == 3 assert np.isclose(test.reinforcement_cost_per_mw.max(), 225129.798, - atol=0.001) + atol=0.001) else: assert len(test["reinforcement_poi_lat"].unique()) == 4 assert len(test["reinforcement_poi_lon"].unique()) == 4 assert np.isclose(test.reinforcement_cost_per_mw.max(), 569757.740, - atol=0.001) + atol=0.001) LOGGERS.clear() From ace580544486d508b63f295615c6efff2caf417c Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Fri, 19 May 2023 10:50:18 -0600 Subject: [PATCH 17/17] Linter fix --- reVX/least_cost_xmission/least_cost_xmission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reVX/least_cost_xmission/least_cost_xmission.py b/reVX/least_cost_xmission/least_cost_xmission.py index 4f4c3ac1b..834ee3031 100644 --- a/reVX/least_cost_xmission/least_cost_xmission.py +++ b/reVX/least_cost_xmission/least_cost_xmission.py @@ -418,7 +418,7 @@ def _clip_to_radius(self, sc_point, radius, sc_features, clipping_buffer): expanded (multiplicatively by the clipping buffer) until at least one connection feature is found. """ - if radius is None or not len(sc_features): + if radius is None or len(sc_features) == 0: return sc_features # Get pixel resolution and calculate buffer