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(5743.338)) model = pv.default("PVWattsNone") model.SolarResource.solar_resource_data = solar_resource.data model.execute(1) assert (model.Outputs.annual_energy == approx(5743.537))
def __init__(self, site: SiteInfo, system_capacity_kw: float, detailed_not_simple: bool = False): """ :param system_capacity_kw: :param detailed_not_simple: Detailed model uses Pvsamv1, simple uses PVWatts """ 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.system_capacity_kw: float = system_capacity_kw
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 default(): """Get the default PySAM pvwattsv7 object""" res_file = os.path.join( DEFAULTSDIR, 'USA AZ Phoenix Sky Harbor Intl Ap (TMY3).csv') obj = PySamPV7.default('PVWattsNone') obj.SolarResource.solar_resource_file = res_file obj.execute() return obj
def calculate_power(solar_data, pv_dict): """Use PVWatts to translate weather data into power. :param dict solar_data: weather data as returned by :meth:`Psm3Data.to_dict`. :param dict pv_dict: solar plant attributes. :return: (*numpy.array*) hourly power output. """ pv_dat = pssc.dict_to_ssc_table(pv_dict, "pvwattsv7") pv = PVWatts.wrap(pv_dat) pv.SolarResource.assign({"solar_resource_data": solar_data}) pv.execute() return np.array(pv.Outputs.gen)
def test_solar(): solar = str(Path(__file__).parent / "blythe_ca_33.617773_-114.588261_psmv3_60_tmy.csv") data = tools.TMY_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)) 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 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 get_solar_energy_output(self): ssc = pssc.PySSC() f = open(self.sam_export_json) self.dic = json.load(f) self.dic['solar_resource_file'] = self.fp_srw ### uncomment if you want to change any model input parameters # self.dic['system_capacity'] = 20000 # self.dic['module_type'] = 0 # self.dic['dc_ac_ratio'] = 1.3 # self.dic['array_type'] = 2 # self.dic['tilt'] = 35 # self.dic['azimuth'] = 180 # self.dic['gcr'] = 0.40 # self.dic['losses'] = 14 # self.dic['en_snowloss'] = 0 # self.dic['inv_eff'] = 95 pv_dat = pssc.dict_to_ssc_table(self.dic, "pvwattsv7") grid_dat = pssc.dict_to_ssc_table(self.dic, "grid") f.close() pv = PVWatts.wrap(pv_dat) grid = Grid.from_existing(pv) grid.assign(Grid.wrap(grid_dat).export()) pv.execute() grid.execute() self.json_dict = pv.Outputs.export() # print(self.json_dict.keys()) self.df_output = pd.DataFrame() self.df_output[self.year] = self.json_dict['gen'] for col in self.df_output.columns: self.df_output[col] = preprocessing.minmax_scale( self.df_output[col].values.reshape(1, -1), feature_range=(0, 1), axis=1, copy=True).T if self.df_all is None: self.df_all = self.df_output.copy() else: self.df_all = pd.concat([self.df_all, self.df_output], axis=1)
def run_solar(solar_csv, latitude): s = pv.default("PVWattsNone") ##### Parameters ####### s.SolarResource.solar_resource_file = solar_csv s.SystemDesign.array_type = 0 s.SystemDesign.azimuth = 180 s.SystemDesign.tilt = abs(latitude) nameplate_capacity = 1000 #kw s.SystemDesign.system_capacity = nameplate_capacity # System Capacity (kW) s.SystemDesign.dc_ac_ratio = 1.1 #DC to AC ratio s.SystemDesign.inv_eff = 96 #default inverter eff @ rated power (%) s.SystemDesign.losses = 14 #other DC losses (%) (14% is default from documentation) ######################## s.execute() output_cf = np.array( s.Outputs.ac) / (nameplate_capacity * 1000 ) #convert AC generation (w) to capacity factor return output_cf
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 getSolarCF(solarResourceData): s = pv.default("PVWattsNone") ##### Parameters ####### s.SolarResource.solar_resource_data=solarResourceData s.SystemDesign.array_type = 0 s.SystemDesign.azimuth = 180 s.SystemDesign.tilt = abs(solarResourceData['lat']) nameplate_capacity = 1000 #kw s.SystemDesign.system_capacity = nameplate_capacity # System Capacity (kW) s.SystemDesign.dc_ac_ratio = 1.1 #DC to AC ratio s.SystemDesign.inv_eff = 96 #default inverter eff @ rated power (%) s.SystemDesign.losses = 14 #other DC losses (%) (14% is default from documentation) ######################## s.execute() solarCF = np.array(s.Outputs.ac) / (nameplate_capacity * 1000) #convert AC generation (w) to capacity factor if args.verbose: print('\t','Average Solar CF = {cf}'.format(cf=round(np.average(solarCF),2))) return solarCF
def retrieve_data(solar_plant, email, api_key, year="2016", rate_limit=0.5): """Retrieves irradiance data from NSRDB and calculate the power output using the System Adviser Model (SAM). :param pandas.DataFrame solar_plant: plant data frame. :param str email: email used to`sign up <https://developer.nrel.gov/signup/>`_. :param str api_key: API key. :param str year: year. :param int/float rate_limit: minimum seconds to wait between requests to NREL :return: (*pandas.DataFrame*) -- data frame with *'Pout'*, *'plant_id'*, *'ts'* and *'ts_id'* as columns. Values are power output for a 1MW generator. """ # SAM only takes 365 days. try: leap_day = (pd.Timestamp("%s-02-29-00" % year).dayofyear - 1) * 24 is_leap_year = True dates = pd.date_range(start="%s-01-01-00" % 2015, freq="H", periods=365 * 24) dates = dates.map(lambda t: t.replace(year=int(year))) except ValueError: leap_day = None is_leap_year = False dates = pd.date_range(start="%s-01-01-00" % year, freq="H", periods=365 * 24) # Identify unique location coord = get_plant_id_unique_location(solar_plant) data = pd.DataFrame({"Pout": [], "plant_id": [], "ts": [], "ts_id": []}) # PV tracking ratios # By state and by interconnect when EIA data do not have any solar PV in # the state pv_info = get_pv_tracking_data() zone_id = solar_plant.zone_id.unique() frac = {} for i in zone_id: state = id2abv[i] frac[i] = get_pv_tracking_ratio_state(pv_info, [state]) if frac[i] is None: frac[i] = get_pv_tracking_ratio_state( pv_info, list(interconnect2abv[abv2interconnect[state]])) # Inverter Loading Ratio ilr = 1.25 api = NrelApi(email, api_key, rate_limit) for key in tqdm(coord.keys(), total=len(coord)): lat, lon = key[1], key[0] solar_data = api.get_psm3_at( lat, lon, attributes="dhi,dni,wind_speed,air_temperature", year=year, leap_day=False, dates=dates, ).to_dict() for i in coord[key]: data_site = pd.DataFrame({ "ts": pd.date_range(start="%s-01-01-00" % year, end="%s-12-31-23" % year, freq="H") }) data_site["ts_id"] = range(1, len(data_site) + 1) data_site["plant_id"] = i power = 0 for j, axis in enumerate([0, 2, 4]): pv_dict = { "system_capacity": ilr, "dc_ac_ratio": ilr, "tilt": 30, "azimuth": 180, "inv_eff": 94, "losses": 14, "array_type": axis, "gcr": 0.4, "adjust:constant": 0, } pv_dat = pssc.dict_to_ssc_table(pv_dict, "pvwattsv7") pv = PVWatts.wrap(pv_dat) pv.SolarResource.assign({"solar_resource_data": solar_data}) pv.execute() ratio = frac[solar_plant.loc[i].zone_id][j] power += ratio * np.array(pv.Outputs.gen) if is_leap_year is True: data_site["Pout"] = np.insert(power, leap_day, power[leap_day - 24:leap_day]) else: data_site["Pout"] = power data = data.append(data_site, ignore_index=True, sort=False) data["plant_id"] = data["plant_id"].astype(np.int32) data["ts_id"] = data["ts_id"].astype(np.int32) data.sort_values(by=["ts_id", "plant_id"], inplace=True) data.reset_index(inplace=True, drop=True) return data
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']))
# PVWatts simulation. # The json file is generated from SAM using the "Generate Code" menu item # in the added simulation case. Choose "JSON for inputs" and a .json file # with the title of your simulation case will be created where you select with # the "Open" button on the file dialog. json_file_path = 'Examples/100kW_PVWatts.json' # Change this file name to yours! with open(json_file_path) as f: dic = json.load(f) # The next seven lines are needed to load the PySAM data structures with the # inputs from the json file. pv_dat = pssc.dict_to_ssc_table(dic, "pvwattsv7") grid_dat = pssc.dict_to_ssc_table(dic, "grid") ur_dat = pssc.dict_to_ssc_table(dic, "utilityrate5") cl_dat = pssc.dict_to_ssc_table(dic, "cashloan") pv = PVWatts.wrap(pv_dat) grid = Grid.from_existing(pv) ur = UtilityRate.from_existing(pv) cl = Cashloan.from_existing(pv) grid.assign(Grid.wrap(grid_dat).export()) ur.assign(UtilityRate.wrap(ur_dat).export()) cl.assign(Cashloan.wrap(cl_dat).export()) # The models are executed in order. Note that the outputs from the first # simulation are automatically available for the next one, and so on. :-) pv.execute() grid.execute() ur.execute() cl.execute() if verbose: # Print out some results. The variable names can be found in
('Json file', '*.json'), ('All files', '*.*'), ]) if verbose: print(json_file_path) except NameError: print('NameError: with the json file') else: with open(json_file_path) as f: dic = json.load(f) pv_dat = pssc.dict_to_ssc_table(dic, "pvwattsv7") grid_dat = pssc.dict_to_ssc_table(dic, "grid") ur_dat = pssc.dict_to_ssc_table(dic, "utilityrate5") cl_dat = pssc.dict_to_ssc_table(dic, "cashloan") pv = PVWattsCommercial.wrap(pv_dat) grid = Grid.from_existing(pv) ur = UtilityRate.from_existing(pv, 'PVWattsCommercial') cl = Cashloan.from_existing(pv, 'PVWattsCommercial') grid.assign(Grid.wrap(grid_dat).export()) ur.assign(UtilityRate.wrap(ur_dat).export()) cl.assign(Cashloan.wrap(cl_dat).export()) degradation = cl.SystemOutput.degradation[0] if verbose: print('degradation', degradation) if testing: pv.execute() grid.execute() ur.execute() cl.execute()
# See function documentation for full parameter list nsrdbfetcher = tools.FetchResourceFiles(tech='solar', nrel_api_key=sam_api_key, nrel_api_email=sam_email) # --- List of (lon, lat) tuples or Shapely points --- lon_lats = [(lon, lat)] nsrdbfetcher.fetch(lon_lats) # --- Get resource data file path --- nsrdb_path_dict = nsrdbfetcher.resource_file_paths_dict nsrdb_fp = nsrdb_path_dict[lon_lats[0]] if nsrdb_fp is not None: # --- Initialize Generator --- generator = pv.default('PVWattsSingleOwner') generator.SolarResource.assign({'solar_resource_file': nsrdb_fp}) # --- Initialize Financial Model --- financial = so.from_existing(generator, 'PVWattsSingleOwner') # --- Execute Models --- print('PVWatts V7 - Single Owner Results') generator.execute() print('capacity factor = {:.3f}'.format(generator.Outputs.capacity_factor)) financial.execute() print('npv = ${:,.2f}'.format( financial.Outputs.project_return_aftertax_npv)) else: print('Solar resource file does not exist. Skipping solar simulations.')
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}