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 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/reV/bespoke/bespoke.py b/reV/bespoke/bespoke.py index 0c38fa9c7..3632dfbe9 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( @@ -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 diff --git a/reV/supply_curve/points.py b/reV/supply_curve/points.py index 8f1c801f6..5076ac87b 100644 --- a/reV/supply_curve/points.py +++ b/reV/supply_curve/points.py @@ -2537,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 diff --git a/reV/supply_curve/supply_curve.py b/reV/supply_curve/supply_curve.py index 8c1d3f415..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, @@ -1289,7 +1288,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) 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" diff --git a/tests/test_gen_geothermal.py b/tests/test_gen_geothermal.py index f1fdedab2..d4159e407 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]