From a50736cdb9320883b3c388f0e63af9dbb9c91c05 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Sat, 25 May 2024 23:26:25 -0600 Subject: [PATCH 1/5] Add `lcoe_floored_reinforcement` as sorting option --- reV/supply_curve/supply_curve.py | 33 ++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/reV/supply_curve/supply_curve.py b/reV/supply_curve/supply_curve.py index 816cafe6d..56e0828d9 100644 --- a/reV/supply_curve/supply_curve.py +++ b/reV/supply_curve/supply_curve.py @@ -929,16 +929,32 @@ def compute_total_lcoe( cost *= self._trans_table[self._sc_capacity_col] cost /= self._trans_table[MetaKeyName.CAPACITY] # align with "mean_cf" + cf_mean_arr = self._trans_table[MetaKeyName.MEAN_CF].values + resource_lcoe = self._trans_table[MetaKeyName.MEAN_LCOE] + + if 'reinforcement_cost_floored_per_mw' in self._trans_table: + logger.info("'reinforcement_cost_floored_per_mw' column found in " + "transmission table. Adding floored reinforcement " + "cost LCOE as sorting option.") + fr_cost = (self._trans_table['reinforcement_cost_floored_per_mw'] + .values.copy()) + fr_cost *= self._trans_table[self._sc_capacity_col] + # align with "mean_cf" + fr_cost /= self._trans_table[MetaKeyName.CAPACITY] + + lcot_fr = ((cost + fr_cost) * fcr) / (cf_mean_arr * 8760) + lcoe_fr = lcot + resource_lcoe + self._trans_table['lcot_floored_reinforcement'] = lcot_fr + self._trans_table['lcoe_floored_reinforcement'] = lcoe_fr if 'reinforcement_cost_per_mw' in self._trans_table: logger.info("'reinforcement_cost_per_mw' column found in " "transmission table. Adding reinforcement costs " "to total LCOE.") - cf_mean_arr = self._trans_table[MetaKeyName.MEAN_CF].values - lcot = (cost * fcr) / (cf_mean_arr * 8760) - lcoe = lcot + self._trans_table[MetaKeyName.MEAN_LCOE] - self._trans_table['lcot_no_reinforcement'] = lcot - self._trans_table['lcoe_no_reinforcement'] = lcoe + lcot_nr = (cost * fcr) / (cf_mean_arr * 8760) + lcoe_nr = lcot + resource_lcoe + self._trans_table['lcot_no_reinforcement'] = lcot_nr + self._trans_table['lcoe_no_reinforcement'] = lcoe_nr r_cost = (self._trans_table['reinforcement_cost_per_mw'] .values.copy()) r_cost *= self._trans_table[self._sc_capacity_col] @@ -946,13 +962,10 @@ def compute_total_lcoe( r_cost /= self._trans_table[MetaKeyName.CAPACITY] cost += r_cost # $/MW - cf_mean_arr = self._trans_table[MetaKeyName.MEAN_CF].values lcot = (cost * fcr) / (cf_mean_arr * 8760) - self._trans_table['lcot'] = lcot - self._trans_table['total_lcoe'] = ( - self._trans_table['lcot'] - + self._trans_table[MetaKeyName.MEAN_LCOE]) + self._trans_table['total_lcoe'] = (self._trans_table['lcot'] + + resource_lcoe) if consider_friction: self._calculate_total_lcoe_friction() From d09fba46e4d543f78c4a775fc6fabe3c9556f50c Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 28 May 2024 16:57:58 -0600 Subject: [PATCH 2/5] Fix unbound error --- reV/supply_curve/supply_curve.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/reV/supply_curve/supply_curve.py b/reV/supply_curve/supply_curve.py index 56e0828d9..4d65e1da4 100644 --- a/reV/supply_curve/supply_curve.py +++ b/reV/supply_curve/supply_curve.py @@ -943,7 +943,7 @@ def compute_total_lcoe( fr_cost /= self._trans_table[MetaKeyName.CAPACITY] lcot_fr = ((cost + fr_cost) * fcr) / (cf_mean_arr * 8760) - lcoe_fr = lcot + resource_lcoe + lcoe_fr = lcot_fr + resource_lcoe self._trans_table['lcot_floored_reinforcement'] = lcot_fr self._trans_table['lcoe_floored_reinforcement'] = lcoe_fr @@ -952,7 +952,7 @@ def compute_total_lcoe( "transmission table. Adding reinforcement costs " "to total LCOE.") lcot_nr = (cost * fcr) / (cf_mean_arr * 8760) - lcoe_nr = lcot + resource_lcoe + lcoe_nr = lcot_nr + resource_lcoe self._trans_table['lcot_no_reinforcement'] = lcot_nr self._trans_table['lcoe_no_reinforcement'] = lcoe_nr r_cost = (self._trans_table['reinforcement_cost_per_mw'] @@ -964,8 +964,7 @@ def compute_total_lcoe( lcot = (cost * fcr) / (cf_mean_arr * 8760) self._trans_table['lcot'] = lcot - self._trans_table['total_lcoe'] = (self._trans_table['lcot'] - + resource_lcoe) + self._trans_table['total_lcoe'] = lcot + resource_lcoe if consider_friction: self._calculate_total_lcoe_friction() From a9ba6e9d11a0d1f9b2897f6c3ba7916fbccd2b4e Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 28 May 2024 16:58:23 -0600 Subject: [PATCH 3/5] Allow `trans_cap_cost_per_mw` to take priority --- reV/supply_curve/supply_curve.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reV/supply_curve/supply_curve.py b/reV/supply_curve/supply_curve.py index 4d65e1da4..b27851894 100644 --- a/reV/supply_curve/supply_curve.py +++ b/reV/supply_curve/supply_curve.py @@ -910,7 +910,9 @@ def compute_total_lcoe( Flag to consider friction layer on LCOE when "mean_lcoe_friction" is in the sc points input, by default True """ - if "trans_cap_cost" not in self._trans_table: + if "trans_cap_cost_per_mw" in self._trans_table: + cost = self._trans_table["trans_cap_cost_per_mw"] + elif "trans_cap_cost" not in self._trans_table: scc = self._sc_capacity_col cost = self._compute_trans_cap_cost( self._trans_table, From f6c9594c36ca67501882fa672085a15fd092e614 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Wed, 29 May 2024 10:53:57 -0600 Subject: [PATCH 4/5] Fix bug where costs were modified in place --- reV/supply_curve/supply_curve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reV/supply_curve/supply_curve.py b/reV/supply_curve/supply_curve.py index b27851894..c875c09be 100644 --- a/reV/supply_curve/supply_curve.py +++ b/reV/supply_curve/supply_curve.py @@ -911,7 +911,7 @@ def compute_total_lcoe( is in the sc points input, by default True """ if "trans_cap_cost_per_mw" in self._trans_table: - cost = self._trans_table["trans_cap_cost_per_mw"] + cost = self._trans_table["trans_cap_cost_per_mw"].values.copy() elif "trans_cap_cost" not in self._trans_table: scc = self._sc_capacity_col cost = self._compute_trans_cap_cost( From 2b8c2a14a5cd4fec7626fb3637bf1b0c82f78862 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Fri, 31 May 2024 11:21:21 -0600 Subject: [PATCH 5/5] Add tests for new floored reinforcement costs --- tests/test_supply_curve_compute.py | 95 ++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/tests/test_supply_curve_compute.py b/tests/test_supply_curve_compute.py index eeed68126..af80190d7 100644 --- a/tests/test_supply_curve_compute.py +++ b/tests/test_supply_curve_compute.py @@ -578,6 +578,101 @@ def test_least_cost_simple_with_reinforcement(): sc_simple_r.total_lcoe) +# pylint: disable=no-member +@pytest.mark.parametrize("r_costs", [True, False]) +def test_least_cost_simple_with_trans_cap_cost_per_mw(r_costs): + """ + Test simple supply curve with only "trans_cap_cost_per_mw" entry + """ + + with tempfile.TemporaryDirectory() as td: + trans_tables = [] + for cap in [100, 200, 400, 1000]: + in_table = os.path.join( + TESTDATADIR, "trans_tables", f"costs_RI_{cap}MW.csv" + ) + in_table = pd.read_csv(in_table) + out_fp = os.path.join(td, f"costs_RI_{cap}MW.csv") + t_gids = in_table["trans_gid"].values + if r_costs: + sort_on = "lcoe_no_reinforcement" + in_table["reinforcement_cost_per_mw"] = t_gids[::-1] + else: + sort_on = "total_lcoe" + in_table["reinforcement_cost_per_mw"] = 0 + in_table["reinforcement_dist_km"] = 0 + in_table["trans_cap_cost_per_mw"] = t_gids + in_table = in_table.drop(columns=["trans_cap_cost", "max_cap"]) + in_table.to_csv(out_fp, index=False) + trans_tables.append(out_fp) + + out_fpath = os.path.join(td, "sc") + sc = SupplyCurve(SC_POINTS, trans_tables) + sc_simple = sc.run(out_fpath, fixed_charge_rate=0.1, + simple=True, sort_on=sort_on) + sc_simple = pd.read_csv(sc_simple) + assert (sc_simple["trans_gid"] == 42445).all() + + if not r_costs: + lcot = 4244.5 / (sc_simple[SupplyCurveField.MEAN_CF] * 8760) + assert np.allclose(lcot, sc_simple["lcot"], atol=0.001) + + +# pylint: disable=no-member +def test_least_cost_simple_with_reinforcement_floor(): + """ + Test simple supply curve sorting with reinforcement costs in the + least-cost path transmission tables + """ + + with tempfile.TemporaryDirectory() as td: + trans_tables = [] + for cap in [100, 200, 400, 1000]: + in_table = os.path.join( + TESTDATADIR, "trans_tables", f"costs_RI_{cap}MW.csv" + ) + in_table = pd.read_csv(in_table) + out_fp = os.path.join(td, f"costs_RI_{cap}MW.csv") + in_table["reinforcement_cost_per_mw"] = 0 + in_table["reinforcement_dist_km"] = 0 + in_table["reinforcement_cost_floored_per_mw"] = 0 + in_table.to_csv(out_fp, index=False) + trans_tables.append(out_fp) + + out_fpath = os.path.join(td, "sc") + sc = SupplyCurve(SC_POINTS, trans_tables) + sc_simple = sc.run(out_fpath, fixed_charge_rate=0.1, + simple=True) + sc_simple = pd.read_csv(sc_simple) + + fpath_baseline = os.path.join(TESTDATADIR, + 'sc_out/sc_simple_lc.csv') + baseline_verify(sc_simple, fpath_baseline) + verify_trans_cap(sc_simple, trans_tables) + + trans_tables = [] + for cap in [100, 200, 400, 1000]: + in_table = os.path.join( + TESTDATADIR, "trans_tables", f"costs_RI_{cap}MW.csv" + ) + in_table = pd.read_csv(in_table) + out_fp = os.path.join(td, f"costs_RI_{cap}MW.csv") + in_table["reinforcement_cost_per_mw"] = 0 + in_table["reinforcement_dist_km"] = 0 + in_table["reinforcement_cost_floored_per_mw"] = 2000 + in_table.to_csv(out_fp, index=False) + trans_tables.append(out_fp) + + out_fpath = os.path.join(td, "sc_r") + sc = SupplyCurve(SC_POINTS, trans_tables) + sc_simple_r = sc.run(out_fpath, fixed_charge_rate=0.1, simple=True, + sort_on="lcot_floored_reinforcement") + sc_simple_r = pd.read_csv(sc_simple_r) + + baseline_verify(sc_simple, fpath_baseline) + verify_trans_cap(sc_simple, trans_tables) + + def test_least_cost_full_pass_through(): """ Test the full supply curve sorting passes through variables correctly