Skip to content

Commit

Permalink
[WIP] Adding new cost outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
ppinchuk committed Jun 11, 2024
1 parent 1f12274 commit 371a31b
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 34 deletions.
57 changes: 45 additions & 12 deletions reV/econ/economies_of_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,33 @@ def capital_cost_scalar(self):
"""
return self._evaluate()

def _cost_from_cap(self, col_name):
"""Get full cost value from cost per mw in data.
Parameters
----------
col_name : str
Name of column containing the cost per mw value.
Returns
-------
float | None
Cost value if it was found in data, ``None`` otherwise.
"""
cap = self._data.get(SupplyCurveField.CAPACITY_AC_MW)
if cap is None:
return None

cost_per_mw = self._data.get(col_name)
if cost_per_mw is None:
return None

cost = cap * cost_per_mw
if cost > 0:
return cost

return None

@property
def raw_capital_cost(self):
"""Unscaled (raw) capital cost found in the data input arg.
Expand All @@ -199,6 +226,12 @@ def raw_capital_cost(self):
out : float | np.ndarray
Unscaled (raw) capital_cost found in the data input arg.
"""
raw_capital_cost_from_cap = self._cost_from_cap(
SupplyCurveField.COST_SITE_OCC_USD_PER_AC_MW
)
if raw_capital_cost_from_cap is not None:
return raw_capital_cost_from_cap

key_list = ["capital_cost", "mean_capital_cost"]
return self._get_prioritized_keys(self._data, key_list)

Expand All @@ -217,18 +250,6 @@ def scaled_capital_cost(self):
cc *= self.capital_cost_scalar
return cc

@property
def system_capacity(self):
"""Get the system capacity in kW (SAM input, not the reV supply
curve capacity).
Returns
-------
out : float | np.ndarray
"""
key_list = ["system_capacity", "mean_system_capacity"]
return self._get_prioritized_keys(self._data, key_list)

@property
def fcr(self):
"""Fixed charge rate from input data arg
Expand All @@ -251,6 +272,12 @@ def foc(self):
out : float | np.ndarray
Fixed operating cost from input data arg
"""
foc_from_cap = self._cost_from_cap(
SupplyCurveField.COST_BASE_FOC_USD_PER_AC_MW
)
if foc_from_cap is not None:
return foc_from_cap

key_list = ["fixed_operating_cost", "mean_fixed_operating_cost",
"foc", "mean_foc"]
return self._get_prioritized_keys(self._data, key_list)
Expand All @@ -264,6 +291,12 @@ def voc(self):
out : float | np.ndarray
Variable operating cost from input data arg
"""
voc_from_cap = self._cost_from_cap(
SupplyCurveField.COST_BASE_FOC_USD_PER_AC_MW
)
if voc_from_cap is not None:
return voc_from_cap

key_list = ["variable_operating_cost", "mean_variable_operating_cost",
"voc", "mean_voc"]
return self._get_prioritized_keys(self._data, key_list)
Expand Down
97 changes: 80 additions & 17 deletions reV/supply_curve/points.py
Original file line number Diff line number Diff line change
Expand Up @@ -1504,7 +1504,7 @@ def __init__(
recalc_lcoe : bool
Flag to re-calculate the LCOE from the multi-year mean capacity
factor and annual energy production data. This requires several
datasets to be aggregated in the h5_dsets input: system_capacity,
datasets to be aggregated in the gen input: system_capacity,
fixed_charge_rate, capital_cost, fixed_operating_cost,
and variable_operating_cost.
apply_exclusions : bool
Expand All @@ -1525,6 +1525,8 @@ def __init__(
self._power_density = self._power_density_ac = power_density
self._friction_layer = friction_layer
self._recalc_lcoe = recalc_lcoe
self._ssc = None
self._slk = {}

super().__init__(
gid,
Expand Down Expand Up @@ -1816,30 +1818,24 @@ def mean_lcoe(self):
# year CF, but the output should be identical to the original LCOE and
# so is not consequential).
if self._recalc_lcoe:
required = (
"fixed_charge_rate",
"capital_cost",
"fixed_operating_cost",
"variable_operating_cost",
"system_capacity",
)
if self.mean_h5_dsets_data is not None and all(
k in self.mean_h5_dsets_data for k in required
):
required = ("fixed_charge_rate", "capital_cost",
"fixed_operating_cost", "variable_operating_cost",
"system_capacity")
if all(self._sam_lcoe_kwargs.get(k) is not None for k in required):
aep = (
self.mean_h5_dsets_data["system_capacity"]
self._sam_lcoe_kwargs["system_capacity"]
* self.mean_cf
* 8760
)
# Note the AEP computation uses the SAM config
# `system_capacity`, so no need to scale `capital_cost`
# or `fixed_operating_cost` by anything
mean_lcoe = lcoe_fcr(
self.mean_h5_dsets_data["fixed_charge_rate"],
self.mean_h5_dsets_data["capital_cost"],
self.mean_h5_dsets_data["fixed_operating_cost"],
self._sam_lcoe_kwargs["fixed_charge_rate"],
self._sam_lcoe_kwargs["capital_cost"],
self._sam_lcoe_kwargs["fixed_operating_cost"],
aep,
self.mean_h5_dsets_data["variable_operating_cost"],
self._sam_lcoe_kwargs["variable_operating_cost"],
)

# alternative if lcoe was not able to be re-calculated from
Expand Down Expand Up @@ -2194,6 +2190,55 @@ def regional_multiplier(self):
multipliers = self.gen["capital_cost_multiplier"]
return self.exclusion_weighted_mean(multipliers)

@property
def _sam_system_capacity(self):
"""float: Mean SAM generation system capacity input, defaults to 0. """
if self._ssc is not None:
return self._ssc

self._ssc = 0
if "system_capacity" in self.gen.datasets:
self._ssc = self.exclusion_weighted_mean(
self.gen["system_capacity"]
)

return self._ssc

@property
def _sam_lcoe_kwargs(self):
"""dict: Mean LCOE inputs, as passed to SAM during generation."""
if self._slk:
return self._slk

self._slk = {"capital_cost": None, "fixed_operating_cost": None,
"variable_operating_cost": None,
"fixed_charge_rate": None, "system_capacity": None}

for dset in self._slk:
if dset in self.gen.datasets:
self._slk[dset] = self.exclusion_weighted_mean(
self.gen[dset]
)

return self._slk

def _compute_cost_per_ac_mw(self, dset):
"""Compute a cost per AC MW for a given input. """
if self._sam_system_capacity <= 0:
return 0

if dset not in self.gen.datasets:
return 0

sam_cost = self.exclusion_weighted_mean(self.gen[dset])
sam_cost_per_mw = sam_cost / self._sam_system_capacity
sc_point_cost = sam_cost_per_mw * self.capacity

ac_cap = (self.capacity
if self.capacity_ac is None
else self.capacity_ac)
return sc_point_cost / ac_cap

@property
def mean_h5_dsets_data(self):
"""Get the mean supplemental h5 datasets data (optional)
Expand Down Expand Up @@ -2313,6 +2358,24 @@ def point_summary(self, args=None):
SupplyCurveField.SC_POINT_ANNUAL_ENERGY_MW: (
self.sc_point_annual_energy
),
SupplyCurveField.COST_SITE_OCC_USD_PER_AC_MW: (
self._compute_cost_per_ac_mw("capital_cost")
),
SupplyCurveField.COST_BASE_OCC_USD_PER_AC_MW: (
self._compute_cost_per_ac_mw("base_capital_cost")
),
SupplyCurveField.COST_SITE_FOC_USD_PER_AC_MW: (
self._compute_cost_per_ac_mw("fixed_operating_cost")
),
SupplyCurveField.COST_BASE_FOC_USD_PER_AC_MW: (
self._compute_cost_per_ac_mw("base_fixed_operating_cost")
),
SupplyCurveField.COST_SITE_VOC_USD_PER_AC_MW: (
self._compute_cost_per_ac_mw("variable_operating_cost")
),
SupplyCurveField.COST_BASE_VOC_USD_PER_AC_MW: (
self._compute_cost_per_ac_mw("base_variable_operating_cost")
)
}

extra_atts = {
Expand Down Expand Up @@ -2489,7 +2552,7 @@ def summarize(
recalc_lcoe : bool
Flag to re-calculate the LCOE from the multi-year mean capacity
factor and annual energy production data. This requires several
datasets to be aggregated in the h5_dsets input: system_capacity,
datasets to be aggregated in the gen input: system_capacity,
fixed_charge_rate, capital_cost, fixed_operating_cost,
and variable_operating_cost.
Expand Down
16 changes: 12 additions & 4 deletions reV/utilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,11 @@ class SupplyCurveField(FieldEnum):
OFFSHORE = "offshore"
SC_ROW_IND = "sc_row_ind"
SC_COL_IND = "sc_col_ind"
SC_POINT_CAPITAL_COST = "sc_point_capital_cost"
SC_POINT_FIXED_OPERATING_COST = "sc_point_fixed_operating_cost"
SC_POINT_ANNUAL_ENERGY_MW = "sc_point_annual_energy"
MEAN_FRICTION = "mean_friction"
MEAN_LCOE_FRICTION = "mean_lcoe_friction"
TOTAL_LCOE_FRICTION = "total_lcoe_friction"
RAW_LCOE = "raw_lcoe"
SCALED_CAPITAL_COST = "scaled_capital_cost"
SCALED_SC_POINT_CAPITAL_COST = "scaled_sc_point_capital_cost"
TURBINE_X_COORDS = "turbine_x_coords"
TURBINE_Y_COORDS = "turbine_y_coords"
EOS_MULT = "eos_mult"
Expand All @@ -162,6 +158,18 @@ class SupplyCurveField(FieldEnum):
CONVEX_HULL_CAPACITY_DENSITY = "convex_hull_capacity_density"
FULL_CELL_CAPACITY_DENSITY = "full_cell_capacity_density"

COST_BASE_OCC_USD_PER_AC_MW = "cost_base_occ_usd_per_ac_mw"
COST_SITE_OCC_USD_PER_AC_MW = "cost_site_occ_usd_per_ac_mw"
COST_BASE_FOC_USD_PER_AC_MW = "cost_base_foc_usd_per_ac_mw"
COST_SITE_FOC_USD_PER_AC_MW = "cost_site_foc_usd_per_ac_mw"
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"

SC_POINT_CAPITAL_COST = "sc_point_capital_cost"
SC_POINT_FIXED_OPERATING_COST = "sc_point_fixed_operating_cost"
SCALED_CAPITAL_COST = "scaled_capital_cost"
SCALED_SC_POINT_CAPITAL_COST = "scaled_sc_point_capital_cost"

@classmethod
def map_from_legacy(cls):
"""Map of legacy names to current values.
Expand Down
3 changes: 2 additions & 1 deletion tests/test_econ_of_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ def test_econ_of_scale_baseline():
sc_df[SupplyCurveField.MEAN_LCOE])
assert (sc_df[SupplyCurveField.EOS_MULT] == 1).all()
assert np.allclose(sc_df['mean_capital_cost'],
sc_df[SupplyCurveField.SCALED_CAPITAL_COST])
sc_df[SupplyCurveField.COST_SITE_OCC_USD_PER_AC_MW]
* 20000)


def test_sc_agg_econ_scale():
Expand Down

0 comments on commit 371a31b

Please sign in to comment.