Skip to content

Commit

Permalink
Merge pull request #2 from kbrunik/eco-sys-kb
Browse files Browse the repository at this point in the history
Update System Cost Methods
  • Loading branch information
jaredthomas68 authored Jan 30, 2024
2 parents 54c306e + 572cd77 commit 76da019
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 9 deletions.
28 changes: 19 additions & 9 deletions hopp/simulation/hybrid_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,16 +311,18 @@ def setup_cost_calculator(self, cost_calculator: object):

def set_om_costs_per_kw(self, pv_om_per_kw=None, wind_om_per_kw=None,
tower_om_per_kw=None, trough_om_per_kw=None,
wave_om_per_kw=None,
wave_om_per_kw=None, battery_om_per_kw=None,
hybrid_om_per_kw=None):
"""
Sets Capacity-based O&M amount for each technology [$/kWcap].
"""
# TODO: Remove??? This doesn't seem to be used.
# TODO: fix this error statement it doesn't work
# om_vals = [pv_om_per_kw, wind_om_per_kw, tower_om_per_kw, trough_om_per_kw, wave_om_per_kw, hybrid_om_per_kw]
# techs = ["pv", "wind", "tower", "trough", "wave", "hybrid"]
# om_lengths = {tech + "_om_per_kw" : om_val for om_val, tech in zip(om_vals, techs)}
# if len(set(om_lengths.values())) != 1 and len(set(om_lengths.values())) is not None:
# raise ValueError(f"Length of yearly om cost per kw arrays must be equal. Some lengths of om_per_kw values are different from others: {om_lengths}")

if pv_om_per_kw and self.pv:
self.pv.om_capacity = pv_om_per_kw

Expand All @@ -336,6 +338,9 @@ def set_om_costs_per_kw(self, pv_om_per_kw=None, wind_om_per_kw=None,
if wave_om_per_kw and self.wave:
self.wave.om_capacity = wave_om_per_kw

if battery_om_per_kw and self.battery:
self.battery.om_capacity = battery_om_per_kw

if hybrid_om_per_kw:
self.grid.om_capacity = hybrid_om_per_kw

Expand Down Expand Up @@ -382,16 +387,21 @@ def calculate_installed_cost(self):
battery_mwh = self.battery.system_capacity_kwh / 1000

# TODO: add tower and trough to cost_model functionality
pv_cost, wind_cost, storage_cost, total_cost = self.cost_model.calculate_total_costs(wind_mw,
pv_mw,
battery_mw,
battery_mwh)
# pv_cost, wind_cost, storage_cost, total_cost = self.cost_model.calculate_total_costs(wind_mw,
# pv_mw,
# battery_mw,
# battery_mwh)
total_cost = 0

if self.pv:
self.pv.total_installed_cost = pv_cost
self.pv.total_installed_cost = self.pv.calculate_total_installed_cost()
total_cost += self.pv.total_installed_cost
if self.wind:
self.wind.total_installed_cost = wind_cost
self.wind.total_installed_cost = self.wind.calculate_total_installed_cost()
total_cost += self.wind.total_installed_cost
if self.battery:
self.battery.total_installed_cost = storage_cost
self.battery.total_installed_cost = self.battery.calculate_total_installed_cost()
total_cost += self.battery.total_installed_cost
if self.wave:
self.wave.total_installed_cost = self.wave.calculate_total_installed_cost()
total_cost += self.wave.total_installed_cost
Expand Down
23 changes: 23 additions & 0 deletions hopp/simulation/technologies/battery/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,29 @@ def validate_replacement_inputs(self, project_life):
self._financial_model.value('batt_bank_replacement', [0] + list(self._financial_model.value('batt_bank_replacement')))
else:
raise ValueError(f"Error in Battery model: `batt_bank_replacement` should be length of project_life {project_life} but is instead {len(self._financial_model.value('batt_bank_replacement'))}")

def set_overnight_capital_cost(self, energy_capital_cost, power_capital_cost):
"""Set overnight capital costs [$/kW].
This method calculates and sets the overnight capital cost based on the given energy and power capital costs.
Args:
energy_capital_cost (float): The capital cost per unit of energy capacity [$/kWh].
power_capital_cost (float): The capital cost per unit of power capacity [$/kW].
Returns:
None: This method does not return any value. The calculated overnight capital cost is stored internally.
Note:
The overnight capital cost is calculated using the formula:
`overnight_capital_cost = (energy_capital_cost * hours) + power_capital_cost`
where `hours` is the ratio of energy capacity to power capacity.
Example:
>>> set_overnight_capital_cost(1500, 500)
"""
hours = self.system_capacity_kwh/self.system_capacity_kw
self._overnight_capital_cost = (energy_capital_cost * hours) + power_capital_cost

def simulate_financials(
self,
Expand Down
10 changes: 10 additions & 0 deletions hopp/simulation/technologies/power_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,16 @@ def simulate(self, interconnect_kw: float, project_life: int = 25, lifetime_sim=
self.simulate_financials(interconnect_kw, project_life)
logger.info(f"{self.name} simulation executed with AEP {self.annual_energy_kwh}")

def set_overnight_capital_cost(self, overnight_capital_cost):
"""Set overnight capital costs [$/kW]."""
self._overnight_capital_cost = overnight_capital_cost

def calculate_total_installed_cost(self) -> float:
if isinstance(self._financial_model, Singleowner.Singleowner):
return self._financial_model.SystemCosts.total_installed_cost
else:
total_installed_cost = self.system_capacity_kw * self._overnight_capital_cost
return self._financial_model.value("total_installed_cost", total_installed_cost)
#
# Inputs
#
Expand Down
96 changes: 96 additions & 0 deletions tests/hopp/test_hybrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,102 @@ def test_hybrid_pv_only(hybrid_config):
assert npvs.pv == approx(-5121293, 1e3)
assert npvs.hybrid == approx(-5121293, 1e3)

def test_hybrid_pv_only_custom_fin(hybrid_config, subtests):
solar_only = {
'pv': {
'system_capacity_kw': 5000,
'layout_params': {
'x_position': 0.5,
'y_position': 0.5,
'aspect_power': 0,
'gcr': 0.5,
's_buffer': 2,
'x_buffer': 2,
},
'dc_degradation': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'fin_model': DEFAULT_FIN_CONFIG
},
'grid':{
'interconnect_kw': interconnection_size_kw,
'fin_model': DEFAULT_FIN_CONFIG,
}
}
hybrid_config["technologies"] = solar_only
hi = HoppInterface(hybrid_config)

hybrid_plant = hi.system
hybrid_plant.pv.set_overnight_capital_cost(400)
hybrid_plant.set_om_costs_per_kw(pv_om_per_kw=20)

hi.simulate()

aeps = hybrid_plant.annual_energies
npvs = hybrid_plant.net_present_values
cf = hybrid_plant.capacity_factors

with subtests.test("total installed cost"):
assert hybrid_plant.pv.total_installed_cost == approx(2000000,1e-3)

with subtests.test("om cost"):
assert hybrid_plant.pv.om_capacity == (20,)

with subtests.test("capacity factor"):
assert cf.hybrid == approx(cf.pv)

with subtests.test("aep"):
assert aeps.pv == approx(9884106.55, 1e-3)
assert aeps.hybrid == aeps.pv

def test_hybrid_pv_battery_custom_fin(hybrid_config, subtests):
tech = {
'pv': {
'system_capacity_kw': 5000,
'layout_params': {
'x_position': 0.5,
'y_position': 0.5,
'aspect_power': 0,
'gcr': 0.5,
's_buffer': 2,
'x_buffer': 2,
},
'dc_degradation': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'fin_model': DEFAULT_FIN_CONFIG
},
'battery': {
'system_capacity_kw': 5000,
'system_capacity_kwh': 20000,
'fin_model': DEFAULT_FIN_CONFIG
},
'grid':{
'interconnect_kw': interconnection_size_kw,
'fin_model': DEFAULT_FIN_CONFIG,
}
}
hybrid_config["technologies"] = tech
hi = HoppInterface(hybrid_config)

hybrid_plant = hi.system
hybrid_plant.pv.set_overnight_capital_cost(400)
hybrid_plant.battery.set_overnight_capital_cost(300,200)
hybrid_plant.set_om_costs_per_kw(pv_om_per_kw=20,battery_om_per_kw=30)

hi.simulate()

aeps = hybrid_plant.annual_energies
npvs = hybrid_plant.net_present_values
cf = hybrid_plant.capacity_factors

with subtests.test("pv total installed cost"):
assert hybrid_plant.pv.total_installed_cost == approx(2000000,1e-3)

with subtests.test("pv om cost"):
assert hybrid_plant.pv.om_capacity == (20,)

with subtests.test("battery total installed cost"):
assert hybrid_plant.battery.total_installed_cost == approx(7000000,1e-3)

with subtests.test("battery om cost"):
assert hybrid_plant.battery.om_capacity == (30,)

def test_detailed_pv_system_capacity(hybrid_config, subtests):
with subtests.test("Detailed PV model (pvsamv1) using defaults except the top level system_capacity_kw parameter"):
Expand Down

0 comments on commit 76da019

Please sign in to comment.