Skip to content

Commit

Permalink
Merge pull request #447 from NREL/pp/report_scaled_capital_cost
Browse files Browse the repository at this point in the history
Report scaled capital cost in output supply curve
  • Loading branch information
ppinchuk authored Mar 7, 2024
2 parents 4a0fd31 + a8b55a0 commit e1cb102
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 11 deletions.
108 changes: 103 additions & 5 deletions reV/supply_curve/points.py
Original file line number Diff line number Diff line change
Expand Up @@ -1585,6 +1585,9 @@ def mean_lcoe(self):
if all(k in self.mean_h5_dsets_data for k in required):
aep = (self.mean_h5_dsets_data['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'],
Expand Down Expand Up @@ -1796,6 +1799,94 @@ def capacity_ac(self):

return self.area * self.power_density_ac

@property
def sc_point_capital_cost(self):
"""Get the capital cost for the entire SC point.
This method scales the capital cost based on the included-area
capacity. The calculation requires 'capital_cost' and
'system_capacity' in the generation file and passed through as
`h5_dsets`, otherwise it returns `None`.
Returns
-------
sc_point_capital_cost : float | None
Total supply curve point capital cost ($).
"""
if self.mean_h5_dsets_data is None:
return None

required = ('capital_cost', 'system_capacity')
if not all(k in self.mean_h5_dsets_data for k in required):
return None

cap_cost_per_mw = (self.mean_h5_dsets_data['capital_cost']
/ self.mean_h5_dsets_data['system_capacity'])
return cap_cost_per_mw * self.capacity

@property
def sc_point_fixed_operating_cost(self):
"""Get the fixed operating cost for the entire SC point.
This method scales the fixed operating cost based on the
included-area capacity. The calculation requires
'fixed_operating_cost' and 'system_capacity' in the generation
file and passed through as `h5_dsets`, otherwise it returns
`None`.
Returns
-------
sc_point_fixed_operating_cost : float | None
Total supply curve point fixed operating cost ($).
"""
if self.mean_h5_dsets_data is None:
return None

required = ('fixed_operating_cost', 'system_capacity')
if not all(k in self.mean_h5_dsets_data for k in required):
return None

fixed_cost_per_mw = (self.mean_h5_dsets_data['fixed_operating_cost']
/ self.mean_h5_dsets_data['system_capacity'])
return fixed_cost_per_mw * self.capacity

@property
def sc_point_annual_energy(self):
"""Get the total annual energy (MWh) for the entire SC point.
This value is computed using the capacity of the supply curve
point as well as the mean capacity factor. If the mean capacity
factor is `None`, this value will also be `None`.
Returns
-------
sc_point_annual_energy : float | None
Total annual energy (MWh) for the entire SC point.
"""
if self.mean_cf is None:
return None

return self.mean_cf * self.capacity * 8760

@property
def sc_point_annual_energy_ac(self):
"""Get the total AC annual energy (MWh) for the entire SC point.
This value is computed using the AC capacity of the supply curve
point as well as the mean capacity factor. If either the mean
capacity factor or the AC capacity value is `None`, this value
will also be `None`.
Returns
-------
sc_point_annual_energy_ac : float | None
Total AC annual energy (MWh) for the entire SC point.
"""
if self.mean_cf is None or self.capacity_ac is None:
return None

return self.mean_cf * self.capacity_ac * 8760

@property
def h5_dsets_data(self):
"""Get any additional/supplemental h5 dataset data to summarize.
Expand Down Expand Up @@ -1928,11 +2019,13 @@ def point_summary(self, args=None):
'timezone': self.timezone,
}

if self.capacity_ac is not None:
ARGS['capacity_ac'] = self.capacity_ac

if self.offshore is not None:
ARGS['offshore'] = self.offshore
extra_atts = ['capacity_ac', 'offshore', 'sc_point_capital_cost',
'sc_point_fixed_operating_cost',
'sc_point_annual_energy', 'sc_point_annual_energy_ac']
for attr in extra_atts:
value = getattr(self, attr)
if value is not None:
ARGS[attr] = value

if self._friction_layer is not None:
ARGS['mean_friction'] = self.mean_friction
Expand Down Expand Up @@ -1980,6 +2073,11 @@ def economies_of_scale(cap_cost_scale, summary):
summary['raw_lcoe'] = eos.raw_lcoe
summary['mean_lcoe'] = eos.scaled_lcoe
summary['capital_cost_scalar'] = eos.capital_cost_scalar
summary['scaled_capital_cost'] = eos.scaled_capital_cost
if "sc_point_capital_cost" in summary:
scaled_costs = (summary["sc_point_capital_cost"]
* eos.capital_cost_scalar)
summary['scaled_sc_point_capital_cost'] = scaled_costs

return summary

Expand Down
2 changes: 1 addition & 1 deletion reV/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
reV Version number
"""

__version__ = "0.8.6"
__version__ = "0.8.7"
4 changes: 4 additions & 0 deletions tests/test_econ_of_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ def test_econ_of_scale_baseline():
sc_df = pd.read_csv(out_fp_sc + ".csv")
assert np.allclose(base_df['mean_lcoe'], sc_df['mean_lcoe'])
assert (sc_df['capital_cost_scalar'] == 1).all()
assert np.allclose(sc_df['mean_capital_cost'],
sc_df['scaled_capital_cost'])


def test_sc_agg_econ_scale():
Expand Down Expand Up @@ -225,6 +227,8 @@ def test_sc_agg_econ_scale():
/ aep + data['variable_operating_cost'])

assert np.allclose(scalars, sc_df['capital_cost_scalar'])
assert np.allclose(scalars * sc_df['mean_capital_cost'],
sc_df['scaled_capital_cost'])

assert np.allclose(true_scaled_lcoe, sc_df['mean_lcoe'])
assert np.allclose(true_raw_lcoe, sc_df['raw_lcoe'])
Expand Down
26 changes: 21 additions & 5 deletions tests/test_supply_curve_sc_aggregation.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def test_agg_summary():

summary = summary.fillna('None')
s_baseline = s_baseline.fillna('None')
summary = summary[list(s_baseline.columns)]

assert_frame_equal(summary, s_baseline, check_dtype=False, rtol=0.0001)

Expand Down Expand Up @@ -205,6 +206,7 @@ def test_pre_extract_inclusions(pre_extract):

summary = summary.fillna('None')
s_baseline = s_baseline.fillna('None')
summary = summary[list(s_baseline.columns)]

assert_frame_equal(summary, s_baseline, check_dtype=False, rtol=0.0001)

Expand Down Expand Up @@ -356,7 +358,9 @@ def test_data_layer_methods():
assert slope_min <= slope_mean <= slope_max


def test_recalc_lcoe():
@pytest.mark.parametrize("cap_cost_scale",
['1', '2 * np.multiply(1000, capacity) ** -0.3'])
def test_recalc_lcoe(cap_cost_scale):
"""Test supply curve aggregation with the re-calculation of lcoe using the
multi-year mean capacity factor"""

Expand Down Expand Up @@ -401,28 +405,40 @@ def test_recalc_lcoe():
res.create_dataset('lcoe_fcr-means',
res['meta'].shape, data=lcoe_arr)

h5_dsets = ('capital_cost', 'fixed_operating_cost',
h5_dsets = ['capital_cost', 'fixed_operating_cost',
'fixed_charge_rate', 'variable_operating_cost',
'system_capacity')
'system_capacity']

base = SupplyCurveAggregation(EXCL, TM_DSET, excl_dict=EXCL_DICT,
res_class_dset=None, res_class_bins=None,
data_layers=DATA_LAYERS,
h5_dsets=h5_dsets,
gids=list(np.arange(10)),
recalc_lcoe=False)
recalc_lcoe=False,
cap_cost_scale=cap_cost_scale)
summary_base = base.summarize(gen_temp, max_workers=1)

sca = SupplyCurveAggregation(EXCL, TM_DSET, excl_dict=EXCL_DICT,
res_class_dset=None, res_class_bins=None,
data_layers=DATA_LAYERS,
h5_dsets=h5_dsets,
gids=list(np.arange(10)),
recalc_lcoe=True)
recalc_lcoe=True,
cap_cost_scale=cap_cost_scale)
summary = sca.summarize(gen_temp, max_workers=1)

assert not np.allclose(summary_base['mean_lcoe'], summary['mean_lcoe'])

if cap_cost_scale == '1':
cc_dset = 'sc_point_capital_cost'
else:
cc_dset = 'scaled_sc_point_capital_cost'
lcoe = lcoe_fcr(summary['mean_fixed_charge_rate'], summary[cc_dset],
summary['sc_point_fixed_operating_cost'],
summary['sc_point_annual_energy'],
summary['mean_variable_operating_cost'])
assert np.allclose(lcoe, summary['mean_lcoe'])


@pytest.mark.parametrize('tm_dset', ("techmap_ri", "techmap_ri_new"))
@pytest.mark.parametrize('pre_extract', (True, False))
Expand Down

0 comments on commit e1cb102

Please sign in to comment.