From 6f7064cb2e9aed322edc9010f9cb53e67baf1898 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 17 Jun 2024 14:16:43 -0600 Subject: [PATCH 1/8] Default to NaN instead of `None` --- reV/bespoke/bespoke.py | 10 +++++----- reV/supply_curve/points.py | 5 ++++- reV/supply_curve/supply_curve.py | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/reV/bespoke/bespoke.py b/reV/bespoke/bespoke.py index 0c38fa9c7..bd14d0aa5 100644 --- a/reV/bespoke/bespoke.py +++ b/reV/bespoke/bespoke.py @@ -1263,10 +1263,10 @@ def run_wind_plant_ts(self): self._outputs.update(means) self._meta[SupplyCurveField.MEAN_RES] = self.res_df["windspeed"].mean() - self._meta[SupplyCurveField.MEAN_CF_DC] = None - self._meta[SupplyCurveField.MEAN_CF_AC] = None - self._meta[SupplyCurveField.MEAN_LCOE] = None - self._meta[SupplyCurveField.SC_POINT_ANNUAL_ENERGY_MW] = None + self._meta[SupplyCurveField.MEAN_CF_DC] = np.nan + self._meta[SupplyCurveField.MEAN_CF_AC] = np.nan + self._meta[SupplyCurveField.MEAN_LCOE] = np.nan + self._meta[SupplyCurveField.SC_POINT_ANNUAL_ENERGY_MW] = np.nan # copy dataset outputs to meta data for supply curve table summary if "cf_mean-means" in self.outputs: self._meta.loc[:, SupplyCurveField.MEAN_CF_AC] = self.outputs[ @@ -1373,7 +1373,7 @@ def run_plant_optimization(self): # convert SAM system capacity in kW to reV supply curve cap in MW capacity_ac_mw = system_capacity_kw / 1e3 self._meta[SupplyCurveField.CAPACITY_AC_MW] = capacity_ac_mw - self._meta[SupplyCurveField.CAPACITY_DC_MW] = None + self._meta[SupplyCurveField.CAPACITY_DC_MW] = np.nan # add required ReEDS multipliers to meta baseline_cost = self.plant_optimizer.capital_cost_per_kw( diff --git a/reV/supply_curve/points.py b/reV/supply_curve/points.py index 8f1c801f6..b0f4d28f9 100644 --- a/reV/supply_curve/points.py +++ b/reV/supply_curve/points.py @@ -2356,7 +2356,10 @@ def point_summary(self, args=None): summary = {} for arg in args: if arg in ARGS: - summary[arg] = ARGS[arg] + val = ARGS[arg] + if val is None: + val = np.nan + summary[arg] = val else: warn( 'Cannot find "{}" as an available SC self summary ' diff --git a/reV/supply_curve/supply_curve.py b/reV/supply_curve/supply_curve.py index 8c1d3f415..45ea70270 100644 --- a/reV/supply_curve/supply_curve.py +++ b/reV/supply_curve/supply_curve.py @@ -1289,7 +1289,7 @@ def _adjust_output_columns(self, columns, consider_friction): for col in _REQUIRED_OUTPUT_COLS: if col not in self._trans_table: - self._trans_table[col] = None + self._trans_table[col] = np.nan if col not in columns: columns.append(col) From 2e82e2aac7d4714ebccd7913c683f69691819020 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 17 Jun 2024 16:02:53 -0600 Subject: [PATCH 2/8] Bump version --- reV/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reV/version.py b/reV/version.py index 706db7cfc..c242d7808 100644 --- a/reV/version.py +++ b/reV/version.py @@ -2,4 +2,4 @@ reV Version number """ -__version__ = "0.9.0" +__version__ = "0.9.1" From 207cf3cbebf6bd8069eb85dd563d7f29d7246f8d Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 17 Jun 2024 16:25:39 -0600 Subject: [PATCH 3/8] Update docstrings --- reV/bespoke/bespoke.py | 4 ++-- reV/econ/econ.py | 33 ++++++++++++++++++++++----------- reV/generation/generation.py | 29 +++++++++++++++++++---------- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/reV/bespoke/bespoke.py b/reV/bespoke/bespoke.py index bd14d0aa5..3632dfbe9 100644 --- a/reV/bespoke/bespoke.py +++ b/reV/bespoke/bespoke.py @@ -1618,7 +1618,7 @@ def __init__(self, excl_fpath, res_fpath, tm_dset, objective_function, multiple sites can be specified to evaluate ``reV`` at multiple specific locations. A string pointing to a project points CSV file may also be specified. Typically, the CSV - contains two columns: + contains the following columns: - ``gid``: Integer specifying the supply curve GID of each site. @@ -1635,7 +1635,7 @@ def __init__(self, excl_fpath, res_fpath, tm_dset, objective_function, site-specific capital cost value for each location). Columns that do not correspond to a config key may also be included, but they will be ignored. The CSV file input can also have - these extra columns: + these extra, optional columns: - ``capital_cost_multiplier`` - ``fixed_operating_cost_multiplier`` diff --git a/reV/econ/econ.py b/reV/econ/econ.py index a55985cdb..a9928af18 100644 --- a/reV/econ/econ.py +++ b/reV/econ/econ.py @@ -76,25 +76,36 @@ def __init__(self, project_points, sam_files, cf_file, site_data=None, (or slice) representing the GIDs of multiple sites can be specified to evaluate reV at multiple specific locations. A string pointing to a project points CSV file may also be - specified. Typically, the CSV contains two columns: + specified. Typically, the CSV contains the following + columns: - - ``gid``: Integer specifying the GID of each site. + - ``gid``: Integer specifying the generation GID of each + site. - ``config``: Key in the `sam_files` input dictionary (see below) corresponding to the SAM configuration to use for each particular site. This value can also be ``None`` (or left out completely) if you specify only a single SAM configuration file as the `sam_files` input. - - The CSV file may also contain site-specific inputs by + - ``capital_cost_multiplier``: This is an *optional* + multiplier input that, if included, will be used to + regionally scale the ``capital_cost`` input in the SAM + config. If you include this column in your CSV, you + *do not* need to specify ``capital_cost``, unless you + would like that value to vary regionally and + independently of the multiplier (i.e. the multiplier + will still be applied on top of the ``capital_cost`` + input). + + The CSV file may also contain other site-specific inputs by including a column named after a config keyword (e.g. a - column called ``capital_cost`` may be included to specify a - site-specific capital cost value for each location). Columns - that do not correspond to a config key may also be included, - but they will be ignored. 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 - input as well. + column called ``wind_turbine_rotor_diameter`` may be + included to specify a site-specific turbine diameter for + each location). Columns that do not correspond to a config + key may also be included, but they will be ignored. 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 input as well. sam_files : dict | str A dictionary mapping SAM input configuration ID(s) to SAM configuration(s). Keys are the SAM config ID(s) which diff --git a/reV/generation/generation.py b/reV/generation/generation.py index e5ae69094..2a57e3318 100644 --- a/reV/generation/generation.py +++ b/reV/generation/generation.py @@ -171,7 +171,7 @@ def __init__( multiple sites can be specified to evaluate reV at multiple specific locations. A string pointing to a project points CSV file may also be specified. Typically, the CSV contains - two columns: + the following columns: - ``gid``: Integer specifying the generation GID of each site. @@ -181,16 +181,25 @@ def __init__( ``None`` (or left out completely) if you specify only a single SAM configuration file as the `sam_files` input. - - The CSV file may also contain site-specific inputs by + - ``capital_cost_multiplier``: This is an *optional* + multiplier input that, if included, will be used to + regionally scale the ``capital_cost`` input in the SAM + config. If you include this column in your CSV, you + *do not* need to specify ``capital_cost``, unless you + would like that value to vary regionally and + independently of the multiplier (i.e. the multiplier + will still be applied on top of the ``capital_cost`` + input). + + The CSV file may also contain other site-specific inputs by including a column named after a config keyword (e.g. a - column called ``capital_cost`` may be included to specify a - site-specific capital cost value for each location). Columns - that do not correspond to a config key may also be included, - but they will be ignored. 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 - input as well. + column called ``wind_turbine_rotor_diameter`` may be + included to specify a site-specific turbine diameter for + each location). Columns that do not correspond to a config + key may also be included, but they will be ignored. 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 input as well. .. Note:: By default, the generation GID of each site is assumed to match the resource GID to be evaluated for that From 885b0efdc5f77b5e9acc8ec5a53c2d344136cfdb Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Mon, 17 Jun 2024 16:25:48 -0600 Subject: [PATCH 4/8] Ignore flake8 error --- reV/supply_curve/supply_curve.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/reV/supply_curve/supply_curve.py b/reV/supply_curve/supply_curve.py index 45ea70270..c6628fb99 100644 --- a/reV/supply_curve/supply_curve.py +++ b/reV/supply_curve/supply_curve.py @@ -1116,8 +1116,7 @@ def add_sum_cols(table, sum_cols): return table - # pylint: disable=C901 - def _full_sort( + def _full_sort( # noqa: C901 self, trans_table, trans_costs=None, From 58229f2f3587a4e93cbc862912956bbf130c46b2 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 18 Jun 2024 13:34:50 -0600 Subject: [PATCH 5/8] Delay conversion to NaN until after data layers and EoS --- reV/supply_curve/points.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/reV/supply_curve/points.py b/reV/supply_curve/points.py index b0f4d28f9..5076ac87b 100644 --- a/reV/supply_curve/points.py +++ b/reV/supply_curve/points.py @@ -2356,10 +2356,7 @@ def point_summary(self, args=None): summary = {} for arg in args: if arg in ARGS: - val = ARGS[arg] - if val is None: - val = np.nan - summary[arg] = val + summary[arg] = ARGS[arg] else: warn( 'Cannot find "{}" as an available SC self summary ' @@ -2540,6 +2537,10 @@ def summarize( if cap_cost_scale is not None: summary = point.economies_of_scale(cap_cost_scale, summary) + for arg, val in summary.items(): + if val is None: + summary[arg] = np.nan + return summary From 71764f2ef560b39ca3716dee14a5f94c74b7fe06 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 18 Jun 2024 13:36:05 -0600 Subject: [PATCH 6/8] Add another alias for system_capacity --- reV/SAM/SAM.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/reV/SAM/SAM.py b/reV/SAM/SAM.py index fdd7040e2..aa33d4863 100644 --- a/reV/SAM/SAM.py +++ b/reV/SAM/SAM.py @@ -950,4 +950,7 @@ def _add_sys_capacity(sam_inputs): if cap is not None: cap = max(cap) + if cap is None: + cap = sam_inputs.get("nameplate") + sam_inputs["system_capacity"] = cap From 6d2c7c6378bb63190729b6074667e8a96dd0705e Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 18 Jun 2024 14:04:27 -0600 Subject: [PATCH 7/8] Align SAM costs passthrough for geothermal runs --- reV/SAM/generation.py | 38 +++++++++++++++++++++++++++++------- tests/test_gen_geothermal.py | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/reV/SAM/generation.py b/reV/SAM/generation.py index 2ef711fd9..e2187f7f2 100644 --- a/reV/SAM/generation.py +++ b/reV/SAM/generation.py @@ -1786,6 +1786,10 @@ def _set_nameplate_to_match_resource_potential(self, resource): "{}".format(self.sam_sys_inputs["nameplate"]) ) logger.info(msg) + # required for downstream LCOE calcs + self.sam_sys_inputs["system_capacity"] = ( + self.sam_sys_inputs["nameplate"] + ) return val = set(resource["potential_MW"].unique()) @@ -1801,6 +1805,8 @@ def _set_nameplate_to_match_resource_potential(self, resource): logger.debug("Setting the nameplate to {}".format(val)) self.sam_sys_inputs["nameplate"] = val + # required for downstream LCOE calcs + self.sam_sys_inputs["system_capacity"] = val def _set_resource_potential_to_match_gross_output(self): """Set the resource potential input to match the gross generation. @@ -1861,7 +1867,9 @@ def _set_costs(self): logger.debug( "Setting the capital_cost to ${:,.2f}".format(capital_cost) ) - self.sam_sys_inputs["capital_cost"] = capital_cost + reg_mult = self.sam_sys_inputs.get("capital_cost_multiplier", 1) + self.sam_sys_inputs["base_capital_cost"] = capital_cost + self.sam_sys_inputs["capital_cost"] = capital_cost * reg_mult dc_per_well = self.sam_sys_inputs.pop("drill_cost_per_well", None) num_wells = self.sam_sys_inputs.pop( @@ -1884,19 +1892,35 @@ def _set_costs(self): drill_cost, num_wells, dc_per_well ) ) - self.sam_sys_inputs["capital_cost"] = capital_cost + drill_cost + reg_mult = self.sam_sys_inputs.get( + "capital_cost_multiplier", 1 + ) + base_cc = capital_cost / reg_mult + new_base_cc = base_cc + drill_cost + self.sam_sys_inputs["base_capital_cost"] = new_base_cc + self.sam_sys_inputs["capital_cost"] = new_base_cc * reg_mult foc_per_kw = self.sam_sys_inputs.pop( "fixed_operating_cost_per_kw", None ) if foc_per_kw is not None: - fixed_operating_cost = foc_per_kw * plant_size_kw + foc = foc_per_kw * plant_size_kw logger.debug( - "Setting the fixed_operating_cost to ${:,.2f}".format( - capital_cost - ) + "Setting the fixed_operating_cost to ${:,.2f}".format(foc) + ) + self.sam_sys_inputs["base_fixed_operating_cost"] = foc + self.sam_sys_inputs["fixed_operating_cost"] = foc + + voc_per_kw = self.sam_sys_inputs.pop( + "variable_operating_cost_per_kw", None + ) + if voc_per_kw is not None: + voc = voc_per_kw * plant_size_kw + logger.debug( + "Setting the variable_operating_cost to ${:,.2f}".format(voc) ) - self.sam_sys_inputs["fixed_operating_cost"] = fixed_operating_cost + self.sam_sys_inputs["base_variable_operating_cost"] = voc + self.sam_sys_inputs["variable_operating_cost"] = voc def _create_pysam_wfile(self, resource, meta): """Create PySAM weather input file. diff --git a/tests/test_gen_geothermal.py b/tests/test_gen_geothermal.py index f1fdedab2..2b23cfcef 100644 --- a/tests/test_gen_geothermal.py +++ b/tests/test_gen_geothermal.py @@ -103,6 +103,14 @@ def test_gen_geothermal(depth, sample_resource_data): "lcoe_fcr": 12.52, "nameplate": 200_000, "resource_temp": 150, + "capital_cost" : 172485199.53989035, + "fixed_operating_cost" : 4885201.7298879623, + "variable_operating_cost" : 0, + "fixed_charge_rate" : 0.098000000000000004, + "base_capital_cost" : 172485199.53989035, + "base_fixed_operating_cost" : 4885201.7298879623, + "base_variable_operating_cost" : 0, + "system_capacity": 200_000, } for dset in output_request: truth = truth_vals[dset] @@ -156,6 +164,14 @@ def test_gen_geothermal_temp_too_low(sample_resource_data): "lcoe_fcr": 0, "nameplate": 0, "resource_temp": 60, + "capital_cost" : 0, + "fixed_operating_cost" : 0, + "variable_operating_cost" : 0, + "fixed_charge_rate" : 0, + "base_capital_cost" : 0, + "base_fixed_operating_cost" : 0, + "base_variable_operating_cost" : 0, + "system_capacity": 0, } for dset in output_request: truth = truth_vals[dset] @@ -207,7 +223,13 @@ def test_per_kw_cost_inputs(sample_resource_data): truth_vals = { "capital_cost": 383_086_656, "fixed_operating_cost": 25539104, + "variable_operating_cost" : 0, "lcoe_fcr": 72.5092, + "fixed_charge_rate" : 0.098000000000000004, + "base_capital_cost" : 383_086_656, + "base_fixed_operating_cost" : 25539104, + "base_variable_operating_cost" : 0, + "system_capacity": 383_086_656 / 3_000, } for dset in output_request: truth = truth_vals[dset] @@ -257,7 +279,12 @@ def test_drill_cost_inputs(sample_resource_data): truth_vals = { "capital_cost": 466_134_733, "fixed_operating_cost": 25539104, + "variable_operating_cost" : 0, "lcoe_fcr": 81.8643, + "fixed_charge_rate" : 0.098000000000000004, + "base_capital_cost" : 466_134_733, + "base_fixed_operating_cost" : 25539104, + "base_variable_operating_cost" : 0, } for dset in output_request: truth = truth_vals[dset] @@ -315,6 +342,14 @@ def test_gen_with_nameplate_input(sample_resource_data): "lcoe_fcr": 62.613, "nameplate": 40_000, "resource_temp": 150, + "capital_cost" : 172485199.53989035, + "fixed_operating_cost" : 4885201.7298879623, + "variable_operating_cost" : 0, + "fixed_charge_rate" : 0.098000000000000004, + "base_capital_cost" : 172485199.53989035, + "base_fixed_operating_cost" : 4885201.7298879623, + "base_variable_operating_cost" : 0, + "system_capacity": 40_000, } for dset in output_request: truth = truth_vals[dset] From 4d27801bb166bfbe46bd5e38cc976a834c343272 Mon Sep 17 00:00:00 2001 From: ppinchuk Date: Tue, 18 Jun 2024 14:21:29 -0600 Subject: [PATCH 8/8] Linter fix --- tests/test_gen_geothermal.py | 62 ++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/test_gen_geothermal.py b/tests/test_gen_geothermal.py index 2b23cfcef..d4159e407 100644 --- a/tests/test_gen_geothermal.py +++ b/tests/test_gen_geothermal.py @@ -103,13 +103,13 @@ def test_gen_geothermal(depth, sample_resource_data): "lcoe_fcr": 12.52, "nameplate": 200_000, "resource_temp": 150, - "capital_cost" : 172485199.53989035, - "fixed_operating_cost" : 4885201.7298879623, - "variable_operating_cost" : 0, - "fixed_charge_rate" : 0.098000000000000004, - "base_capital_cost" : 172485199.53989035, - "base_fixed_operating_cost" : 4885201.7298879623, - "base_variable_operating_cost" : 0, + "capital_cost": 172485199.53989035, + "fixed_operating_cost": 4885201.7298879623, + "variable_operating_cost": 0, + "fixed_charge_rate": 0.098000000000000004, + "base_capital_cost": 172485199.53989035, + "base_fixed_operating_cost": 4885201.7298879623, + "base_variable_operating_cost": 0, "system_capacity": 200_000, } for dset in output_request: @@ -164,13 +164,13 @@ def test_gen_geothermal_temp_too_low(sample_resource_data): "lcoe_fcr": 0, "nameplate": 0, "resource_temp": 60, - "capital_cost" : 0, - "fixed_operating_cost" : 0, - "variable_operating_cost" : 0, - "fixed_charge_rate" : 0, - "base_capital_cost" : 0, - "base_fixed_operating_cost" : 0, - "base_variable_operating_cost" : 0, + "capital_cost": 0, + "fixed_operating_cost": 0, + "variable_operating_cost": 0, + "fixed_charge_rate": 0, + "base_capital_cost": 0, + "base_fixed_operating_cost": 0, + "base_variable_operating_cost": 0, "system_capacity": 0, } for dset in output_request: @@ -223,12 +223,12 @@ def test_per_kw_cost_inputs(sample_resource_data): truth_vals = { "capital_cost": 383_086_656, "fixed_operating_cost": 25539104, - "variable_operating_cost" : 0, + "variable_operating_cost": 0, "lcoe_fcr": 72.5092, - "fixed_charge_rate" : 0.098000000000000004, - "base_capital_cost" : 383_086_656, - "base_fixed_operating_cost" : 25539104, - "base_variable_operating_cost" : 0, + "fixed_charge_rate": 0.098000000000000004, + "base_capital_cost": 383_086_656, + "base_fixed_operating_cost": 25539104, + "base_variable_operating_cost": 0, "system_capacity": 383_086_656 / 3_000, } for dset in output_request: @@ -279,12 +279,12 @@ def test_drill_cost_inputs(sample_resource_data): truth_vals = { "capital_cost": 466_134_733, "fixed_operating_cost": 25539104, - "variable_operating_cost" : 0, + "variable_operating_cost": 0, "lcoe_fcr": 81.8643, - "fixed_charge_rate" : 0.098000000000000004, - "base_capital_cost" : 466_134_733, - "base_fixed_operating_cost" : 25539104, - "base_variable_operating_cost" : 0, + "fixed_charge_rate": 0.098000000000000004, + "base_capital_cost": 466_134_733, + "base_fixed_operating_cost": 25539104, + "base_variable_operating_cost": 0, } for dset in output_request: truth = truth_vals[dset] @@ -342,13 +342,13 @@ def test_gen_with_nameplate_input(sample_resource_data): "lcoe_fcr": 62.613, "nameplate": 40_000, "resource_temp": 150, - "capital_cost" : 172485199.53989035, - "fixed_operating_cost" : 4885201.7298879623, - "variable_operating_cost" : 0, - "fixed_charge_rate" : 0.098000000000000004, - "base_capital_cost" : 172485199.53989035, - "base_fixed_operating_cost" : 4885201.7298879623, - "base_variable_operating_cost" : 0, + "capital_cost": 172485199.53989035, + "fixed_operating_cost": 4885201.7298879623, + "variable_operating_cost": 0, + "fixed_charge_rate": 0.098000000000000004, + "base_capital_cost": 172485199.53989035, + "base_fixed_operating_cost": 4885201.7298879623, + "base_variable_operating_cost": 0, "system_capacity": 40_000, } for dset in output_request: