From cb995bdcf4cb6fea66aee7d33d03615c0e53e8b4 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 29 Apr 2024 17:40:04 -0600 Subject: [PATCH 01/11] Add `avg_sl_dist_to_center_m` option for bespoke objective --- reV/bespoke/place_turbines.py | 88 ++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/reV/bespoke/place_turbines.py b/reV/bespoke/place_turbines.py index fd6bc84cb..c1370b492 100644 --- a/reV/bespoke/place_turbines.py +++ b/reV/bespoke/place_turbines.py @@ -55,45 +55,53 @@ def __init__(self, wind_plant, objective_function, Parameters ---------- wind_plant : WindPowerPD - wind plant object to analyze wind plant performance. This object - should have everything in the plant defined, such that only the - turbine coordinates and plant capacity need to be defined during - the optimization. + wind plant object to analyze wind plant performance. This + object should have everything in the plant defined, such + that only the turbine coordinates and plant capacity need to + be defined during the optimization. objective_function : str - The objective function of the optimization as a string, should - return the objective to be minimized during layout optimization. - Variables available are: + The objective function of the optimization as a string, + should return the objective to be minimized during layout + optimization. Variables available are: - n_turbines: the number of turbines - system_capacity: wind plant capacity - aep: annual energy production - - fixed_charge_rate: user input fixed_charge_rate if included - as part of the sam system config. + - avg_sl_dist_to_center_m: Average straight-line + distance to the supply curve point center from all + turbine locations (in m). Useful for computing plant + BOS costs. + - fixed_charge_rate: user input fixed_charge_rate if + included as part of the sam system config. - capital_cost: plant capital cost as evaluated by `capital_cost_function` - - fixed_operating_cost: plant fixed annual operating cost as - evaluated by `fixed_operating_cost_function` - - variable_operating_cost: plant variable annual operating cost - as evaluated by `variable_operating_cost_function` - - self.wind_plant: the SAM wind plant object, through which - all SAM variables can be accessed - - cost: the annual cost of the wind plant (from cost_function) + - fixed_operating_cost: plant fixed annual operating + cost as evaluated by `fixed_operating_cost_function` + - variable_operating_cost: plant variable annual + operating cost as evaluated by + `variable_operating_cost_function` + - self.wind_plant: the SAM wind plant object, through + which all SAM variables can be accessed + - cost: the annual cost of the wind plant (from + cost_function) capital_cost_function : str - The plant capital cost function as a string, must return the total - capital cost in $. Has access to the same variables as the - objective_function. + The plant capital cost function as a string, must return the + total capital cost in $. Has access to the same variables as + the objective_function. fixed_operating_cost_function : str - The plant annual fixed operating cost function as a string, must - return the fixed operating cost in $/year. Has access to the same - variables as the objective_function. + The plant annual fixed operating cost function as a string, + must return the fixed operating cost in $/year. Has access + to the same variables as the objective_function. variable_operating_cost_function : str - The plant annual variable operating cost function as a string, must - return the variable operating cost in $/kWh. Has access to the same - variables as the objective_function. - exclusions : ExclusionMaskFromDict - The exclusions that define where turbines can be placed. Contains - exclusions.latitude, exclusions.longitude, and exclusions.mask + The plant annual variable operating cost function as a + string, must return the variable operating cost in $/kWh. + Has access to the same variables as the objective_function. + include_mask : np.ndarray + Supply curve point 2D inclusion mask where included pixels + are set to 1 and excluded pixels are set to 0. + pixel_side_length : int + Side length (m) of a single pixel of the `include_mask`. min_spacing : float The minimum spacing between turbines (in meters). wake_loss_multiplier : float, optional @@ -148,7 +156,7 @@ def define_exclusions(self): """From the exclusions data, create a shapely MultiPolygon as self.safe_polygons that defines where turbines can be placed. """ - nx, ny = np.shape(self.include_mask) + ny, nx = np.shape(self.include_mask) self.safe_polygons = MultiPolygon() side_x = np.arange(nx + 1) * self.pixel_side_length side_y = np.arange(ny, -1, -1) * self.pixel_side_length @@ -211,6 +219,13 @@ def initialize_packing(self): self.x_locations = packing.turbine_x self.y_locations = packing.turbine_y + def _avg_sl_dist_to_cent(self, x_locs, y_locs): + """Average straight-line distance to center from turb locations. """ + ny, nx = np.shape(self.include_mask) + cx = nx * self.pixel_side_length / 2 + cy = ny * self.pixel_side_length / 2 + return np.hypot(x_locs - cx, y_locs - cy).mean() + # pylint: disable=W0641,W0123 def optimization_objective(self, x): """The optimization objective used in the bespoke optimization @@ -218,8 +233,9 @@ def optimization_objective(self, x): x = [bool(y) for y in x] if len(x) > 0: n_turbines = np.sum(x) - self.wind_plant["wind_farm_xCoordinates"] = self.x_locations[x] - self.wind_plant["wind_farm_yCoordinates"] = self.y_locations[x] + x_locs, y_locs = self.x_locations[x], self.y_locations[x] + self.wind_plant["wind_farm_xCoordinates"] = x_locs + self.wind_plant["wind_farm_yCoordinates"] = y_locs system_capacity = n_turbines * self.turbine_capacity self.wind_plant["system_capacity"] = system_capacity @@ -227,8 +243,9 @@ def optimization_objective(self, x): self.wind_plant.assign_inputs() self.wind_plant.execute() aep = self._aep_after_scaled_wake_losses() + avg_sl_dist_to_center_m = self._avg_sl_dist_to_cent(x_locs, y_locs) else: - n_turbines = system_capacity = aep = 0 + n_turbines = system_capacity = aep = avg_sl_dist_to_center_m = 0 fixed_charge_rate = self.fixed_charge_rate capital_cost = eval(self.capital_cost_function, @@ -378,6 +395,12 @@ def turbine_y(self): """This is the final optimized turbine y locations (m)""" return self.y_locations[self.optimized_design_variables] + @property + @none_until_optimized + def avg_sl_dist_to_center_m(self): + """This is the final avg straight line distance to center (m)""" + return self._avg_sl_dist_to_cent(self.turbine_x, self.turbine_y) + @property @none_until_optimized def nturbs(self): @@ -522,4 +545,5 @@ def objective(self): capital_cost = self.capital_cost fixed_operating_cost = self.fixed_operating_cost variable_operating_cost = self.variable_operating_cost + avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m return eval(self.objective_function, globals(), locals()) From 671177064b402f8990dd1a28c31bf8e17001855e Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Wed, 1 May 2024 17:40:41 -0600 Subject: [PATCH 02/11] Add `balance_of_system_cost_function` as input --- reV/bespoke/bespoke.py | 107 ++++++++++++++++++++++++++-------- reV/bespoke/cli_bespoke.py | 2 + reV/bespoke/place_turbines.py | 84 +++++++++++++++++++++----- 3 files changed, 154 insertions(+), 39 deletions(-) diff --git a/reV/bespoke/bespoke.py b/reV/bespoke/bespoke.py index e88a9db58..4a8eab686 100644 --- a/reV/bespoke/bespoke.py +++ b/reV/bespoke/bespoke.py @@ -197,6 +197,7 @@ def __init__(self, gid, excl, res, tm_dset, sam_sys_inputs, objective_function, capital_cost_function, fixed_operating_cost_function, variable_operating_cost_function, + balance_of_system_cost_function, min_spacing='5x', wake_loss_multiplier=1, ga_kwargs=None, output_request=('system_capacity', 'cf_mean'), ws_bins=(0.0, 20.0, 5.0), wd_bins=(0.0, 360.0, 45.0), @@ -225,19 +226,29 @@ def __init__(self, gid, excl, res, tm_dset, sam_sys_inputs, return the objective to be minimized during layout optimization. Variables available are: - - n_turbines: the number of turbines - - system_capacity: wind plant capacity - - aep: annual energy production - - fixed_charge_rate: user input fixed_charge_rate if included - as part of the sam system config. - - self.wind_plant: the SAM wind plant object, through which - all SAM variables can be accessed - - capital_cost: plant capital cost as evaluated + - ``n_turbines``: the number of turbines + - ``system_capacity``: wind plant capacity + - ``aep``: annual energy production + - ``avg_sl_dist_to_center_m``: Average straight-line + distance to the supply curve point center from all + turbine locations (in m). Useful for computing plant + BOS costs. + - ``avg_sl_dist_to_medoid_m``: Average straight-line + distance to the medoid of all turbine locations + (in m). Useful for computing plant BOS costs. + - ``fixed_charge_rate``: user input fixed_charge_rate if + included as part of the sam system config. + - ``capital_cost``: plant capital cost as evaluated by `capital_cost_function` - - fixed_operating_cost: plant fixed annual operating cost as - evaluated by `fixed_operating_cost_function` - - variable_operating_cost: plant variable annual operating cost - as evaluated by `variable_operating_cost_function` + - ``fixed_operating_cost``: plant fixed annual operating + cost as evaluated by `fixed_operating_cost_function` + - ``variable_operating_cost``: plant variable annual + operating cost as evaluated by + `variable_operating_cost_function` + - ``balance_of_system_cost``: plant balance of system + cost as evaluated by `balance_of_system_cost_function` + - ``self.wind_plant``: the SAM wind plant object, + through which all SAM variables can be accessed capital_cost_function : str The plant capital cost function as a string, must return the total @@ -250,6 +261,16 @@ def __init__(self, gid, excl, res, tm_dset, sam_sys_inputs, variable_operating_cost_function : str The plant annual variable operating cost function as a string, must return the variable operating cost in $/kWh. Has access to the same + variables as the objective_function. You can set this to "0" + to effectively ignore variable operating costs. + balance_of_system_cost_function : str + The plant balance-of-system cost function as a string, must + return the variable operating cost in $. Has access to the + same variables as the objective_function. You can set this + to "0" to effectively ignore balance-of-system costs. + balance_of_system_cost_function : str + The plant balance-of-system cost function as a string, must + return the variable operating cost in $. Has access to the same variables as the objective_function. min_spacing : float | int | str Minimum spacing between turbines in meters. Can also be a string @@ -384,6 +405,7 @@ def __init__(self, gid, excl, res, tm_dset, sam_sys_inputs, self.fixed_operating_cost_function = fixed_operating_cost_function self.variable_operating_cost_function = \ variable_operating_cost_function + self.balance_of_system_cost_function = balance_of_system_cost_function self.min_spacing = min_spacing self.wake_loss_multiplier = wake_loss_multiplier self.ga_kwargs = ga_kwargs or {} @@ -935,6 +957,7 @@ def plant_optimizer(self): self.capital_cost_function, self.fixed_operating_cost_function, self.variable_operating_cost_function, + self.balance_of_system_cost_function, self.include_mask, self.pixel_side_length, self.min_spacing, @@ -953,10 +976,12 @@ def recalc_lcoe(self): 'multi-year mean AEP.') fcr = lcoe_kwargs['fixed_charge_rate'] - cap_cost = lcoe_kwargs['capital_cost'] + 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) @@ -974,13 +999,14 @@ def get_lcoe_kwargs(self): sam_sys_inputs, normalized to the original system_capacity, and updated based on the bespoke optimized system_capacity, includes fixed_charge_rate, system_capacity (kW), capital_cost ($), - fixed_operating_cos ($), variable_operating_cost ($/kWh) - Data source priority: outputs, plant_optimizer, - original_sam_sys_inputs, meta + fixed_operating_cos ($), variable_operating_cost ($/kWh), + balance_of_system_cost ($). Data source priority: outputs, + plant_optimizer, original_sam_sys_inputs, meta """ kwargs_list = ['fixed_charge_rate', 'system_capacity', 'capital_cost', - 'fixed_operating_cost', 'variable_operating_cost'] + 'fixed_operating_cost', 'variable_operating_cost', + 'balance_of_system_cost'] lcoe_kwargs = {} for kwarg in kwargs_list: @@ -1056,7 +1082,8 @@ def _check_sys_inputs(plant1, plant2, 'hourly', 'capital_cost', 'fixed_operating_cost', - 'variable_operating_cost')): + 'variable_operating_cost', + 'balance_of_system_cost')): """Check two reV-SAM models for matching system inputs. Parameters @@ -1173,7 +1200,13 @@ def run_plant_optimization(self): self._outputs["system_capacity"] = self.plant_optimizer.capacity self._meta["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.plant_optimizer.avg_sl_dist_to_medoid_m self._meta["bespoke_aep"] = self.plant_optimizer.aep + self._meta["bespoke_avg_sl_dist_to_center_m"] = \ + self.plant_optimizer.avg_sl_dist_to_center_m self._meta["bespoke_objective"] = self.plant_optimizer.objective self._meta["bespoke_capital_cost"] = \ self.plant_optimizer.capital_cost @@ -1181,6 +1214,8 @@ def run_plant_optimization(self): self.plant_optimizer.fixed_operating_cost self._meta["bespoke_variable_operating_cost"] = \ self.plant_optimizer.variable_operating_cost + self._meta["bespoke_balance_of_system_cost"] = \ + self.plant_optimizer.balance_of_system_cost self._meta["included_area"] = self.plant_optimizer.area self._meta["included_area_capacity_density"] = \ self.plant_optimizer.capacity_density @@ -1269,7 +1304,8 @@ class BespokeWindPlants(BaseAggregation): def __init__(self, excl_fpath, res_fpath, tm_dset, objective_function, capital_cost_function, fixed_operating_cost_function, - variable_operating_cost_function, project_points, + variable_operating_cost_function, + balance_of_system_cost_function, project_points, sam_files, min_spacing='5x', wake_loss_multiplier=1, ga_kwargs=None, output_request=('system_capacity', 'cf_mean'), ws_bins=(0.0, 20.0, 5.0), wd_bins=(0.0, 360.0, 45.0), @@ -1349,17 +1385,26 @@ def __init__(self, excl_fpath, res_fpath, tm_dset, objective_function, - ``n_turbines``: the number of turbines - ``system_capacity``: wind plant capacity - ``aep``: annual energy production + - ``avg_sl_dist_to_center_m``: Average straight-line + distance to the supply curve point center from all + turbine locations (in m). Useful for computing plant + BOS costs. + - ``avg_sl_dist_to_medoid_m``: Average straight-line + distance to the medoid of all turbine locations + (in m). Useful for computing plant BOS costs. - ``fixed_charge_rate``: user input fixed_charge_rate if included as part of the sam system config. - - ``self.wind_plant``: the SAM wind plant object, - through which all SAM variables can be accessed - ``capital_cost``: plant capital cost as evaluated by `capital_cost_function` - ``fixed_operating_cost``: plant fixed annual operating cost as evaluated by `fixed_operating_cost_function` - ``variable_operating_cost``: plant variable annual - operating cost, as evaluated by + operating cost as evaluated by `variable_operating_cost_function` + - ``balance_of_system_cost``: plant balance of system + cost as evaluated by `balance_of_system_cost_function` + - ``self.wind_plant``: the SAM wind plant object, + through which all SAM variables can be accessed capital_cost_function : str The plant capital cost function written out as a string. @@ -1376,6 +1421,13 @@ def __init__(self, excl_fpath, res_fpath, tm_dset, objective_function, out as a string. This expression must return the variable operating cost in $/kWh. This expression has access to the same variables as the `objective_function` argument above. + You can set this to "0" to effectively ignore variable + operating costs. + balance_of_system_cost_function : str + The plant balance-of-system cost function as a string, must + return the variable operating cost in $. Has access to the + same variables as the objective_function. You can set this + to "0" to effectively ignore balance-of-system costs. project_points : int | list | tuple | str | dict | pd.DataFrame | slice Input specifying which sites to process. A single integer representing the supply curve GID of a site may be specified @@ -1406,11 +1458,13 @@ def __init__(self, excl_fpath, res_fpath, tm_dset, objective_function, - ``capital_cost_multiplier`` - ``fixed_operating_cost_multiplier`` - ``variable_operating_cost_multiplier`` + - ``balance_of_system_cost_multiplier`` These particular inputs are treated as multipliers to be applied to the respective cost curves (`capital_cost_function`, `fixed_operating_cost_function`, - and `variable_operating_cost_function`) both during and + `variable_operating_cost_function`, and + `balance_of_system_cost_function`) both during and after the optimization. A DataFrame following the same guidelines as the CSV input (or a dictionary that can be used to initialize such a DataFrame) may be used for this @@ -1697,6 +1751,8 @@ def __init__(self, excl_fpath, res_fpath, tm_dset, objective_function, .format(fixed_operating_cost_function)) logger.info('Bespoke variable operating cost function: {}' .format(variable_operating_cost_function)) + logger.info('Bespoke balance of system cost function: {}' + .format(balance_of_system_cost_function)) logger.info('Bespoke wake loss multiplier: {}' .format(wake_loss_multiplier)) logger.info('Bespoke GA initialization kwargs: {}'.format(ga_kwargs)) @@ -1724,6 +1780,7 @@ def __init__(self, excl_fpath, res_fpath, tm_dset, objective_function, self._cap_cost_fun = capital_cost_function self._foc_fun = fixed_operating_cost_function self._voc_fun = variable_operating_cost_function + self._bos_fun = balance_of_system_cost_function self._min_spacing = min_spacing self._wake_loss_multiplier = wake_loss_multiplier self._ga_kwargs = ga_kwargs or {} @@ -2158,6 +2215,7 @@ def run_serial(cls, excl_fpath, res_fpath, tm_dset, capital_cost_function, fixed_operating_cost_function, variable_operating_cost_function, + balance_of_system_cost_function, min_spacing='5x', wake_loss_multiplier=1, ga_kwargs=None, output_request=('system_capacity', 'cf_mean'), ws_bins=(0.0, 20.0, 5.0), wd_bins=(0.0, 360.0, 45.0), @@ -2218,6 +2276,7 @@ def run_serial(cls, excl_fpath, res_fpath, tm_dset, capital_cost_function, fixed_operating_cost_function, variable_operating_cost_function, + balance_of_system_cost_function, min_spacing=min_spacing, wake_loss_multiplier=wake_loss_multiplier, ga_kwargs=ga_kwargs, @@ -2298,6 +2357,7 @@ def run_parallel(self, max_workers=None): self._cap_cost_fun, self._foc_fun, self._voc_fun, + self._bos_fun, self._min_spacing, wake_loss_multiplier=self._wake_loss_multiplier, ga_kwargs=self._ga_kwargs, @@ -2382,6 +2442,7 @@ def run(self, out_fpath=None, max_workers=None): self._cap_cost_fun, self._foc_fun, self._voc_fun, + self._bos_fun, min_spacing=self._min_spacing, wake_loss_multiplier=wlm, ga_kwargs=self._ga_kwargs, diff --git a/reV/bespoke/cli_bespoke.py b/reV/bespoke/cli_bespoke.py index 7e4f65231..e80384e1e 100644 --- a/reV/bespoke/cli_bespoke.py +++ b/reV/bespoke/cli_bespoke.py @@ -54,6 +54,8 @@ def _log_bespoke_cli_inputs(config): .format(config.get("fixed_operating_cost_function"))) logger.info('Bespoke variable operating cost function: "{}"' .format(config.get("variable_operating_cost_function"))) + logger.info('Bespoke balance of system cost function: "{}"' + .format(config.get("balance_of_system_cost_function"))) logger.info('Bespoke wake loss multiplier: "{}"' .format(config.get("wake_loss_multiplier", 1))) logger.info('The following project points were specified: "{}"' diff --git a/reV/bespoke/place_turbines.py b/reV/bespoke/place_turbines.py index c1370b492..7426e1010 100644 --- a/reV/bespoke/place_turbines.py +++ b/reV/bespoke/place_turbines.py @@ -49,6 +49,7 @@ def __init__(self, wind_plant, objective_function, capital_cost_function, fixed_operating_cost_function, variable_operating_cost_function, + balance_of_system_cost_function, include_mask, pixel_side_length, min_spacing, wake_loss_multiplier=1): """ @@ -64,26 +65,29 @@ def __init__(self, wind_plant, objective_function, should return the objective to be minimized during layout optimization. Variables available are: - - n_turbines: the number of turbines - - system_capacity: wind plant capacity - - aep: annual energy production - - avg_sl_dist_to_center_m: Average straight-line + - ``n_turbines``: the number of turbines + - ``system_capacity``: wind plant capacity + - ``aep``: annual energy production + - ``avg_sl_dist_to_center_m``: Average straight-line distance to the supply curve point center from all turbine locations (in m). Useful for computing plant BOS costs. - - fixed_charge_rate: user input fixed_charge_rate if + - ``avg_sl_dist_to_medoid_m``: Average straight-line + distance to the medoid of all turbine locations + (in m). Useful for computing plant BOS costs. + - ``fixed_charge_rate``: user input fixed_charge_rate if included as part of the sam system config. - - capital_cost: plant capital cost as evaluated + - ``capital_cost``: plant capital cost as evaluated by `capital_cost_function` - - fixed_operating_cost: plant fixed annual operating + - ``fixed_operating_cost``: plant fixed annual operating cost as evaluated by `fixed_operating_cost_function` - - variable_operating_cost: plant variable annual + - ``variable_operating_cost``: plant variable annual operating cost as evaluated by `variable_operating_cost_function` - - self.wind_plant: the SAM wind plant object, through - which all SAM variables can be accessed - - cost: the annual cost of the wind plant (from - cost_function) + - ``balance_of_system_cost``: plant balance of system + cost as evaluated by `balance_of_system_cost_function` + - ``self.wind_plant``: the SAM wind plant object, + through which all SAM variables can be accessed capital_cost_function : str The plant capital cost function as a string, must return the @@ -97,6 +101,13 @@ def __init__(self, wind_plant, objective_function, The plant annual variable operating cost function as a string, must return the variable operating cost in $/kWh. Has access to the same variables as the objective_function. + You can set this to "0" to effectively ignore variable + operating costs. + balance_of_system_cost_function : str + The plant balance-of-system cost function as a string, must + return the variable operating cost in $. Has access to the + same variables as the objective_function. You can set this + to "0" to effectively ignore balance-of-system costs. include_mask : np.ndarray Supply curve point 2D inclusion mask where included pixels are set to 1 and excluded pixels are set to 0. @@ -119,6 +130,7 @@ def __init__(self, wind_plant, objective_function, self.fixed_operating_cost_function = fixed_operating_cost_function self.variable_operating_cost_function = \ variable_operating_cost_function + self.balance_of_system_cost_function = balance_of_system_cost_function self.objective_function = objective_function self.include_mask = include_mask @@ -143,6 +155,7 @@ def __init__(self, wind_plant, objective_function, self._preflight(self.capital_cost_function) self._preflight(self.fixed_operating_cost_function) self._preflight(self.variable_operating_cost_function) + self._preflight(self.balance_of_system_cost_function) def _preflight(self, eqn): """Run preflight checks on the equation string.""" @@ -219,11 +232,21 @@ def initialize_packing(self): self.x_locations = packing.turbine_x self.y_locations = packing.turbine_y - def _avg_sl_dist_to_cent(self, x_locs, y_locs): - """Average straight-line distance to center from turb locations. """ + def _sc_center(self): + """Supply curve point center. """ ny, nx = np.shape(self.include_mask) cx = nx * self.pixel_side_length / 2 cy = ny * self.pixel_side_length / 2 + return cx, cy + + def _avg_sl_dist_to_cent(self, x_locs, y_locs): + """Average straight-line distance to center from turb locations. """ + cx, cy = self._sc_center() + return np.hypot(x_locs - cx, y_locs - cy).mean() + + def _avg_sl_dist_to_med(self, x_locs, y_locs): + """Average straight-line distance to turbine medoid. """ + cx, cy = _turb_medoid(x_locs, y_locs) return np.hypot(x_locs - cx, y_locs - cy).mean() # pylint: disable=W0641,W0123 @@ -244,8 +267,10 @@ def optimization_objective(self, x): self.wind_plant.execute() aep = self._aep_after_scaled_wake_losses() avg_sl_dist_to_center_m = self._avg_sl_dist_to_cent(x_locs, y_locs) + avg_sl_dist_to_medoid_m = self._avg_sl_dist_to_med(x_locs, y_locs) else: - n_turbines = system_capacity = aep = avg_sl_dist_to_center_m = 0 + n_turbines = system_capacity = aep = 0 + avg_sl_dist_to_center_m = avg_sl_dist_to_medoid_m = 0 fixed_charge_rate = self.fixed_charge_rate capital_cost = eval(self.capital_cost_function, @@ -254,13 +279,16 @@ def optimization_objective(self, x): globals(), locals()) variable_operating_cost = eval(self.variable_operating_cost_function, globals(), locals()) - + balance_of_system_cost = eval(self.balance_of_system_cost_function, + globals(), locals()) capital_cost *= self.wind_plant.sam_sys_inputs.get( 'capital_cost_multiplier', 1) fixed_operating_cost *= self.wind_plant.sam_sys_inputs.get( 'fixed_operating_cost_multiplier', 1) variable_operating_cost *= self.wind_plant.sam_sys_inputs.get( 'variable_operating_cost_multiplier', 1) + balance_of_system_cost *= self.wind_plant.sam_sys_inputs.get( + 'balance_of_system_cost_multiplier', 1) objective = eval(self.objective_function, globals(), locals()) @@ -371,6 +399,7 @@ def capital_cost_per_kw(self, capacity_mw): """ fixed_charge_rate = self.fixed_charge_rate + avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m n_turbines = int(round(capacity_mw * 1e3 / self.turbine_capacity)) system_capacity = n_turbines * self.turbine_capacity mult = self.wind_plant.sam_sys_inputs.get( @@ -499,6 +528,7 @@ def capital_cost(self): n_turbines = self.nturbs system_capacity = self.capacity aep = self.aep + avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m mult = self.wind_plant.sam_sys_inputs.get( 'capital_cost_multiplier', 1) return eval(self.capital_cost_function, globals(), locals()) * mult @@ -513,6 +543,7 @@ def fixed_operating_cost(self): n_turbines = self.nturbs system_capacity = self.capacity aep = self.aep + avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m mult = self.wind_plant.sam_sys_inputs.get( 'fixed_operating_cost_multiplier', 1) return eval(self.fixed_operating_cost_function, @@ -528,11 +559,26 @@ def variable_operating_cost(self): n_turbines = self.nturbs system_capacity = self.capacity aep = self.aep + avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m mult = self.wind_plant.sam_sys_inputs.get( 'variable_operating_cost_multiplier', 1) return eval(self.variable_operating_cost_function, globals(), locals()) * mult + @property + @none_until_optimized + def balance_of_system_cost(self): + """This is the balance of system cost of the optimized plant ($)""" + fixed_charge_rate = self.fixed_charge_rate + n_turbines = self.nturbs + system_capacity = self.capacity + aep = self.aep + avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m + mult = self.wind_plant.sam_sys_inputs.get( + 'balance_of_system_cost_multiplier', 1) + return eval(self.balance_of_system_cost_function, + globals(), locals()) * mult + # pylint: disable=W0641,W0123 @property @none_until_optimized @@ -545,5 +591,11 @@ def objective(self): capital_cost = self.capital_cost fixed_operating_cost = self.fixed_operating_cost variable_operating_cost = self.variable_operating_cost + balance_of_system_cost = self.balance_of_system_cost avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m return eval(self.objective_function, globals(), locals()) + + +def _turb_medoid(x_locs, y_locs): + """Turbine medoid. """ + return np.median(x_locs), np.median(y_locs) From 69c65a06739b428e1b480f8baa06798a3854a2a2 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Wed, 1 May 2024 17:42:21 -0600 Subject: [PATCH 03/11] Add missing property --- reV/bespoke/place_turbines.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/reV/bespoke/place_turbines.py b/reV/bespoke/place_turbines.py index 7426e1010..613cef171 100644 --- a/reV/bespoke/place_turbines.py +++ b/reV/bespoke/place_turbines.py @@ -430,6 +430,12 @@ def avg_sl_dist_to_center_m(self): """This is the final avg straight line distance to center (m)""" return self._avg_sl_dist_to_cent(self.turbine_x, self.turbine_y) + @property + @none_until_optimized + def avg_sl_dist_to_medoid_m(self): + """This is the final avg straight line distance to turb medoid (m)""" + return self._avg_sl_dist_to_med(self.turbine_x, self.turbine_y) + @property @none_until_optimized def nturbs(self): @@ -529,6 +535,7 @@ def capital_cost(self): system_capacity = self.capacity aep = self.aep avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m + avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m mult = self.wind_plant.sam_sys_inputs.get( 'capital_cost_multiplier', 1) return eval(self.capital_cost_function, globals(), locals()) * mult @@ -544,6 +551,7 @@ def fixed_operating_cost(self): system_capacity = self.capacity aep = self.aep avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m + avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m mult = self.wind_plant.sam_sys_inputs.get( 'fixed_operating_cost_multiplier', 1) return eval(self.fixed_operating_cost_function, @@ -560,6 +568,7 @@ def variable_operating_cost(self): system_capacity = self.capacity aep = self.aep avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m + avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m mult = self.wind_plant.sam_sys_inputs.get( 'variable_operating_cost_multiplier', 1) return eval(self.variable_operating_cost_function, @@ -574,6 +583,7 @@ def balance_of_system_cost(self): system_capacity = self.capacity aep = self.aep avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m + avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m mult = self.wind_plant.sam_sys_inputs.get( 'balance_of_system_cost_multiplier', 1) return eval(self.balance_of_system_cost_function, @@ -593,6 +603,7 @@ def objective(self): variable_operating_cost = self.variable_operating_cost balance_of_system_cost = self.balance_of_system_cost avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m + avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m return eval(self.objective_function, globals(), locals()) From 42ce9f6d37f2cd2f064747aaacdbb9c4551b20f8 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Thu, 2 May 2024 10:55:37 -0600 Subject: [PATCH 04/11] Remove duplicated output --- reV/bespoke/bespoke.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/reV/bespoke/bespoke.py b/reV/bespoke/bespoke.py index 4a8eab686..e8780dfba 100644 --- a/reV/bespoke/bespoke.py +++ b/reV/bespoke/bespoke.py @@ -1205,8 +1205,6 @@ def run_plant_optimization(self): self._meta["avg_sl_dist_to_medoid_m"] = \ self.plant_optimizer.avg_sl_dist_to_medoid_m self._meta["bespoke_aep"] = self.plant_optimizer.aep - self._meta["bespoke_avg_sl_dist_to_center_m"] = \ - self.plant_optimizer.avg_sl_dist_to_center_m self._meta["bespoke_objective"] = self.plant_optimizer.objective self._meta["bespoke_capital_cost"] = \ self.plant_optimizer.capital_cost From b9eddba1ab4d499a0a9b5e348f194a2c95987d34 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Thu, 30 May 2024 13:06:31 -0600 Subject: [PATCH 05/11] Add `nn_conn_dist_m` as option for BOS equation --- reV/bespoke/place_turbines.py | 57 +++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/reV/bespoke/place_turbines.py b/reV/bespoke/place_turbines.py index 613cef171..a3cafd382 100644 --- a/reV/bespoke/place_turbines.py +++ b/reV/bespoke/place_turbines.py @@ -75,6 +75,10 @@ def __init__(self, wind_plant, objective_function, - ``avg_sl_dist_to_medoid_m``: Average straight-line distance to the medoid of all turbine locations (in m). Useful for computing plant BOS costs. + - ``nn_conn_dist_m``: Total BOS connection distance + using nearest-neighbor connections. This variable is + only available for the + ``balance_of_system_cost_function`` equation. - ``fixed_charge_rate``: user input fixed_charge_rate if included as part of the sam system config. - ``capital_cost``: plant capital cost as evaluated @@ -149,6 +153,7 @@ def __init__(self, wind_plant, objective_function, self.packing_polygons = None self.optimized_design_variables = None self.safe_polygons = None + self._optimized_nn_conn_dist_m = None self.ILLEGAL = ('import ', 'os.', 'sys.', '.__', '__.', 'eval', 'exec') self._preflight(self.objective_function) @@ -268,9 +273,12 @@ def optimization_objective(self, x): aep = self._aep_after_scaled_wake_losses() avg_sl_dist_to_center_m = self._avg_sl_dist_to_cent(x_locs, y_locs) avg_sl_dist_to_medoid_m = self._avg_sl_dist_to_med(x_locs, y_locs) + if "nn_conn_dist_m" in self.balance_of_system_cost_function: + nn_conn_dist_m = _compute_nn_conn_dist(x_locs, y_locs) else: n_turbines = system_capacity = aep = 0 avg_sl_dist_to_center_m = avg_sl_dist_to_medoid_m = 0 + nn_conn_dist_m = 0 fixed_charge_rate = self.fixed_charge_rate capital_cost = eval(self.capital_cost_function, @@ -436,6 +444,16 @@ def avg_sl_dist_to_medoid_m(self): """This is the final avg straight line distance to turb medoid (m)""" return self._avg_sl_dist_to_med(self.turbine_x, self.turbine_y) + @property + @none_until_optimized + def nn_conn_dist_m(self): + """This is the final avg straight line distance to turb medoid (m)""" + if self._optimized_nn_conn_dist_m is None: + self._optimized_nn_conn_dist_m = _compute_nn_conn_dist( + self.turbine_x, self.turbine_y + ) + return self._optimized_nn_conn_dist_m + @property @none_until_optimized def nturbs(self): @@ -536,6 +554,8 @@ def capital_cost(self): aep = self.aep avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m + nn_conn_dist_m = self.nn_conn_dist_m + mult = self.wind_plant.sam_sys_inputs.get( 'capital_cost_multiplier', 1) return eval(self.capital_cost_function, globals(), locals()) * mult @@ -552,6 +572,8 @@ def fixed_operating_cost(self): aep = self.aep avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m + nn_conn_dist_m = self.nn_conn_dist_m + mult = self.wind_plant.sam_sys_inputs.get( 'fixed_operating_cost_multiplier', 1) return eval(self.fixed_operating_cost_function, @@ -569,6 +591,8 @@ def variable_operating_cost(self): aep = self.aep avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m + nn_conn_dist_m = self.nn_conn_dist_m + mult = self.wind_plant.sam_sys_inputs.get( 'variable_operating_cost_multiplier', 1) return eval(self.variable_operating_cost_function, @@ -584,6 +608,8 @@ def balance_of_system_cost(self): aep = self.aep avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m + nn_conn_dist_m = self.nn_conn_dist_m + mult = self.wind_plant.sam_sys_inputs.get( 'balance_of_system_cost_multiplier', 1) return eval(self.balance_of_system_cost_function, @@ -604,9 +630,40 @@ def objective(self): balance_of_system_cost = self.balance_of_system_cost avg_sl_dist_to_center_m = self.avg_sl_dist_to_center_m avg_sl_dist_to_medoid_m = self.avg_sl_dist_to_medoid_m + nn_conn_dist_m = self.nn_conn_dist_m + return eval(self.objective_function, globals(), locals()) def _turb_medoid(x_locs, y_locs): """Turbine medoid. """ return np.median(x_locs), np.median(y_locs) + + +def _compute_nn_conn_dist(x_coords, y_coords): + """Connect turbines using a greedy nearest-neighbor approach. """ + if len(x_coords) <= 1: + return 0 + + coordinates = np.c_[x_coords, y_coords] + allowed_conns = np.r_[coordinates.mean(axis=0)[None], coordinates] + + mask = np.zeros_like(allowed_conns) + mask[0] = 1 + left_to_connect = np.ma.array(allowed_conns, mask=mask) + + mask = np.ones_like(allowed_conns) + mask[0] = 0 + allowed_conns = np.ma.array(allowed_conns, mask=mask) + + total_dist = 0 + for __ in range(len(coordinates)): + dists = left_to_connect[:, :, None] - allowed_conns.T[None] + dists = np.hypot(dists[:, 0], dists[:, 1]) + min_dists = dists.min(axis=-1) + total_dist += min_dists.min() + next_connection = min_dists.argmin() + allowed_conns.mask[next_connection] = 0 + left_to_connect.mask[next_connection] = 1 + + return total_dist From 09a736b2d1cae6740f648c84bb9e160ef9f5cba9 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Thu, 30 May 2024 13:07:17 -0600 Subject: [PATCH 06/11] Add `nn_conn_dist_m` as possible output --- reV/bespoke/bespoke.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/reV/bespoke/bespoke.py b/reV/bespoke/bespoke.py index e8780dfba..77c5adaf4 100644 --- a/reV/bespoke/bespoke.py +++ b/reV/bespoke/bespoke.py @@ -236,6 +236,10 @@ def __init__(self, gid, excl, res, tm_dset, sam_sys_inputs, - ``avg_sl_dist_to_medoid_m``: Average straight-line distance to the medoid of all turbine locations (in m). Useful for computing plant BOS costs. + - ``nn_conn_dist_m``: Total BOS connection distance + using nearest-neighbor connections. This variable is + only available for the + ``balance_of_system_cost_function`` equation. - ``fixed_charge_rate``: user input fixed_charge_rate if included as part of the sam system config. - ``capital_cost``: plant capital cost as evaluated @@ -1204,6 +1208,7 @@ def run_plant_optimization(self): self.plant_optimizer.avg_sl_dist_to_center_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"] = \ @@ -1390,6 +1395,10 @@ def __init__(self, excl_fpath, res_fpath, tm_dset, objective_function, - ``avg_sl_dist_to_medoid_m``: Average straight-line distance to the medoid of all turbine locations (in m). Useful for computing plant BOS costs. + - ``nn_conn_dist_m``: Total BOS connection distance + using nearest-neighbor connections. This variable is + only available for the + ``balance_of_system_cost_function`` equation. - ``fixed_charge_rate``: user input fixed_charge_rate if included as part of the sam system config. - ``capital_cost``: plant capital cost as evaluated From 7d70257178987121622064f89ee133211f8b060f Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Thu, 30 May 2024 13:07:31 -0600 Subject: [PATCH 07/11] Update tests for BOS --- tests/test_bespoke.py | 63 +++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/tests/test_bespoke.py b/tests/test_bespoke.py index 6c17a6698..b2c63081d 100644 --- a/tests/test_bespoke.py +++ b/tests/test_bespoke.py @@ -17,7 +17,7 @@ from reV import TESTDATADIR from reV.cli import main from reV.bespoke.bespoke import BespokeSinglePlant, BespokeWindPlants -from reV.bespoke.place_turbines import PlaceTurbines +from reV.bespoke.place_turbines import PlaceTurbines, _compute_nn_conn_dist from reV.handlers.outputs import Outputs from reV.supply_curve.tech_mapping import TechMapping from reV.supply_curve.supply_curve import SupplyCurve @@ -69,6 +69,7 @@ FOC_FUN = ('60 * system_capacity ' '* np.exp(-system_capacity / 1E5 * 0.1 + (1 - 0.1))') VOC_FUN = '3' +BOS_FUN = '0' OBJECTIVE_FUNCTION = ('(0.0975 * capital_cost + fixed_operating_cost) ' '/ aep + variable_operating_cost') @@ -95,6 +96,7 @@ def test_turbine_placement(gid=33): CAP_COST_FUN, FOC_FUN, VOC_FUN, + '10 * nn_conn_dist_m', excl_dict=EXCL_DICT, output_request=output_request, ) @@ -109,6 +111,7 @@ def test_turbine_placement(gid=33): assert place_optimizer.capital_cost is None assert place_optimizer.fixed_operating_cost is None assert place_optimizer.variable_operating_cost is None + assert place_optimizer.balance_of_system_cost is None assert place_optimizer.objective is None place_optimizer.place_turbines(max_time=5) @@ -139,6 +142,9 @@ def test_turbine_placement(gid=33): capital_cost = eval(CAP_COST_FUN, globals(), locals()) fixed_operating_cost = eval(FOC_FUN, globals(), locals()) * 2 variable_operating_cost = eval(VOC_FUN, globals(), locals()) * 5 + balance_of_system_cost = 10 * _compute_nn_conn_dist( + place_optimizer.turbine_x, place_optimizer.turbine_y + ) # pylint: disable=W0123 assert place_optimizer.objective ==\ eval(OBJECTIVE_FUNCTION, globals(), locals()) @@ -146,6 +152,7 @@ def test_turbine_placement(gid=33): assert place_optimizer.fixed_operating_cost == fixed_operating_cost assert (place_optimizer.variable_operating_cost == variable_operating_cost) + assert place_optimizer.balance_of_system_cost == balance_of_system_cost bsp.close() @@ -170,7 +177,7 @@ def test_zero_area(gid=33): bsp = BespokeSinglePlant(gid, excl_fp, res_fp, TM_DSET, SAM_SYS_INPUTS, objective_function, CAP_COST_FUN, - FOC_FUN, VOC_FUN, + FOC_FUN, VOC_FUN, BOS_FUN, excl_dict=EXCL_DICT, output_request=output_request, ) @@ -213,7 +220,7 @@ def test_correct_turb_location(gid=33): bsp = BespokeSinglePlant(gid, excl_fp, res_fp, TM_DSET, SAM_SYS_INPUTS, objective_function, CAP_COST_FUN, - FOC_FUN, VOC_FUN, + FOC_FUN, VOC_FUN, BOS_FUN, excl_dict=EXCL_DICT, output_request=output_request, ) @@ -224,6 +231,7 @@ def test_correct_turb_location(gid=33): bsp.capital_cost_function, bsp.fixed_operating_cost_function, bsp.variable_operating_cost_function, + bsp.balance_of_system_cost_function, include_mask, pixel_side_length=90, min_spacing=45) @@ -306,7 +314,7 @@ def test_single(gid=33): bsp = BespokeSinglePlant(gid, excl_fp, res_fp, TM_DSET, SAM_SYS_INPUTS, OBJECTIVE_FUNCTION, CAP_COST_FUN, - FOC_FUN, VOC_FUN, + FOC_FUN, VOC_FUN, BOS_FUN, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request, @@ -389,7 +397,7 @@ def test_extra_outputs(gid=33): bsp = BespokeSinglePlant(gid, excl_fp, res_fp, TM_DSET, SAM_SYS_INPUTS, objective_function, CAP_COST_FUN, - FOC_FUN, VOC_FUN, + FOC_FUN, VOC_FUN, BOS_FUN, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request, @@ -401,7 +409,7 @@ def test_extra_outputs(gid=33): bsp = BespokeSinglePlant(gid, excl_fp, res_fp, TM_DSET, sam_sys_inputs, objective_function, CAP_COST_FUN, - FOC_FUN, VOC_FUN, + FOC_FUN, VOC_FUN, BOS_FUN, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request, @@ -436,7 +444,7 @@ def test_extra_outputs(gid=33): bsp = BespokeSinglePlant(gid, excl_fp, res_fp, TM_DSET, sam_sys_inputs, objective_function, CAP_COST_FUN, - FOC_FUN, VOC_FUN, + FOC_FUN, VOC_FUN, BOS_FUN, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request, @@ -505,7 +513,8 @@ 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, fully_excluded_points, + FOC_FUN, VOC_FUN, BOS_FUN, + fully_excluded_points, SAM_CONFIGS, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request) @@ -515,8 +524,8 @@ 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, points, - SAM_CONFIGS, ga_kwargs={'max_time': 5}, + CAP_COST_FUN, FOC_FUN, VOC_FUN, BOS_FUN, + 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) @@ -554,8 +563,8 @@ def test_bespoke(): out_fpath_pre = os.path.join(td, 'bespoke_out_pre.h5') bsp = BespokeWindPlants(excl_fp, res_fp, TM_DSET, OBJECTIVE_FUNCTION, - CAP_COST_FUN, FOC_FUN, VOC_FUN, points, - SAM_CONFIGS, ga_kwargs={'max_time': 1}, + CAP_COST_FUN, FOC_FUN, VOC_FUN, BOS_FUN, + points, SAM_CONFIGS, ga_kwargs={'max_time': 1}, excl_dict=EXCL_DICT, output_request=output_request, pre_load_data=True) @@ -695,6 +704,7 @@ def test_wake_loss_multiplier(wlm): CAP_COST_FUN, FOC_FUN, VOC_FUN, + BOS_FUN, excl_dict=EXCL_DICT, output_request=output_request, ) @@ -721,6 +731,7 @@ def test_wake_loss_multiplier(wlm): CAP_COST_FUN, FOC_FUN, VOC_FUN, + BOS_FUN, excl_dict=EXCL_DICT, output_request=output_request, wake_loss_multiplier=wlm) @@ -760,6 +771,7 @@ def test_bespoke_wind_plant_with_power_curve_losses(): CAP_COST_FUN, FOC_FUN, VOC_FUN, + BOS_FUN, excl_dict=EXCL_DICT, output_request=output_request, ) @@ -786,6 +798,7 @@ def test_bespoke_wind_plant_with_power_curve_losses(): CAP_COST_FUN, FOC_FUN, VOC_FUN, + BOS_FUN, excl_dict=EXCL_DICT, output_request=output_request) @@ -821,7 +834,7 @@ def test_bespoke_run_with_power_curve_losses(): bsp = BespokeSinglePlant(33, excl_fp, res_fp, TM_DSET, SAM_SYS_INPUTS, OBJECTIVE_FUNCTION, CAP_COST_FUN, - FOC_FUN, VOC_FUN, + FOC_FUN, VOC_FUN, BOS_FUN, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request) @@ -841,6 +854,7 @@ def test_bespoke_run_with_power_curve_losses(): CAP_COST_FUN, FOC_FUN, VOC_FUN, + BOS_FUN, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request) @@ -872,7 +886,7 @@ def test_bespoke_run_with_scheduled_losses(): bsp = BespokeSinglePlant(33, excl_fp, res_fp, TM_DSET, SAM_SYS_INPUTS, OBJECTIVE_FUNCTION, CAP_COST_FUN, - FOC_FUN, VOC_FUN, + FOC_FUN, VOC_FUN, BOS_FUN, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request) @@ -898,6 +912,7 @@ def test_bespoke_run_with_scheduled_losses(): CAP_COST_FUN, FOC_FUN, VOC_FUN, + BOS_FUN, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request) @@ -938,6 +953,7 @@ def test_bespoke_aep_is_zero_if_no_turbines_placed(): CAP_COST_FUN, FOC_FUN, VOC_FUN, + BOS_FUN, excl_dict=EXCL_DICT, output_request=output_request, ) @@ -997,7 +1013,7 @@ def test_bespoke_prior_run(): bsp = BespokeWindPlants(excl_fp, res_fp_all, TM_DSET, OBJECTIVE_FUNCTION, CAP_COST_FUN, - FOC_FUN, VOC_FUN, points, sam_configs, + FOC_FUN, VOC_FUN, BOS_FUN, points, sam_configs, ga_kwargs={'max_time': 1}, excl_dict=EXCL_DICT, output_request=output_request) bsp.run(max_workers=1, out_fpath=out_fpath1) @@ -1007,7 +1023,7 @@ def test_bespoke_prior_run(): bsp = BespokeWindPlants(excl_fp, res_fp_2013, TM_DSET, OBJECTIVE_FUNCTION, CAP_COST_FUN, - FOC_FUN, VOC_FUN, points, sam_configs, + FOC_FUN, VOC_FUN, BOS_FUN, points, sam_configs, ga_kwargs={'max_time': 1}, excl_dict=EXCL_DICT, output_request=output_request, prior_run=out_fpath1) @@ -1071,7 +1087,7 @@ def test_gid_map(): bsp = BespokeWindPlants(excl_fp, res_fp_2013, TM_DSET, OBJECTIVE_FUNCTION, CAP_COST_FUN, - FOC_FUN, VOC_FUN, points, SAM_CONFIGS, + FOC_FUN, VOC_FUN, BOS_FUN, points, SAM_CONFIGS, ga_kwargs={'max_time': 1}, excl_dict=EXCL_DICT, output_request=output_request) bsp.run(max_workers=1, out_fpath=out_fpath1) @@ -1081,7 +1097,7 @@ def test_gid_map(): bsp = BespokeWindPlants(excl_fp, res_fp_2013, TM_DSET, OBJECTIVE_FUNCTION, CAP_COST_FUN, - FOC_FUN, VOC_FUN, points, SAM_CONFIGS, + FOC_FUN, VOC_FUN, BOS_FUN, points, SAM_CONFIGS, ga_kwargs={'max_time': 1}, excl_dict=EXCL_DICT, output_request=output_request, gid_map=fp_gid_map) @@ -1110,7 +1126,7 @@ def test_gid_map(): out_fpath_pre = os.path.join(td, 'bespoke_out_pre.h5') bsp = BespokeWindPlants(excl_fp, res_fp_2013, TM_DSET, OBJECTIVE_FUNCTION, CAP_COST_FUN, - FOC_FUN, VOC_FUN, points, SAM_CONFIGS, + FOC_FUN, VOC_FUN, BOS_FUN, points, SAM_CONFIGS, ga_kwargs={'max_time': 1}, excl_dict=EXCL_DICT, output_request=output_request, gid_map=fp_gid_map, pre_load_data=True) @@ -1155,7 +1171,7 @@ def test_bespoke_bias_correct(): bsp = BespokeWindPlants(excl_fp, res_fp_2013, TM_DSET, OBJECTIVE_FUNCTION, CAP_COST_FUN, - FOC_FUN, VOC_FUN, points, SAM_CONFIGS, + FOC_FUN, VOC_FUN, BOS_FUN, points, SAM_CONFIGS, ga_kwargs={'max_time': 1}, excl_dict=EXCL_DICT, output_request=output_request) bsp.run(max_workers=1, out_fpath=out_fpath1) @@ -1165,7 +1181,7 @@ def test_bespoke_bias_correct(): bsp = BespokeWindPlants(excl_fp, res_fp_2013, TM_DSET, OBJECTIVE_FUNCTION, CAP_COST_FUN, - FOC_FUN, VOC_FUN, points, SAM_CONFIGS, + FOC_FUN, VOC_FUN, BOS_FUN, points, SAM_CONFIGS, ga_kwargs={'max_time': 1}, excl_dict=EXCL_DICT, output_request=output_request, bias_correct=fp_bc) @@ -1220,6 +1236,7 @@ def test_cli(runner, clear_loggers): "capital_cost_function": CAP_COST_FUN, "fixed_operating_cost_function": FOC_FUN, "variable_operating_cost_function": VOC_FUN, + "balance_of_system_cost_function": "0", "project_points": [33, 35], "sam_files": SAM_CONFIGS, "min_spacing": '5x', @@ -1310,8 +1327,8 @@ def test_bespoke_5min_sample(): excl_file.create_dataset(name=tm_dset, data=arr) bsp = BespokeWindPlants(excl_fp, res_fp, tm_dset, OBJECTIVE_FUNCTION, - CAP_COST_FUN, FOC_FUN, VOC_FUN, points, - sam_configs, ga_kwargs={'max_time': 5}, + CAP_COST_FUN, FOC_FUN, VOC_FUN, BOS_FUN, + points, sam_configs, ga_kwargs={'max_time': 5}, excl_dict=EXCL_DICT, output_request=output_request) _ = bsp.run(max_workers=1, out_fpath=out_fpath) From 9a84daf737db0ebefcdeeef201d72653c0d4ccd4 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Thu, 30 May 2024 13:26:26 -0600 Subject: [PATCH 08/11] Linter fixes --- reV/bespoke/bespoke.py | 2 +- tests/test_bespoke.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reV/bespoke/bespoke.py b/reV/bespoke/bespoke.py index 750e0dc88..5d5cbb878 100644 --- a/reV/bespoke/bespoke.py +++ b/reV/bespoke/bespoke.py @@ -1336,7 +1336,7 @@ def run_plant_optimization(self): ) self._meta["bespoke_balance_of_system_cost"] = ( self.plant_optimizer.balance_of_system_cost - ) + ) self._meta["included_area"] = self.plant_optimizer.area self._meta["included_area_capacity_density"] = ( self.plant_optimizer.capacity_density diff --git a/tests/test_bespoke.py b/tests/test_bespoke.py index 1e1007328..af6571a7e 100644 --- a/tests/test_bespoke.py +++ b/tests/test_bespoke.py @@ -891,6 +891,7 @@ def test_bespoke_wind_plant_with_power_curve_losses(): optimizer.wind_plant.assign_inputs() optimizer.wind_plant.execute() + # pylint: disable=W0612 aep = optimizer._aep_after_scaled_wake_losses() bsp.close() @@ -910,7 +911,6 @@ def test_bespoke_wind_plant_with_power_curve_losses(): output_request=output_request) - def test_bespoke_run_with_power_curve_losses(): """Test bespoke run with power curve losses.""" output_request = ("system_capacity", "cf_mean", "cf_profile") From fbf28f09f2037f160e677e3e4b46ab61f64e25cc Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Thu, 30 May 2024 13:40:39 -0600 Subject: [PATCH 09/11] Fix `test_consistent_eval_namespace` --- tests/test_bespoke.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_bespoke.py b/tests/test_bespoke.py index af6571a7e..5e07a88b1 100644 --- a/tests/test_bespoke.py +++ b/tests/test_bespoke.py @@ -699,6 +699,7 @@ def test_consistent_eval_namespace(gid=33): cap_cost_fun = "2000" foc_fun = "0" voc_fun = "0" + bos_fun = "0" objective_function = ( "n_turbines + id(self.wind_plant) " "+ system_capacity + capital_cost + aep" @@ -722,6 +723,7 @@ def test_consistent_eval_namespace(gid=33): cap_cost_fun, foc_fun, voc_fun, + bos_fun, ga_kwargs={"max_time": 5}, excl_dict=EXCL_DICT, output_request=output_request, From 27c9b35e5ebbdcfa8ce0aac964a1110779b04b87 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Thu, 30 May 2024 14:26:42 -0600 Subject: [PATCH 10/11] Fix missing argument --- tests/test_bespoke.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_bespoke.py b/tests/test_bespoke.py index 5e07a88b1..f794e714f 100644 --- a/tests/test_bespoke.py +++ b/tests/test_bespoke.py @@ -273,6 +273,7 @@ def test_packing_algorithm(gid=33): cap_cost_fun = "" foc_fun = "" voc_fun = "" + bos_fun = "" objective_function = "" with tempfile.TemporaryDirectory() as td: res_fp = os.path.join(td, "ri_100_wtk_{}.h5") @@ -293,6 +294,7 @@ def test_packing_algorithm(gid=33): cap_cost_fun, foc_fun, voc_fun, + bos_fun, ga_kwargs={"max_time": 5}, excl_dict=EXCL_DICT, output_request=output_request, From beb1f6928791da7af89be3b9f18cc9a271a9fcdc Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Thu, 30 May 2024 19:10:39 -0600 Subject: [PATCH 11/11] Add back missing test logic --- tests/test_bespoke.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_bespoke.py b/tests/test_bespoke.py index f794e714f..ddb83dbf1 100644 --- a/tests/test_bespoke.py +++ b/tests/test_bespoke.py @@ -913,6 +913,21 @@ def test_bespoke_wind_plant_with_power_curve_losses(): BOS_FUN, excl_dict=EXCL_DICT, output_request=output_request) + optimizer2 = bsp.plant_optimizer + optimizer2.wind_plant["wind_farm_xCoordinates"] = [1000, -1000] + optimizer2.wind_plant["wind_farm_yCoordinates"] = [1000, -1000] + cap = 2 * optimizer2.turbine_capacity + optimizer2.wind_plant["system_capacity"] = cap + + optimizer2.wind_plant.assign_inputs() + optimizer2.wind_plant.execute() + aep_losses = optimizer2._aep_after_scaled_wake_losses() + bsp.close() + + assert aep > aep_losses, f"{aep}, {aep_losses}" + + err_msg = "{:0.3f} != 0.9".format(aep_losses / aep) + assert np.isclose(aep_losses / aep, 0.9), err_msg def test_bespoke_run_with_power_curve_losses():