def test_wind(wind_resource): data = wind_resource.data for key in ('heights', 'fields', 'data'): assert (key in data) model = wp.default("WindPowerNone") model.Resource.wind_resource_filename = wind_resource.filename model.execute(0) assert (model.Outputs.annual_energy == approx(85049139.587)) model = wp.default("WindPowerNone") model.Resource.wind_resource_data = wind_resource.data model.execute(0) assert (model.Outputs.annual_energy == approx(85049139.587))
def getWindCF(windSRW, iecClass, powerCurve): d = wp.default("WindPowerNone") powerout = powerCurve["Composite IEC Class {iecClass}".format(iecClass=iecClass)] speed = powerCurve["Wind Speed"] ##### Parameters ####### d.Resource.wind_resource_filename = windSRW d.Resource.wind_resource_model_choice = 0 d.Turbine.wind_turbine_powercurve_powerout = powerout d.Turbine.wind_turbine_powercurve_windspeeds = speed d.Turbine.wind_turbine_rotor_diameter = 90 d.Turbine.wind_turbine_hub_ht = 100 nameplate_capacity = 1500 #kw d.Farm.system_capacity = nameplate_capacity # System Capacity (kW) d.Farm.wind_farm_wake_model = 0 d.Farm.wind_farm_xCoordinates = np.array([0]) # Lone turbine (centered at position 0,0 in farm) d.Farm.wind_farm_yCoordinates = np.array([0]) ######################## d.execute() windCF = np.array(d.Outputs.gen) / nameplate_capacity #convert AC generation (kw) to capacity factor if args.verbose: print('\t','Average Wind CF = {cf}'.format(cf=round(np.average(windCF),2))) if not args.save_resource: os.remove(windSRW) return windCF
def __init__(self, site: SiteInfo, system_capacity_kw : float, rating_range_kw: tuple = (1000, 3000), grid_not_row_layout: bool = False): """ :param system_capacity_kw: :param grid_not_row_layout: make a regular grid instead of a row whose layout is irrespective of site boundaries :param rating_range_kw: allowable kw range of turbines, default is 1000 - 3000 kW """ self._rating_range_kw = rating_range_kw system_model = Windpower.default("WindPowerSingleOwner") financial_model = Singleowner.from_existing(system_model, "WindPowerSingleOwner") super().__init__("WindPlant", site, system_model, financial_model) self.system_model.Resource.wind_resource_data = self.site.wind_resource.data self._grid_not_row_layout = grid_not_row_layout self.row_spacing = 5 * self.system_model.Turbine.wind_turbine_rotor_diameter self.grid_spacing = None self.system_capacity_closest_fit(system_capacity_kw)
def setup_atb_turbine(self): self.wind_simulation = wind.default("WindpowerSingleowner") # Use ATB Turbine 2018 Market Average self.wind_simulation.Turbine.wind_turbine_hub_ht = 88 self.wind_simulation.Turbine.wind_turbine_rotor_diameter = 116 self.wind_simulation.Turbine.wind_turbine_powercurve_windspeeds = [ 0.25 * i for i in range(161) ] self.wind_simulation.Turbine.wind_turbine_powercurve_powerout = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55, 78, 104, 133, 167, 204, 246, 293, 345, 402, 464, 532, 606, 686, 772, 865, 965, 1072, 1186, 1308, 1438, 1576, 1723, 1878, 2042, 2215, 2397, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 2430, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] # Use a single turbine, do not model wake effects self.wind_simulation.Farm.wind_farm_xCoordinates = [0] self.wind_simulation.Farm.wind_farm_yCoordinates = [0] self.wind_simulation.Farm.system_capacity = max( self.wind_simulation.Turbine.wind_turbine_powercurve_powerout) self.wind_simulation.Resource.wind_resource_model_choice = 2
def default(): """Get the default PySAM object""" res_file = os.path.join(DEFAULTSDIR, 'WY Southern-Flat Lands.csv') obj = PySamWindPower.default('WindPowerNone') obj.Resource.wind_resource_filename = res_file obj.execute() return obj
def test_Resource_wind_resource_distribution_default(): a = wp.default("WindPowerNone") a.Resource.wind_resource_model_choice = 2 a.Resource.wind_resource_distribution = ((1.5, 180, .12583), (5, 180, .3933), (8, 180, .18276), (10, 180, .1341), (13.5, 180, .14217), (19, 180, .0211)) a.Farm.wind_farm_wake_model = 0 a.execute(1) assert (a.Outputs.annual_energy > 0)
def _setup_simulation(self) -> None: """ Wind simulation -> PySAM windpower model """ def run_wind_model(windmodel: windpower.Windpower): windmodel.Farm.system_capacity = \ max(windmodel.Turbine.wind_turbine_powercurve_powerout) * len(windmodel.Farm.wind_farm_xCoordinates) windmodel.execute(0) return windmodel.Outputs.annual_energy self._scenario = dict() wind_model = windpower.default("WindPowerSingleOwner") wind_model.Resource.wind_resource_data = self.site_info.wind_resource.data self.turb_diam = wind_model.Turbine.wind_turbine_rotor_diameter wind_model.Farm.wind_farm_wake_model = 2 # use eddy viscosity wake model self._scenario['Wind'] = (wind_model, run_wind_model)
def run_wp(wind_srw, wind_class, power_curve): d = wp.default("WindPowerNone") #assigning values for respective wind power classes if wind_class == 4: powerout = power_curve['Vestas 7MW']['powerout'] speed = power_curve['Vestas 7MW']['powerout'] #here is where one can put in the specific power class data for OFFSHORE turbines(may need to restructure as there may not be a single offshore turbine needed) #Currently treating them as a IEC level 1 powerout = power_curve["Composite IEC Class I"]["powerout"] speed = power_curve["Composite IEC Class I"]["speed"] elif wind_class == 1: powerout = power_curve["Composite IEC Class I"]["powerout"] speed = power_curve["Composite IEC Class I"]["speed"] elif wind_class == 2: powerout = power_curve["Composite IEC Class II"]["powerout"] speed = power_curve["Composite IEC Class II"]["speed"] else: powerout = power_curve["Composite IEC Class III"]["powerout"] speed = power_curve["Composite IEC Class III"]["speed"] ##### Parameters ####### d.Resource.wind_resource_filename = wind_srw d.Resource.wind_resource_model_choice = 0 d.Turbine.wind_turbine_powercurve_powerout = powerout d.Turbine.wind_turbine_powercurve_windspeeds = speed d.Turbine.wind_turbine_rotor_diameter = 90 d.Turbine.wind_turbine_hub_ht = 80 nameplate_capacity = 1500 #kw d.Farm.system_capacity = nameplate_capacity # System Capacity (kW) d.Farm.wind_farm_wake_model = 0 d.Farm.wind_farm_xCoordinates = np.array( [0]) # Lone turbine (centered at position 0,0 in farm) d.Farm.wind_farm_yCoordinates = np.array([0]) ######################## d.execute() output_cf = np.array( d.Outputs.gen ) / nameplate_capacity #convert AC generation (kw) to capacity factor return output_cf
def calculate_max_hybrid_aep(site_info: SiteInfo, num_turbines: int, solar_capacity_kw: float ) -> dict: """ Calculates the max total pv and solar annual energy output by assuming no wake, gcr or flicker losses. All other factors and losses are not adjusted because they remain constant throughout the optimization :return: dictionary of "wind", "solar" and "total" max AEPs """ upper_bounds = dict() # wind wind_model = windpower.default("WindPowerSingleOwner") wind_model.Resource.wind_resource_data = site_info.wind_resource.data wind_params_orig = wind_model.export() wind_model.Farm.wind_farm_xCoordinates = np.zeros(num_turbines) wind_model.Farm.wind_farm_yCoordinates = np.zeros(num_turbines) wind_model.Farm.system_capacity = num_turbines * max(wind_model.Turbine.wind_turbine_powercurve_powerout) wind_model.Farm.wind_farm_wake_model = 3 # constant wake loss model which we can set to 0 wind_model.Losses.wake_int_loss = 0 wind_model.execute(0) # solar solar_model = pvwatts.default("PVWattsSingleOwner") solar_model.SolarResource.solar_resource_data = site_info.solar_resource.data solar_model.SystemDesign.array_type = 2 # single-axis tracking solar_params_orig = solar_model.export() solar_model.SystemDesign.gcr = 0.01 # lowest possible gcr solar_model.SystemDesign.system_capacity = solar_capacity_kw solar_model.execute(0) upper_bounds['wind'] = wind_model.Outputs.annual_energy / 1000 upper_bounds['solar'] = solar_model.Outputs.annual_energy / 1000 upper_bounds['total'] = upper_bounds['wind'] + upper_bounds['solar'] # restore original parameters wind_model.assign(wind_params_orig) solar_model.assign(solar_params_orig) return upper_bounds
def test_wind_powercurve(): model = windpower.default("WindpowerSingleowner") model.Turbine.wind_turbine_rotor_diameter = 75 # calculate system capacity. To evaluate other turbines, update the defaults dictionary model.Turbine.calculate_powercurve( wind_default_rated_output, int(model.Turbine.wind_turbine_rotor_diameter), wind_default_elevation, wind_default_max_cp, wind_default_max_tip_speed, wind_default_max_tip_speed_ratio, wind_default_cut_in_speed, wind_default_cut_out_speed, wind_default_drive_train) windspeeds_truth = [round(x, 2) for x in powercurveWS] windspeeds_calc = [ round(x, 2) for x in model.Turbine.wind_turbine_powercurve_windspeeds ] powercurve_truth = [round(x, 0) for x in powercurveKW] powercurve_calc = [ round(x, 0) for x in model.Turbine.wind_turbine_powercurve_powerout ] assert all([a == b for a, b in zip(windspeeds_truth, windspeeds_calc)]) assert all([a == b for a, b in zip(powercurve_truth, powercurve_calc)])
import PySAM.ResourceTools as tools import PySAM.Windpower as wp import PySAM.Singleowner as so # --- Initialize Wind Fetcher --- wtkfetcher = tools.FetchResourceFiles( tech='wind', workers=1, #thread workers if fetching multiple files nrel_api_key=<NREL_API_KEY>, nrel_api_email=<NREL_API_EMAIL>) # --- Pass a list of (lon, lat) tuples or Shapely points to fetch the nearest resource data --- lon_lats = [(-105.1800775, 39.7383155)] # golden CO wtkfetcher.fetch(lon_lats) # --- Get resource data file path --- wtk_path_dict = wtkfetcher.resource_file_paths_dict wtk_fp = wtk_path_dict[lon_lats[0]] # --- Initialize Generator --- generator = wp.default('WindPowerSingleOwner') generator.Resource.assign({'wind_resource_model_choice': 0}) generator.Resource.assign({'wind_resource_filename': wtk_fp}) #pass path to resource file # --- Initialize Financial Model --- financial = so.from_existing(generator, "WindPowerSingleOwner") # --- Execute Models --- generator.execute() financial.execute()
def _setup_simulation(self) -> None: """ Wind simulation -> PySAM windpower model Solar simulation -> Surrogate model of PySAM Pvwatts model since the AEP scales linearly and independently w.r.t solar capacity and gcr """ def run_wind_model(windmodel: windpower.Windpower): windmodel.Farm.system_capacity = \ max(windmodel.Turbine.wind_turbine_powercurve_powerout) * len(windmodel.Farm.wind_farm_xCoordinates) windmodel.execute(0) return windmodel.Outputs.annual_energy def run_pv_model(pvmodel: pvwatts.Pvwattsv7): cap = pvmodel.SystemDesign.system_capacity gcr = pvmodel.SystemDesign.gcr est = cap * self._solar_size_aep_multiplier * self.solar_gcr_loss_multiplier( gcr) # pvmodel.execute() # rl = pvmodel.Outputs.annual_energy # err = (rl - est)/rl # if err > 0.05: # print("High approx error found with {} kwh and {} gcr of {}".format(cap, gcr, err)) return est # create wind model self._scenario = dict() wind_model = windpower.default("WindPowerSingleOwner") wind_model.Resource.wind_resource_data = self.site_info.wind_resource.data self.turb_diam = wind_model.Turbine.wind_turbine_rotor_diameter wind_model.Farm.wind_farm_wake_model = 2 # use eddy viscosity wake model self._scenario['Wind'] = (wind_model, run_wind_model) # create pv model solar_model = pvwatts.default("PVWattsSingleOwner") solar_model.SolarResource.solar_resource_data = self.site_info.solar_resource.data solar_model.SystemDesign.array_type = 2 # single-axis tracking solar_model.SystemDesign.tilt = 0 # setup surrogate solar_model.execute(0) self._solar_size_aep_multiplier = solar_model.Outputs.annual_energy / solar_model.SystemDesign.system_capacity solar_model.SystemDesign.gcr = 0.01 # lowest possible gcr solar_model.SystemDesign.system_capacity = 1 solar_model.execute(0) if solar_model.Outputs.annual_energy > 0: self._solar_gcr_loss_multiplier[ 'unit'] = solar_model.Outputs.annual_energy else: raise RuntimeError( "Solar GCR Loss Multiplier: Setup failed due to 0 for unit value" ) self._scenario['Solar'] = (solar_model, run_pv_model) # estimate max AEP self.upper_bounds = calculate_max_hybrid_aep(self.site_info, self.num_turbines, self.solar_capacity_kw) logger.info( "Setup Wind and Solar models. Max AEP is {} for wind, {} solar, {} total" .format(self.upper_bounds['wind'], self.upper_bounds['solar'], self.upper_bounds['total']))
def __init__( self, site: SiteInfo, farm_config: dict, rating_range_kw: tuple = (1000, 3000), ): """ Set up a WindPlant :param farm_config: dict, with keys ('num_turbines', 'turbine_rating_kw', 'rotor_diameter', 'hub_height', 'layout_mode', 'layout_params') where layout_mode can be selected from the following: - 'boundarygrid': regular grid with boundary turbines, requires WindBoundaryGridParameters as 'params' - 'grid': regular grid with dx, dy distance, 0 angle; does not require 'params' :param rating_range_kw: allowable kw range of turbines, default is 1000 - 3000 kW """ self._rating_range_kw = rating_range_kw if 'model_name' in farm_config.keys(): if farm_config['model_name'] == 'floris': print('FLORIS is the system model...') system_model = Floris(farm_config, site, timestep=farm_config['timestep']) financial_model = Singleowner.default("WindPowerSingleOwner") else: raise NotImplementedError else: system_model = Windpower.default("WindPowerSingleOwner") financial_model = Singleowner.from_existing( system_model, "WindPowerSingleOwner") super().__init__("WindPlant", site, system_model, financial_model) self._system_model.value("wind_resource_data", self.site.wind_resource.data) if 'layout_mode' not in farm_config.keys(): layout_mode = 'grid' else: layout_mode = farm_config['layout_mode'] params: Optional[WindBoundaryGridParameters] = None if layout_mode == 'boundarygrid': if 'layout_params' not in farm_config.keys(): raise ValueError( "Parameters of WindBoundaryGridParameters required for boundarygrid layout mode" ) else: params: WindBoundaryGridParameters = farm_config[ 'layout_params'] self._layout = WindLayout(site, system_model, layout_mode, params) self._dispatch: WindDispatch = None if 'turbine_rating_kw' not in farm_config.keys(): raise ValueError("Turbine rating required for WindPlant") if 'num_turbines' not in farm_config.keys(): raise ValueError("Num Turbines required for WindPlant") self.turb_rating = farm_config['turbine_rating_kw'] self.num_turbines = farm_config['num_turbines'] if 'hub_height' in farm_config.keys(): self._system_model.Turbine.wind_turbine_hub_ht = farm_config[ 'hub_height'] if 'rotor_diameter' in farm_config.keys(): self.rotor_diameter = farm_config['rotor_diameter']
def __init__(self, tech, re_capacity_mw, #float, or tuple with lower and upper bounds batt_capacity_mw=0, #float, or tuple with lower and upper bounds batt_duration=[0,2,4], #0, 2, or 4hr verbose=True, params=None): if verbose: log.info('\n') log.info(f'Initializing BayesianSystemDesigner for {tech}') self.tech = tech if isinstance(re_capacity_mw, (float, int)): self.re_capacity_kw=re_capacity_mw * 1000 # pysam takes this as kw elif isinstance(re_capacity_mw, (tuple, list)): self.re_capacity_kw = (re_capacity_mw[0] * 1000, re_capacity_mw[1] * 1000) if isinstance(batt_capacity_mw, (float, int)): self.batt_capacity_kw = batt_capacity_mw * 1000 # pysam takes this as kw elif isinstance(batt_capacity_mw, (tuple, list)): self.batt_capacity_kw = (batt_capacity_mw[0] * 1000, batt_capacity_mw[1] * 1000) else: self.batt_capacity_kw = 0 if isinstance(batt_duration, (float, int)): self.batt_duration = batt_duration elif isinstance(batt_duration, (tuple, list)): self.batt_duration = np.array(batt_duration) self.storage = False if isinstance(self.batt_capacity_kw, (int, float)): if self.batt_capacity_kw > 0: self.storage = True elif isinstance(self.batt_capacity_kw, (tuple)): if max(self.batt_capacity_kw) > 0: self.storage = True # --- Initiate Generator and assign params if not passed --- if tech == 'pv': self.gen = pv.default('PVWattsSingleOwner') self.default_params = self.gen.SystemDesign.export() if params == None: # assign default grid of solar params self.param_grid = { 'SystemDesign':{ 'system_capacity': self.re_capacity_kw, 'subarray1_track_mode': 1, #np.array([0, 1, 2, 4]), #1 = fixed 'subarray1_tilt': np.arange(0, 90, 10), 'subarray1_azimuth': np.arange(1, 359, 10), 'dc_ac_ratio': np.arange(0.8, 1.3, 0.1), } } elif tech == 'wind': self.gen = wp.default('WindPowerSingleOwner') self.default_params = self.gen.Turbine.export() if params == None: # assign default grid of wind params self.param_grid = { 'Turbine': {'wind_turbine_hub_ht': np.array([60, 170]), 'turbine_class': np.array([1, 10])}, 'Farm': {'system_capacity': self.re_capacity_kw}, } else: raise NotImplementedError(f'Please write a wrapper to account for the new technology type {tech}') # --- Add Battery Params --- if self.storage: self.param_grid['BatteryTools'] = {'desired_power': self.batt_capacity_kw, 'desired_capacity': self.batt_duration, 'desired_voltage':500} self.param_grid['BatterySystem'] = {'en_batt':1, 'batt_meter_position':0} else: self.param_grid['BatterySystem'] = {'en_batt': 0}
def run_generator(self): self.tech = 'wind' self._size_system() self.generator = wp.default( 'WindPowerSingleOwner' ) #Resource, Turbine, Farm, Losses, Uncertainty, AdjustmentFactors self.generator.Resource.wind_resource_filename = self.resource_file self.generator.Resource.wind_resource_model_choice = 0 # --- Wind class power curve --- wind_class_dict = { 1: { 'cut_in': 9.01, 'cut_out': 12.89 }, 2: { 'cut_in': 8.77, 'cut_out': 9.01 }, 3: { 'cut_in': 8.57, 'cut_out': 8.77 }, 4: { 'cut_in': 8.35, 'cut_out': 8.57 }, 5: { 'cut_in': 8.07, 'cut_out': 8.35 }, 6: { 'cut_in': 7.62, 'cut_out': 8.07 }, 7: { 'cut_in': 7.1, 'cut_out': 7.62 }, 8: { 'cut_in': 6.53, 'cut_out': 7.1 }, 9: { 'cut_in': 5.9, 'cut_out': 6.53 }, 10: { 'cut_in': 1.72, 'cut_out': 5.9 }, } powercurve_dict = { 'turbine_size': 2400, #From ATB 'rotor_diameter': 116, #SAM default 'elevation': 0, 'max_cp': 0.45, # SAM default 'max_tip_speed': 116, #Match rotor diameter 'max_tip_sp_ratio': 8, #SAM default 'cut_in': wind_class_dict[self.turbine_class]['cut_in'], 'cut_out': 25, #not sure how to interpret maximum wind speeds, as they are too low for sensible cutout 'drive_train': 0, } self.generator.Turbine.calculate_powercurve(**powercurve_dict) self.generator.Turbine.wind_resource_shear = 0.14 self.generator.Turbine.wind_turbine_rotor_diameter = 116 # --- Create dummy farm layout in a row --- xcoords, ycoords = self._set_num_turbines_in_row( n_turbines=self.n_turbines, spacing=250) self.generator.Farm.wind_farm_xCoordinates = xcoords self.generator.Farm.wind_farm_yCoordinates = ycoords self.generator.Farm.assign({ k: v for k, v in self.system_config['Farm'].items() if k in list(self.generator.Farm.export().keys()) }) self.generator.Farm.wind_farm_wake_model = 0 self.generator.Farm.wind_resource_turbulence_coeff = 0.1 self.generator.Farm.system_capacity = self.fitted_capacity self.generator.Losses.wake_int_loss = 0 self.generator.Losses.wake_ext_loss = 1.1 self.generator.Losses.wake_future_loss = 0 self.generator.Losses.avail_bop_loss = 0.5 self.generator.Losses.avail_grid_loss = 1.5 self.generator.Losses.avail_turb_loss = 3.58 self.generator.Losses.elec_eff_loss = 1.91 self.generator.Losses.elec_parasitic_loss = 0.1 self.generator.Losses.env_degrad_loss = config.DEGRADATION * 100 self.generator.Losses.env_exposure_loss = 0 self.generator.Losses.env_env_loss = 0.4 self.generator.Losses.env_icing_loss = 0.21 self.generator.Losses.ops_env_loss = 1 self.generator.Losses.ops_grid_loss = 0.84 self.generator.Losses.ops_load_loss = 0.99 self.generator.Losses.ops_strategies_loss = 0 self.generator.Losses.turb_generic_loss = 1.7 self.generator.Losses.turb_hysteresis_loss = 0.4 self.generator.Losses.turb_perf_loss = 1.1 self.generator.Losses.turb_specific_loss = 0.81 self.generator.Uncertainty.total_uncert = 12.085 self.generator.execute()