Source code for ard.cost.wisdem_wrap

import warnings
import numpy as np

import openmdao.api as om
from wisdem.plant_financese.plant_finance import PlantFinance as PlantFinance_orig
from wisdem.landbosse.landbosse_omdao.landbosse import LandBOSSE as LandBOSSE_orig
from wisdem.orbit.orbit_api import Orbit as Orbit_orig

from ard.cost.approximate_turbine_spacing import SpacingApproximations


[docs] class LandBOSSEWithSpacingApproximations(om.Group): """ OpenMDAO group that connects the SpacingApproximations component to the LandBOSSE component. This group calculates the turbine spacing using the SpacingApproximations and passes it to the LandBOSSE component for further cost estimation. """
[docs] def initialize(self): """Initialize the group and declare options.""" self.options.declare( "modeling_options", types=dict, desc="Ard modeling options" )
[docs] def setup(self): """Set up the group by adding and connecting components.""" # Add the PrimarySpacingApproximations component self.add_subsystem( "spacing_approximations", SpacingApproximations(modeling_options=self.options["modeling_options"]), promotes_inputs=["total_length_cables"], ) # Add the LandBOSSE component self.add_subsystem( "landbosse", LandBOSSEGroup(modeling_options=self.options["modeling_options"]), promotes_inputs=[ "*", ( "turbine_spacing_rotor_diameters", "internal_turbine_spacing_rotor_diameters", ), ( "row_spacing_rotor_diameters", "internal_row_spacing_rotor_diameters", ), ], promotes_outputs=["*"], # Expose all outputs from LandBOSSE ) # Connect the turbine and row spacing outputs from the approximations to LandBOSSE self.connect( "spacing_approximations.primary_turbine_spacing_diameters", "internal_turbine_spacing_rotor_diameters", ) self.connect( "spacing_approximations.secondary_turbine_spacing_diameters", "internal_row_spacing_rotor_diameters", )
[docs] class LandBOSSEGroup(om.Group):
[docs] def initialize(self): """Initialize the group and declare options.""" self.options.declare( "modeling_options", types=dict, desc="Ard modeling options" )
[docs] def setup(self): # add IVCs for landbosse variable_mapping = LandBOSSE_setup_latents( modeling_options=self.options["modeling_options"] ) # create source independent variable components for LandBOSSE inputs for key, meta in variable_mapping.items(): if key in ["num_turbines", "number_of_blades"]: comp = om.IndepVarComp() comp.add_discrete_output(name=key, val=meta["val"]) self.add_subsystem(f"IVC_landbosse_{key}", comp, promotes=["*"]) else: self.add_subsystem( f"IVC_landbosse_{key}", om.IndepVarComp(key, val=meta["val"], units=meta["units"]), promotes=["*"], ) # add landbosse self.add_subsystem( "landbosse", LandBOSSE_orig(), promotes=[ "total_capex", "total_capex_kW", "bos_capex_kW", "turbine_spacing_rotor_diameters", "row_spacing_rotor_diameters", ], ) # connect for key, val in variable_mapping.items(): self.connect(key, f"landbosse.{key}")
[docs] class ORBITGroup(om.Group):
[docs] def initialize(self): """Initialize the group and declare options.""" self.options.declare( "modeling_options", types=dict, desc="Ard modeling options" )
[docs] def setup(self): # add IVCs for landbosse variable_mapping = ORBIT_setup_latents( modeling_options=self.options["modeling_options"] ) # create source independent variable components for LandBOSSE inputs for key, meta in variable_mapping.items(): if key in ["number_of_turbines", "number_of_blades", "num_mooring_lines"]: comp = om.IndepVarComp() comp.add_discrete_output(name=key, val=meta["val"]) self.add_subsystem(f"IVC_orbit_{key}", comp, promotes=["*"]) else: self.add_subsystem( f"IVC_orbit_{key}", om.IndepVarComp(key, val=meta["val"], units=meta["units"]), promotes=["*"], ) # add orbit self.add_subsystem( "orbit", Orbit_orig( floating=self.options["modeling_options"]["floating"], jacket=self.options["modeling_options"].get("jacket"), jacket_legs=self.options["modeling_options"].get("jacket_legs"), ), promotes=[ "total_capex", "total_capex_kW", "bos_capex", "installation_capex", "plant_turbine_spacing", "plant_row_spacing", ], ) # connect for key, val in variable_mapping.items(): self.connect(key, f"orbit.{key}")
[docs] class FinanceSEGroup(om.Group):
[docs] def initialize(self): """Initialize the group and declare options.""" self.options.declare( "modeling_options", types=dict, desc="Ard modeling options" )
[docs] def setup(self): # add IVCs for landbosse variable_mapping = FinanceSE_setup_latents( modeling_options=self.options["modeling_options"] ) # create source independent variable components for LandBOSSE inputs for key, meta in variable_mapping.items(): if key in [ "turbine_number", ]: comp = om.IndepVarComp() comp.add_discrete_output(name=key, val=meta["val"]) self.add_subsystem(f"IVC_financese_{key}", comp, promotes=["*"]) else: self.add_subsystem( f"IVC_financese_{key}", om.IndepVarComp(key, val=meta["val"], units=meta["units"]), promotes=["*"], ) # add financese #TODO check promotes self.add_subsystem( "financese", PlantFinance_orig(), promotes=[ "offset_tcc_per_kW", "plant_aep_in", "bos_per_kW", # "tcc_per_kW", "lcoe", ], ) # connect for key, val in variable_mapping.items(): self.connect(key, f"financese.{key}")
[docs] class TurbineCapitalCosts(om.ExplicitComponent): """ A simple component to compute the turbine capital costs. Inputs ------ machine_rating : float rating of the wind turbine in kW tcc_per_kW : float turbine capital costs per kW (as output from WISDEM tools) offset_tcc_per_kW : float additional tcc per kW (offset) Discrete Inputs --------------- turbine_number : int number of turbines in the farm Outputs ------- tcc : float turbine capital costs in USD """
[docs] def setup(self): """Setup of OM component.""" self.add_input("machine_rating", 0.0, units="kW") self.add_input("tcc_per_kW", 0.0, units="USD/kW") self.add_input("offset_tcc_per_kW", 0.0, units="USD/kW") self.add_discrete_input("turbine_number", 0) self.add_output("tcc", 0.0, units="USD")
[docs] def setup_partials(self): """Derivative setup for OM component.""" # complex step for simple gradients self.declare_partials("*", "*", method="cs")
[docs] def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): """Computation for the OM compoent.""" # 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"] outputs["tcc"] = n_turbine * tcc_per_kW * t_rating
[docs] class OperatingExpenses(om.ExplicitComponent): """ A simple component to compute the operating costs. Inputs ------ machine_rating : float rating of the wind turbine in kW opex_per_kW : float annual operating and maintenance costs per kW (as output from WISDEM tools) Discrete Inputs --------------- turbine_number : int number of turbines in the farm Outputs ------- opex : float annual operating and maintenance costs in USD """
[docs] def setup(self): """Setup of OM component.""" self.add_input("machine_rating", 0.0, units="kW") self.add_input("opex_per_kW", 0.0, units="USD/kW/yr") self.add_discrete_input("turbine_number", 0) self.add_output("opex", 0.0, units="USD/yr")
[docs] def setup_partials(self): """Derivative setup for OM component.""" # complex step for simple gradients self.declare_partials("*", "*", method="cs")
[docs] def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): """Computation for the OM compoent.""" # Unpack parameters t_rating = inputs["machine_rating"] n_turbine = discrete_inputs["turbine_number"] opex_per_kW = inputs["opex_per_kW"] outputs["opex"] = n_turbine * opex_per_kW * t_rating
[docs] def LandBOSSE_setup_latents(modeling_options: dict) -> None: """ A function to set up the LandBOSSE latent variables using modeling options. Parameters ---------- prob : openmdao.api.Problem an OpenMDAO problem for which we want to setup the LandBOSSE latent variables modeling_options : dict a modeling options dictionary """ # Define the mapping between OpenMDAO variable names and modeling_options keys offshore_fixed_keys = [ "monopile_mass", "monopile_cost", ] offshore_floating_keys = [ "num_mooring_lines", "mooring_line_mass", "mooring_line_diameter", "mooring_line_length", "anchor_mass", "floating_substructure_cost", ] def _base_common(): return { "num_turbines": { "val": modeling_options["layout"]["N_turbines"], "units": None, }, "turbine_rating_MW": { "val": modeling_options["windIO_plant"]["wind_farm"]["turbine"][ "performance" ]["rated_power"] / 1.0e6, "units": "MW", }, "hub_height_meters": { "val": modeling_options["windIO_plant"]["wind_farm"]["turbine"][ "hub_height" ], "units": "m", }, "rotor_diameter_m": { "val": modeling_options["windIO_plant"]["wind_farm"]["turbine"][ "rotor_diameter" ], "units": "m", }, "number_of_blades": { "val": modeling_options["costs"]["num_blades"], "units": None, }, "tower_mass": { "val": modeling_options["costs"]["tower_mass"], "units": "t", }, "nacelle_mass": { "val": modeling_options["costs"]["nacelle_mass"], "units": "t", }, "blade_mass": { "val": modeling_options["costs"]["blade_mass"], "units": "t", }, "commissioning_cost_kW": { "val": modeling_options["costs"]["commissioning_cost_kW"], "units": "USD/kW", }, "decommissioning_cost_kW": { "val": modeling_options["costs"]["decommissioning_cost_kW"], "units": "USD/kW", }, } if any(key in modeling_options["costs"] for key in offshore_fixed_keys): variable_mapping = _base_common() variable_mapping.update( { "monopile_mass": { "val": modeling_options["costs"]["monopile_mass"], "units": "kg", }, "monopile_cost": { "val": modeling_options["costs"]["monopile_cost"], "units": "USD", }, } ) elif any(key in modeling_options["costs"] for key in offshore_floating_keys): variable_mapping = _base_common() variable_mapping.update( { "num_mooring_lines": { "val": modeling_options["costs"]["num_mooring_lines"], "units": None, }, "mooring_line_mass": { "val": modeling_options["costs"]["mooring_line_mass"], "units": "kg", }, "mooring_line_diameter": { "val": modeling_options["costs"]["mooring_line_diameter"], "units": "m", }, "mooring_line_length": { "val": modeling_options["costs"]["mooring_line_length"], "units": "m", }, "anchor_mass": { "val": modeling_options["costs"]["anchor_mass"], "units": "kg", }, "floating_substructure_cost": { "val": modeling_options["costs"]["floating_substructure_cost"], "units": "USD", }, } ) else: variable_mapping = _base_common() variable_mapping.update( { "rated_thrust_N": { "val": modeling_options["costs"]["rated_thrust_N"], "units": "N", }, "gust_velocity_m_per_s": { "val": modeling_options["costs"]["gust_velocity_m_per_s"], "units": "m/s", }, "blade_surface_area": { "val": modeling_options["costs"]["blade_surface_area"], "units": "m**2", }, "hub_mass": { "val": modeling_options["costs"]["hub_mass"], "units": "kg", }, "foundation_height": { "val": modeling_options["costs"]["foundation_height"], "units": "m", }, "trench_len_to_substation_km": { "val": modeling_options["costs"]["trench_len_to_substation_km"], "units": "km", }, "distance_to_interconnect_mi": { "val": modeling_options["costs"]["distance_to_interconnect_mi"], "units": "mi", }, "interconnect_voltage_kV": { "val": modeling_options["costs"]["interconnect_voltage_kV"], "units": "kV", }, } ) return variable_mapping
[docs] def ORBIT_setup_latents(modeling_options: dict) -> None: """ A function to set up the ORBIT latent variables using modeling options. Parameters ---------- prob : openmdao.api.Problem an OpenMDAO problem for which we want to setup the ORBIT latent variables modeling_options : dict a modeling options dictionary """ variable_mapping = { "turbine_rating": { "val": modeling_options["windIO_plant"]["wind_farm"]["turbine"][ "performance" ]["rated_power"], "units": "W", }, "site_depth": {"val": modeling_options["site_depth"], "units": "m"}, "number_of_turbines": { "val": modeling_options["layout"]["N_turbines"], "units": None, }, "number_of_blades": { "val": modeling_options["costs"]["num_blades"], "units": None, }, "hub_height": { "val": modeling_options["windIO_plant"]["wind_farm"]["turbine"][ "hub_height" ], "units": "m", }, "turbine_rotor_diameter": { "val": modeling_options["windIO_plant"]["wind_farm"]["turbine"][ "rotor_diameter" ], "units": "m", }, "tower_length": { "val": modeling_options["costs"]["tower_length"], "units": "m", }, "tower_mass": {"val": modeling_options["costs"]["tower_mass"], "units": "t"}, "nacelle_mass": { "val": modeling_options["costs"]["nacelle_mass"], "units": "t", }, "blade_mass": {"val": modeling_options["costs"]["blade_mass"], "units": "t"}, "turbine_capex": { "val": modeling_options["costs"]["turbine_capex"], "units": "USD/kW", }, "site_mean_windspeed": { "val": modeling_options["costs"]["site_mean_windspeed"], "units": "m/s", }, "turbine_rated_windspeed": { "val": modeling_options["costs"]["turbine_rated_windspeed"], "units": "m/s", }, "commissioning_cost_kW": { "val": modeling_options["costs"]["commissioning_cost_kW"], "units": "USD/kW", }, "decommissioning_cost_kW": { "val": modeling_options["costs"]["decommissioning_cost_kW"], "units": "USD/kW", }, "plant_substation_distance": { "val": modeling_options["costs"]["plant_substation_distance"], "units": "km", }, "interconnection_distance": { "val": modeling_options["costs"]["interconnection_distance"], "units": "km", }, "site_distance": { "val": modeling_options["costs"]["site_distance"], "units": "km", }, "site_distance_to_landfall": { "val": modeling_options["costs"]["site_distance_to_landfall"], "units": "km", }, "port_cost_per_month": { "val": modeling_options["costs"]["port_cost_per_month"], "units": "USD/month", }, "construction_insurance": { "val": modeling_options["costs"]["construction_insurance"], "units": "USD/kW", }, "construction_financing": { "val": modeling_options["costs"]["construction_financing"], "units": "USD/kW", }, "contingency": { "val": modeling_options["costs"]["contingency"], "units": "USD/kW", }, "site_auction_price": { "val": modeling_options["costs"]["site_auction_price"], "units": "USD", }, "site_assessment_cost": { "val": modeling_options["costs"]["site_assessment_cost"], "units": "USD", }, "construction_plan_cost": { "val": modeling_options["costs"]["construction_plan_cost"], "units": "USD", }, "installation_plan_cost": { "val": modeling_options["costs"]["installation_plan_cost"], "units": "USD", }, "boem_review_cost": { "val": modeling_options["costs"]["boem_review_cost"], "units": "USD", }, } # Add floating-foundation specific keys if applicable if modeling_options["floating"]: variable_mapping.update( { "num_mooring_lines": { "val": modeling_options["costs"]["num_mooring_lines"], "units": None, }, "mooring_line_mass": { "val": modeling_options["costs"]["mooring_line_mass"], "units": "kg", }, "mooring_line_diameter": { "val": modeling_options["costs"]["mooring_line_diameter"], "units": "m", }, "mooring_line_length": { "val": modeling_options["costs"]["mooring_line_length"], "units": "m", }, "anchor_mass": { "val": modeling_options["costs"]["anchor_mass"], "units": "kg", }, "transition_piece_mass": { "val": modeling_options["costs"]["transition_piece_mass"], "units": "t", }, "transition_piece_cost": { "val": modeling_options["costs"]["transition_piece_cost"], "units": "USD", }, "floating_substructure_cost": { "val": modeling_options["costs"]["floating_substructure_cost"], "units": "USD", }, } ) # Add fixed-foundation (mooring) specific keys if applicable else: variable_mapping.update( { "monopile_mass": { "val": modeling_options["costs"]["monopile_mass"], "units": "t", }, "monopile_cost": { "val": modeling_options["costs"]["monopile_cost"], "units": "USD", }, "monopile_length": { "val": modeling_options["costs"]["monopile_length"], "units": "m", }, "monopile_diameter": { "val": modeling_options["costs"]["monopile_diameter"], "units": "m", }, "transition_piece_mass": { "val": modeling_options["costs"]["transition_piece_mass"], "units": "t", }, "transition_piece_cost": { "val": modeling_options["costs"]["transition_piece_cost"], "units": "USD", }, } ) # TODO include jacket-type foundation # # if jacket # prob.set_val( # comp2promotion_map["orbit.orbit.jacket_r_foot"], # modeling_options["turbine"]["costs"]["jacket_r_foot"]) # prob.set_val( # comp2promotion_map["orbit.orbit.jacket_length"], # modeling_options["turbine"]["costs"]["jacket_length"]) # prob.set_val( # comp2promotion_map["orbit.orbit.jacket_mass"], # modeling_options["turbine"]["costs"]["jacket_mass"]) # prob.set_val( # comp2promotion_map["orbit.orbit.jacket_cost"], # modeling_options["turbine"]["costs"]["jacket_cost"]) # prob.set_val( # comp2promotion_map["orbit.orbit.transition_piece_mass"], # modeling_options["turbine"]["costs"]["transition_piece_mass"]) # prob.set_val( # comp2promotion_map["orbit.orbit.transition_piece_cost"], # modeling_options["turbine"]["costs"]["transition_piece_cost"]) return variable_mapping
[docs] def FinanceSE_setup_latents(modeling_options): """ A function to set up the FinanceSE latent variables using modeling options. Parameters ---------- prob : openmdao.api.Problem an OpenMDAO problem for which we want to setup the FinanceSE latent variables modeling_options : dict a modeling options dictionary """ # Define the mapping between OpenMDAO variable names and modeling_options keys variable_mapping = { "turbine_number": { "val": int(modeling_options["layout"]["N_turbines"]), "units": None, }, "machine_rating": { "val": modeling_options["windIO_plant"]["wind_farm"]["turbine"][ "performance" ]["rated_power"], "units": "W", }, "tcc_per_kW": { "val": modeling_options["costs"]["tcc_per_kW"], "units": "USD/kW", }, "opex_per_kW": { "val": modeling_options["costs"]["opex_per_kW"], "units": "USD/kW/year", }, } return variable_mapping