Source code for wisdem.plant_financese.plant_finance

import openmdao.api as om


[docs] class PlantFinance(om.ExplicitComponent): """ Compute LCOE for the wind plant, formulas from https://doi.org/10.1016/j.tej.2021.106931 Parameters ---------- machine_rating : float Rating of the turbine tcc_per_kW : float Turbine capital cost offset_tcc_per_kW : float Offset to turbine capital cost turbine_number : int Number of turbines at plant bos_per_kW : float Balance of system costs of the turbine opex_per_kW : float Average annual operational expenditures of the turbine plant_aep_in : float Annual Energy Production of the wind plant turbine_aep : float Annual Energy Production of the wind turbine wake_loss_factor : float The losses in AEP due to waked conditions fixed_charge_rate : float Fixed charge rate for coe calculation electricity_price : float Electricity price reserve_margin_price : float Reserve margin price capacity_credit : float Capacity credit benchmark_price : float Benchmark price Returns ------- plant_aep : float Annual Energy Production of the wind plant capacity_factor : float Capacity factor of the wind plant lcoe : float Levelized cost of energy for the wind plant lvoe : float Levelized value of energy value_factor : float Value factor nvoc : float Net value of capacity nvoe : float Net value of energy slcoe : float System LCOE bcr : float Benefit cost ratio cbr : float Cost benefit ratio roi : float Return of investment pm : float Profit margin plcoe : float Profitability adjusted LCOE """ def initialize(self): self.options.declare("verbosity", default=False) def setup(self): # Inputs self.add_input("machine_rating", val=0.0, units="kW") self.add_input("tcc_per_kW", val=0.0, units="USD/kW") self.add_input("offset_tcc_per_kW", val=0.0, units="USD/kW") self.add_discrete_input("turbine_number", val=0) self.add_input("bos_per_kW", val=0.0, units="USD/kW") self.add_input("opex_per_kW", val=0.0, units="USD/kW/yr") self.add_input("plant_aep_in", val=0.0, units="kW*h") self.add_input("turbine_aep", val=0.0, units="kW*h") self.add_input("wake_loss_factor", val=0.15) self.add_input("fixed_charge_rate", val=0.075) self.add_input("electricity_price", val=0.04, units="USD/kW/h") self.add_input("reserve_margin_price", val=120.0, units="USD/kW/yr") self.add_input("capacity_credit", val=1.0) self.add_input("benchmark_price", val=0.071, units="USD/kW/h") self.add_output("plant_aep", val=0.0, units="USD/kW/h") self.add_output("capacity_factor", val=0.0, desc="Capacity factor of the wind farm") self.add_output( "lcoe", val=0.0, units="USD/kW/h", desc="Levelized cost of energy: LCOE is the cost that, if assigned to every unit of electricity by an asset over an evaluation period, will equal the total costs during that same period when discounted to the base year.", ) self.add_output( "lvoe", val=0.0, units="USD/kW/h", desc="Levelized value of energy: LVOE is the discounted sum of total value divided by the discounted sum of electrical energy generated.", ) self.add_output("value_factor", val=0.0, desc="Value factor is the LVOE divided by a benchmark price.") self.add_output( "nvoc", val=0.0, units="USD/kW/yr", desc="Net value of capacity: NVOC is the difference in an asset’s total annualized value and annualized cost, divided by the installed capacity of the asset. NVOC ≥ 0 for economic viability.", ) self.add_output( "nvoe", val=0.0, units="USD/kW/h", desc="Net value of energy: NVOE is the difference between LVOE and LCOE. NVOE ≥ 0 for economic viability.", ) self.add_output( "slcoe", val=0.0, units="USD/kW/h", desc="System LCOE: SLCOE is the negative of NVOE but further adjusted by a benchmark price. System LCOE ≤ benchmark price for economic viability.", ) self.add_output( "bcr", val=0.0, desc="Benefit cost ratio: BCR is the discounted sum of total value divided by the discounted sum of total cost. A higher BCR is more competitive. BCR ≥ 1 for economic viability", ) self.add_output( "cbr", val=0.0, desc="Cost benefit ratio: CBR is the inverse of BCR. CBR ≤ 1 for economic viability. A lower CBR is more competitive.", ) self.add_output( "roi", val=0.0, desc="Return on investment: ROI can also be expressed as BCR – 1. A higher ROI is more competitive. ROI ≥ 0 for economic viability.", ) self.add_output( "pm", val=0.0, desc="Profit margin: PM can also be expressed as 1 - CBR. A higher PM is more competitive. PM ≥ 0 for economic viability.", ) self.add_output( "plcoe", val=0.0, units="USD/kW/h", desc="Profitability adjusted PLCOE is the product of a benchmark price and CBR, which is equal to LCOE divided by value factor. A lower PLCOE is more competitive. PLCOE ≤ benchmark price for economic viability.", ) self.declare_partials("*", "*") def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Unpack parameters t_rating = inputs["machine_rating"] n_turbine = discrete_inputs["turbine_number"] tcc_per_kW = inputs["tcc_per_kW"] + inputs["offset_tcc_per_kW"] bos_per_kW = inputs["bos_per_kW"] opex_per_kW = inputs["opex_per_kW"] fcr = inputs["fixed_charge_rate"] wlf = inputs["wake_loss_factor"] turb_aep = inputs["turbine_aep"] electricity_price = inputs["electricity_price"] reserve_margin_price = inputs["reserve_margin_price"] capacity_credit = inputs["capacity_credit"] benchmark_price = inputs["benchmark_price"] c_turbine = tcc_per_kW * t_rating c_bos_turbine = bos_per_kW * t_rating c_opex_turbine = opex_per_kW * t_rating # Run a few checks on the inputs if n_turbine == 0: raise ValueError( "ERROR: The number of the turbines in the plant is not initialized correctly and it is currently equal to 0. Check the connections to Plant_FinanceSE" ) if c_turbine == 0: raise ValueError( "ERROR: The cost of the turbines in the plant is not initialized correctly and it is currently equal to 0 USD. Check the connections to Plant_FinanceSE" ) if c_bos_turbine == 0: print( "WARNING: The BoS costs of the turbine are not initialized correctly and they are currently equal to 0 USD. Check the connections to Plant_FinanceSE" ) if c_opex_turbine == 0: print( "WARNING: The Opex costs of the turbine are not initialized correctly and they are currently equal to 0 USD. Check the connections to Plant_FinanceSE" ) if inputs["plant_aep_in"] == 0: if turb_aep == 0: print( "WARNING: AEP is not computed properly or it is not connected to PlantFinanceSE. Both turbine_aep and park_aep are currently equal to 0 Wh. Check the connections to Plant_FinanceSE" ) park_aep = n_turbine * turb_aep * (1.0 - wlf) dpark_dtaep = n_turbine * (1.0 - wlf) dpark_dnturb = turb_aep * (1.0 - wlf) dpark_dwlf = -n_turbine * turb_aep dpark_dpaep = 0.0 else: park_aep = inputs["plant_aep_in"] dpark_dpaep = 1.0 dpark_dtaep = dpark_dnturb = dpark_dwlf = 0.0 npr = n_turbine * t_rating # net park rating, used in net energy capture calculation below dnpr_dnturb = t_rating dnpr_dtrating = n_turbine nec = park_aep / npr # net energy rating, per COE report dnec_dwlf = dpark_dwlf / npr dnec_dtaep = dpark_dtaep / npr dnec_dpaep = dpark_dpaep / npr dnec_dnturb = dpark_dnturb / npr - dnpr_dnturb * nec / npr dnec_dtrating = -dnpr_dtrating * nec / npr icc = (c_turbine + c_bos_turbine) / t_rating # $/kW, changed per COE report c_opex = (c_opex_turbine) / t_rating # $/kW, changed per COE report dicc_dtrating = -icc / t_rating dcopex_dtrating = -c_opex / t_rating dicc_dcturb = dicc_dcbos = dcopex_dcopex = 1.0 / t_rating C = icc * fcr + c_opex E = nec V = nec * electricity_price + reserve_margin_price * capacity_credit lcoe = C / E lvoe = V / E value_factor = lvoe / benchmark_price nvoc = V - C nvoe = lvoe - lcoe slcoe = benchmark_price - nvoe bcr = V / C cbr = C / V roi = (V - C) / C pm = (V - C) / V plcoe = C / V * benchmark_price # Assign openmdao outputs outputs["plant_aep"] = park_aep outputs["capacity_factor"] = nec / 8760.0 outputs["lcoe"] = lcoe outputs["lvoe"] = lvoe outputs["value_factor"] = value_factor outputs["nvoc"] = nvoc outputs["nvoe"] = nvoe outputs["slcoe"] = slcoe outputs["bcr"] = bcr outputs["cbr"] = cbr outputs["roi"] = roi outputs["pm"] = pm outputs["plcoe"] = plcoe self.J = {} self.J["lcoe", "tcc_per_kW"] = dicc_dcturb * fcr / nec self.J["lcoe", "turbine_number"] = -dnec_dnturb * lcoe / nec self.J["lcoe", "bos_per_kW"] = dicc_dcbos * fcr / nec self.J["lcoe", "opex_per_kW"] = dcopex_dcopex / nec self.J["lcoe", "fixed_charge_rate"] = icc / nec self.J["lcoe", "wake_loss_factor"] = -dnec_dwlf * lcoe / nec self.J["lcoe", "turbine_aep"] = -dnec_dtaep * lcoe / nec self.J["lcoe", "plant_aep_in"] = -dnec_dpaep * lcoe / nec self.J["lcoe", "machine_rating"] = (dicc_dtrating * fcr + dcopex_dtrating) / nec - dnec_dtrating * lcoe / nec def compute_partials(self, inputs, J, discrete_inputs): J = self.J
# OpenMDAO group to execute the plant finance SE model as a standalone class StandalonePlantFinanceSE(om.Group): def setup(self): self.add_subsystem("financese", PlantFinance(), promotes=["*"]) if __name__ == "__main__": wt_opt = om.Problem(model=StandalonePlantFinanceSE(), reports=False) wt_opt.setup(derivatives=False) wt_opt["machine_rating"] = 5.0e3 wt_opt["tcc_per_kW"] = 1.5e3 wt_opt["bos_per_kW"] = 446.0 wt_opt["opex_per_kW"] = 43.0 wt_opt["turbine_aep"] = 25.0e6 wt_opt["fixed_charge_rate"] = 0.065 wt_opt["turbine_number"] = 120 wt_opt.run_model() print("plant_aep ", wt_opt["plant_aep"]) print("lcoe ", wt_opt["lcoe"]) print("lvoe ", wt_opt["lvoe"]) print("value_factor ", wt_opt["value_factor"]) print("nvoc ", wt_opt["nvoc"]) print("nvoe ", wt_opt["nvoe"]) print("slcoe ", wt_opt["slcoe"]) print("bcr ", wt_opt["bcr"]) print("cbr ", wt_opt["cbr"]) print("roi ", wt_opt["roi"]) print("pm ", wt_opt["pm"]) print("plcoe ", wt_opt["plcoe"])