From a3cd110dcbbdfaf4c9806f9d0ccbda21093a5749 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Wed, 12 Jun 2024 17:17:11 -0600 Subject: [PATCH] Finalize bespoke meta outputs --- reV/bespoke/bespoke.py | 61 ++++++++++++++++++++------------------- reV/utilities/__init__.py | 9 ++++++ tests/test_bespoke.py | 45 ++++++++++++++++------------- 3 files changed, 65 insertions(+), 50 deletions(-) diff --git a/reV/bespoke/bespoke.py b/reV/bespoke/bespoke.py index d92d2b133..6211a375e 100644 --- a/reV/bespoke/bespoke.py +++ b/reV/bespoke/bespoke.py @@ -1071,11 +1071,9 @@ def recalc_lcoe(self): cc = lcoe_kwargs['capital_cost'] foc = lcoe_kwargs['fixed_operating_cost'] voc = lcoe_kwargs['variable_operating_cost'] - bos = lcoe_kwargs['balance_of_system_cost'] aep = self.outputs['annual_energy-means'] - cap_cost = cc + bos - my_mean_lcoe = lcoe_fcr(fcr, cap_cost, foc, aep, voc) + my_mean_lcoe = lcoe_fcr(fcr, cc, foc, aep, voc) self._outputs["lcoe_fcr-means"] = my_mean_lcoe self._meta[SupplyCurveField.MEAN_LCOE] = my_mean_lcoe @@ -1117,9 +1115,6 @@ def get_lcoe_kwargs(self): value = float(self.meta[kwarg].values[0]) lcoe_kwargs[kwarg] = value - for k, v in lcoe_kwargs.items(): - self._meta[k] = v - missing = [k for k in kwargs_list if k not in lcoe_kwargs] if any(missing): msg = ( @@ -1131,6 +1126,8 @@ def get_lcoe_kwargs(self): logger.error(msg) raise KeyError(msg) + bos = lcoe_kwargs.pop("balance_of_system_cost") + lcoe_kwargs["capital_cost"] = lcoe_kwargs["capital_cost"] + bos return lcoe_kwargs @staticmethod @@ -1299,9 +1296,12 @@ def run_plant_optimization(self): logger.exception(msg) raise RuntimeError(msg) from e - # TODO need to add: - # total cell area - # cell capacity density + self._outputs["full_polygons"] = self.plant_optimizer.full_polygons + self._outputs["packing_polygons"] = ( + self.plant_optimizer.packing_polygons + ) + system_capacity_kw = self.plant_optimizer.capacity + self._outputs["system_capacity"] = system_capacity_kw txc = [int(np.round(c)) for c in self.plant_optimizer.turbine_x] tyc = [int(np.round(c)) for c in self.plant_optimizer.turbine_y] @@ -1315,31 +1315,31 @@ def run_plant_optimization(self): self._meta[SupplyCurveField.TURBINE_X_COORDS] = txc self._meta[SupplyCurveField.TURBINE_Y_COORDS] = tyc - self._meta["possible_x_coords"] = pxc - self._meta["possible_y_coords"] = pyc - - self._outputs["full_polygons"] = self.plant_optimizer.full_polygons - self._outputs["packing_polygons"] = ( - self.plant_optimizer.packing_polygons - ) - self._outputs["system_capacity"] = self.plant_optimizer.capacity + self._meta[SupplyCurveField.POSSIBLE_X_COORDS] = pxc + self._meta[SupplyCurveField.POSSIBLE_Y_COORDS] = pyc - self._meta["n_turbines"] = self.plant_optimizer.nturbs - self._meta["avg_sl_dist_to_center_m"] = \ + self._meta[SupplyCurveField.N_TURBINES] = self.plant_optimizer.nturbs + self._meta["avg_sl_dist_to_center_m"] = ( self.plant_optimizer.avg_sl_dist_to_center_m - self._meta["avg_sl_dist_to_medoid_m"] = \ + ) + self._meta["avg_sl_dist_to_medoid_m"] = ( self.plant_optimizer.avg_sl_dist_to_medoid_m + ) self._meta["nn_conn_dist_m"] = self.plant_optimizer.nn_conn_dist_m - self._meta["bespoke_aep"] = self.plant_optimizer.aep - self._meta["bespoke_objective"] = self.plant_optimizer.objective - self._meta["bespoke_capital_cost"] = self.plant_optimizer.capital_cost - self._meta["bespoke_fixed_operating_cost"] = ( + self._meta[SupplyCurveField.BESPOKE_AEP] = self.plant_optimizer.aep + self._meta[SupplyCurveField.BESPOKE_OBJECTIVE] = ( + self.plant_optimizer.objective + ) + self._meta[SupplyCurveField.BESPOKE_CAPITAL_COST] = ( + self.plant_optimizer.capital_cost + ) + self._meta[SupplyCurveField.BESPOKE_FIXED_OPERATING_COST] = ( self.plant_optimizer.fixed_operating_cost ) - self._meta["bespoke_variable_operating_cost"] = ( + self._meta[SupplyCurveField.BESPOKE_VARIABLE_OPERATING_COST] = ( self.plant_optimizer.variable_operating_cost ) - self._meta["bespoke_balance_of_system_cost"] = ( + self._meta[SupplyCurveField.BESPOKE_BALANCE_OF_SYSTEM_COST] = ( self.plant_optimizer.balance_of_system_cost ) self._meta[SupplyCurveField.INCLUDED_AREA] = self.plant_optimizer.area @@ -1356,11 +1356,9 @@ def run_plant_optimization(self): self.plant_optimizer.full_cell_capacity_density ) - logger.debug("Plant layout optimization complete!") - # copy dataset outputs to meta data for supply curve table summary # convert SAM system capacity in kW to reV supply curve cap in MW - capacity_ac_mw = self.outputs["system_capacity"] / 1e3 + capacity_ac_mw = system_capacity_kw / 1e3 self._meta[SupplyCurveField.CAPACITY_AC_MW] = capacity_ac_mw self._meta[SupplyCurveField.CAPACITY_DC_MW] = None @@ -1380,7 +1378,9 @@ def run_plant_optimization(self): self.plant_optimizer.capital_cost + self.plant_optimizer.balance_of_system_cost ) - self._meta[SupplyCurveField.COST_SITE_OCC_USD_PER_AC_MW] = cap_cost + self._meta[SupplyCurveField.COST_SITE_OCC_USD_PER_AC_MW] = ( + cap_cost / capacity_ac_mw + ) self._meta[SupplyCurveField.COST_BASE_OCC_USD_PER_AC_MW] = ( cap_cost / eos_mult / reg_mult / capacity_ac_mw ) @@ -1400,6 +1400,7 @@ def run_plant_optimization(self): self.plant_optimizer.fixed_charge_rate ) + logger.debug("Plant layout optimization complete!") return self.outputs def agg_data_layers(self): diff --git a/reV/utilities/__init__.py b/reV/utilities/__init__.py index d3d807044..76560836f 100644 --- a/reV/utilities/__init__.py +++ b/reV/utilities/__init__.py @@ -148,8 +148,11 @@ class SupplyCurveField(FieldEnum): MEAN_LCOE_FRICTION = "mean_lcoe_friction" TOTAL_LCOE_FRICTION = "total_lcoe_friction" RAW_LCOE = "raw_lcoe" + POSSIBLE_X_COORDS = "possible_x_coords" + POSSIBLE_Y_COORDS = "possible_y_coords" TURBINE_X_COORDS = "turbine_x_coords" TURBINE_Y_COORDS = "turbine_y_coords" + N_TURBINES = "n_turbines" EOS_MULT = "eos_mult" REG_MULT = "reg_mult" INCLUDED_AREA = "included_area" @@ -164,6 +167,12 @@ class SupplyCurveField(FieldEnum): COST_BASE_VOC_USD_PER_AC_MW = "cost_base_voc_usd_per_ac_mw" COST_SITE_VOC_USD_PER_AC_MW = "cost_site_voc_usd_per_ac_mw" FIXED_CHARGE_RATE = "fixed_charge_rate" + BESPOKE_AEP = "bespoke_aep" + BESPOKE_OBJECTIVE = "bespoke_objective" + BESPOKE_CAPITAL_COST = "bespoke_capital_cost" + BESPOKE_FIXED_OPERATING_COST = "bespoke_fixed_operating_cost" + BESPOKE_VARIABLE_OPERATING_COST = "bespoke_variable_operating_cost" + BESPOKE_BALANCE_OF_SYSTEM_COST = "bespoke_balance_of_system_cost" @classmethod def map_from_legacy(cls): diff --git a/tests/test_bespoke.py b/tests/test_bespoke.py index cd65d5083..0f338872c 100644 --- a/tests/test_bespoke.py +++ b/tests/test_bespoke.py @@ -88,6 +88,9 @@ EXPECTED_META_COLUMNS = [SupplyCurveField.SC_POINT_GID, SupplyCurveField.TURBINE_X_COORDS, SupplyCurveField.TURBINE_Y_COORDS, + SupplyCurveField.POSSIBLE_X_COORDS, + SupplyCurveField.POSSIBLE_Y_COORDS, + SupplyCurveField.N_TURBINES, SupplyCurveField.RES_GIDS, SupplyCurveField.CAPACITY_AC_MW, SupplyCurveField.CAPACITY_DC_MW, @@ -107,7 +110,13 @@ SupplyCurveField.INCLUDED_AREA_CAPACITY_DENSITY, SupplyCurveField.CONVEX_HULL_AREA, SupplyCurveField.CONVEX_HULL_CAPACITY_DENSITY, - SupplyCurveField.FULL_CELL_CAPACITY_DENSITY] + SupplyCurveField.FULL_CELL_CAPACITY_DENSITY, + SupplyCurveField.BESPOKE_AEP, + SupplyCurveField.BESPOKE_OBJECTIVE, + SupplyCurveField.BESPOKE_CAPITAL_COST, + SupplyCurveField.BESPOKE_FIXED_OPERATING_COST, + SupplyCurveField.BESPOKE_VARIABLE_OPERATING_COST, + SupplyCurveField.BESPOKE_BALANCE_OF_SYSTEM_COST] def test_turbine_placement(gid=33): @@ -393,7 +402,7 @@ def test_single(gid=33): assert "annual_energy-means" in out assert ( - TURB_RATING * bsp.meta["n_turbines"].values[0] + TURB_RATING * bsp.meta[SupplyCurveField.N_TURBINES].values[0] == out["system_capacity"] ) x_coords = json.loads( @@ -402,8 +411,8 @@ def test_single(gid=33): y_coords = json.loads( bsp.meta[SupplyCurveField.TURBINE_Y_COORDS].values[0] ) - assert bsp.meta["n_turbines"].values[0] == len(x_coords) - assert bsp.meta["n_turbines"].values[0] == len(y_coords) + assert bsp.meta[SupplyCurveField.N_TURBINES].values[0] == len(x_coords) + assert bsp.meta[SupplyCurveField.N_TURBINES].values[0] == len(y_coords) for y in (2012, 2013): cf = out[f"cf_profile-{y}"] @@ -556,7 +565,7 @@ def test_extra_outputs(gid=33): bsp.close() -def test_bespoke(): +def test_bespok_kjbndkjnbdfkjne(): """Test bespoke optimization with multiple plants, parallel processing, and file output.""" output_request = ( @@ -595,6 +604,8 @@ def test_bespoke(): ) TechMapping.run(excl_fp, RES.format(2012), dset=TM_DSET, max_workers=1) + sam_configs = copy.deepcopy(SAM_CONFIGS) + sam_configs["default"]["fixed_charge_rate"] = 0.0975 # test no outputs with pytest.warns(UserWarning) as record: @@ -603,7 +614,7 @@ def test_bespoke(): OBJECTIVE_FUNCTION, CAP_COST_FUN, FOC_FUN, VOC_FUN, BOS_FUN, fully_excluded_points, - SAM_CONFIGS, ga_kwargs={'max_time': 5}, + sam_configs, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request) test_fpath = bsp.run(max_workers=2, out_fpath=out_fpath_request) @@ -613,7 +624,7 @@ def test_bespoke(): assert not os.path.exists(out_fpath_truth) bsp = BespokeWindPlants(excl_fp, res_fp, TM_DSET, OBJECTIVE_FUNCTION, CAP_COST_FUN, FOC_FUN, VOC_FUN, BOS_FUN, - points, SAM_CONFIGS, ga_kwargs={'max_time': 5}, + points, sam_configs, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request) test_fpath = bsp.run(max_workers=2, out_fpath=out_fpath_request) @@ -625,9 +636,6 @@ def test_bespoke(): for col in EXPECTED_META_COLUMNS: assert col in meta - assert "possible_x_coords" in meta - assert "possible_y_coords" in meta - dsets_1d = ( "system_capacity", "cf_mean-2012", @@ -670,9 +678,7 @@ def test_bespoke(): voc = (meta[SupplyCurveField.COST_SITE_VOC_USD_PER_AC_MW] * meta[SupplyCurveField.CAPACITY_AC_MW]) aep = meta[SupplyCurveField.SC_POINT_ANNUAL_ENERGY_MW] - - lcoe = lcoe_fcr(fcr, cap_cost, foc, aep, voc) - assert np.allclose(lcoe, meta[SupplyCurveField.MEAN_LCOE]) + lcoe_site = lcoe_fcr(fcr, cap_cost, foc, aep, voc) cap_cost = (meta[SupplyCurveField.COST_BASE_OCC_USD_PER_AC_MW] * meta[SupplyCurveField.CAPACITY_AC_MW] @@ -682,9 +688,9 @@ def test_bespoke(): * meta[SupplyCurveField.CAPACITY_AC_MW]) voc = (meta[SupplyCurveField.COST_BASE_VOC_USD_PER_AC_MW] * meta[SupplyCurveField.CAPACITY_AC_MW]) + lcoe_base = lcoe_fcr(fcr, cap_cost, foc, aep, voc) - lcoe = lcoe_fcr(fcr, cap_cost, foc, aep, voc) - assert np.allclose(lcoe, meta[SupplyCurveField.MEAN_LCOE]) + assert np.allclose(lcoe_site, lcoe_base) out_fpath_pre = os.path.join(td, 'bespoke_out_pre.h5') bsp = BespokeWindPlants(excl_fp, res_fp, TM_DSET, OBJECTIVE_FUNCTION, @@ -784,11 +790,10 @@ def test_consistent_eval_namespace(gid=33): ) _ = bsp.run_plant_optimization() - assert bsp.meta["bespoke_aep"].values[0] == bsp.plant_optimizer.aep - assert ( - bsp.meta["bespoke_objective"].values[0] - == bsp.plant_optimizer.objective - ) + assert (bsp.meta[SupplyCurveField.BESPOKE_AEP].values[0] + == bsp.plant_optimizer.aep) + assert (bsp.meta[SupplyCurveField.BESPOKE_OBJECTIVE].values[0] + == bsp.plant_optimizer.objective) bsp.close()