def test_solar(solar_resource): data = solar_resource.data for key in ('df', 'dn', 'wspd', 'tdry', 'year', 'month', 'day', 'hour', 'minute', 'tz'): assert (key in data) model = pv.default("PVWattsNone") model.SolarResource.solar_resource_file = solar_resource.filename model.execute(0) assert (model.Outputs.annual_energy == approx(9275, 0.1)) model = pv.default("PVWattsNone") model.SolarResource.solar_resource_data = solar_resource.data model.execute(1) assert (model.Outputs.annual_energy == approx(9275, 0.1))
def test_reopt_sizing_pvwatts(solar_resource): round = 0 tracker = SummaryTracker() while round < 25: # multiple runs required to check for memory leaks round += 1 sys = pv.default("PVWattsBatteryCommercial") sys.SolarResource.solar_resource_file = solar_resource batt = bt.from_existing(sys, "PVWattsBatteryCommercial") sys.SolarResource.solar_resource_data = dict({'lat': 3, 'lon': 3}) batt.Battery.crit_load = [0] * 8760 fin = ur.from_existing(sys, "PVWattsBatteryCommercial") post = sys.Reopt_size_battery_post() assert('Scenario' in post['reopt_post']) assert(post['reopt_post']['Scenario']['Site']['latitude'] == pytest.approx(3, 0.1)) tracker_diff = tracker.diff() tracker.print_diff()
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_solar(): solar = str( Path(__file__).parent / "blythe_ca_33.617773_-114.588261_psmv3_60_tmy.csv") data = tools.SAM_CSV_to_solar_data(solar) assert (data['lat'] == 33.61) assert (data['lon'] == -114.58) assert (data['dn'][7] == 262) assert (data['df'][7] == 16) assert (data['gh'][7] == 27) assert (data['tdry'][7] == pytest.approx(8.96, 0.1)) assert (data['tdew'][7] == pytest.approx(-0.03, 0.1)) assert (data['rhum'][7] == pytest.approx(25.0, 0.1)) assert (data['wdir'][7] == pytest.approx(351, 0.1)) model = pv.default("PVwattsNone") model.SolarResource.solar_resource_data = data model.execute() aep = model.Outputs.annual_energy model.SolarResource.solar_resource_file = solar model.execute() assert (aep == pytest.approx(model.Outputs.annual_energy, 1))
def _setup_irradiance(self): """ Compute solar azimuth and elevation degrees; Compute plane-of-array irradiance for a single-axis tracking PVwatts system :return: """ pv_model = pv.default("PVWattsNone") pv_model.SystemDesign.array_type = 2 pv_model.SystemDesign.gcr = .1 if self.solar_resource_data is None: filename = str(self.lat) + "_" + str(self.lon) + "_psmv3_60_2012.csv" weather_path = Path(__file__).parent.parent.parent / "resource_files" / "solar" / filename if not weather_path.is_file(): SolarResource(self.lat, self.lon, year=2012) if not weather_path.is_file(): raise ValueError("resource file does not exist") pv_model.SolarResource.solar_resource_file = str(weather_path) else: pv_model.SolarResource.solar_resource_data = self.solar_resource_data pv_model.execute(0) self.poa = np.array(pv_model.Outputs.poa) logger.info("get_irradiance success")
def __init__(self, site: SiteInfo, pv_config: dict, detailed_not_simple: bool = False): """ :param pv_config: dict, with keys ('system_capacity_kw', 'layout_params') where 'layout_params' is of the SolarGridParameters type :param detailed_not_simple: Detailed model uses Pvsamv1, simple uses PVWatts """ if 'system_capacity_kw' not in pv_config.keys(): raise ValueError self._detailed_not_simple: bool = detailed_not_simple if not detailed_not_simple: system_model = Pvwatts.default("PVWattsSingleOwner") financial_model = Singleowner.from_existing(system_model, "PVWattsSingleOwner") else: system_model = Pvsam.default("FlatPlatePVSingleOwner") financial_model = Singleowner.from_existing(system_model, "FlatPlatePVSingleOwner") super().__init__("SolarPlant", site, system_model, financial_model) self._system_model.SolarResource.solar_resource_data = self.site.solar_resource.data self.dc_degradation = [0] params: Optional[PVGridParameters] = None if 'layout_params' in pv_config.keys(): params: PVGridParameters = pv_config['layout_params'] self._layout = PVLayout(site, system_model, params) self._dispatch: PvDispatch = None self.system_capacity_kw: float = pv_config['system_capacity_kw']
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.Pvwattsv8): 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']))