def test_get_solarposition_altitude(): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, golden.longitude, altitude=golden.altitude, temperature=11) this_expected = expected.copy() this_expected.index = times this_expected = np.round(this_expected, 5) ephem_data = np.round(ephem_data, 5) assert_frame_equal(this_expected, ephem_data[this_expected.columns]) ephem_data = solarposition.get_solarposition(times, golden.latitude, golden.longitude, altitude=0.0, temperature=11) this_expected = expected.copy() this_expected.index = times this_expected = np.round(this_expected, 5) ephem_data = np.round(ephem_data, 5) try: assert_frame_equal(this_expected, ephem_data[this_expected.columns]) except AssertionError: pass else: raise AssertionError
def get_solarposition(self, times, pressure=None, temperature=12, **kwargs): """ Uses the :py:func:`solarposition.get_solarposition` function to calculate the solar zenith, azimuth, etc. at this location. Parameters ---------- times : DatetimeIndex pressure : None, float, or array-like, default None If None, pressure will be calculated using :py:func:`atmosphere.alt2pres` and ``self.altitude``. temperature : None, float, or array-like, default 12 kwargs passed to :py:func:`solarposition.get_solarposition` Returns ------- solar_position : DataFrame Columns depend on the ``method`` kwarg, but always include ``zenith`` and ``azimuth``. """ if pressure is None: pressure = atmosphere.alt2pres(self.altitude) return solarposition.get_solarposition(times, latitude=self.latitude, longitude=self.longitude, altitude=self.altitude, pressure=pressure, temperature=temperature, **kwargs)
def test_get_solarposition_error(): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, golden.longitude, pressure=82000, temperature=11, method='error this')
def test_get_solarposition_no_kwargs(expected_solpos): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, golden.longitude) expected_solpos.index = times expected_solpos = np.round(expected_solpos, 2) ephem_data = np.round(ephem_data, 2) assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
def test_get_solarposition_no_kwargs(): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, golden.longitude) this_expected = expected.copy() this_expected.index = times this_expected = np.round(this_expected, 2) ephem_data = np.round(ephem_data, 2) assert_frame_equal(this_expected, ephem_data[this_expected.columns])
def test_get_solarposition_pressure(pressure, expected): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, golden.longitude, pressure=pressure, temperature=11) this_expected = expected.copy() this_expected.index = times this_expected = np.round(this_expected, 5) ephem_data = np.round(ephem_data, 5) assert_frame_equal(this_expected, ephem_data[this_expected.columns])
def test_get_solarposition_deltat(delta_t, method, expected_solpos_multi, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=2, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, golden.longitude, pressure=82000, delta_t=delta_t, temperature=11, method=method) this_expected = expected_solpos_multi this_expected.index = times this_expected = np.round(this_expected, 5) ephem_data = np.round(ephem_data, 5) assert_frame_equal(this_expected, ephem_data[this_expected.columns])
def test_get_solarposition_deltat(delta_t, method, expected, golden): times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30), periods=2, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, golden.longitude, pressure=82000, delta_t=delta_t, temperature=11, method=method) this_expected = expected.copy() this_expected.index = times this_expected = np.round(this_expected, 5) ephem_data = np.round(ephem_data, 5) assert_frame_equal(this_expected, ephem_data[this_expected.columns])
def test_get_solarposition_altitude(altitude, expected, golden, expected_solpos): times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, golden.longitude, altitude=altitude, temperature=11) if isinstance(expected, str) and expected == 'expected_solpos': expected = expected_solpos this_expected = expected.copy() this_expected.index = times this_expected = np.round(this_expected, 5) ephem_data = np.round(ephem_data, 5) assert_frame_equal(this_expected, ephem_data[this_expected.columns])
def test_haurwitz(): tus = Location(32.2, -111, 'US/Arizona', 700) times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h') times_localized = times.tz_localize(tus.tz) ephem_data = solarposition.get_solarposition(times_localized, tus.latitude, tus.longitude) expected = pd.DataFrame(np.array([[0.], [0.], [82.85934048], [699.74514735], [1016.50198354], [838.32103769], [271.90853863], [0.], [0.]]), columns=['ghi'], index=times_localized) out = clearsky.haurwitz(ephem_data['zenith']) assert_frame_equal(expected, out)
def test_simplified_solis_series_elevation(): tus = Location(32.2, -111, 'US/Arizona', 700) times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h') times_localized = times.tz_localize(tus.tz) ephem_data = solarposition.get_solarposition(times_localized, tus.latitude, tus.longitude) expected = pd.DataFrame( np.array([[0., 0., 0.], [0., 0., 0.], [377.80060035, 79.91931339, 42.77453223], [869.47538184, 706.37903999, 110.05635962], [958.89448856, 1062.44821373, 129.02349236], [913.3209839, 860.48978599, 118.94598678], [634.01066762, 256.00505836, 72.18396705], [0., 0., 0.], [0., 0., 0.]]), columns=['dni', 'ghi', 'dhi'], index=times_localized) expected = expected[['dhi', 'dni', 'ghi']] out = clearsky.simplified_solis(ephem_data['apparent_elevation']) assert_frame_equal(expected, out)
def transform(self, elevation_round=1, azimuth_round=2, agg_func=np.nanmean): # Solar position subroutine requires TZ-aware time stamps or UTC time # stamps, but we typically have non-TZ-aware local time, without DST # shifts (which is the most natural for solar data). So, step 1 is to # convert the time to UTC. times = self.data.index.shift(-self.tz_offset, "H") # run solar position subroutine solpos = get_solarposition(times, self.lat, self.lon) # reset the time from UTC to local solpos.index = self.data.index # join the calculated angles with the power measurments triples = solpos[["apparent_elevation", "azimuth"]].join(self.normed_data.loc[self.bix]) triples = triples.dropna() # cut off all the entries corresponding to the sun below the horizon triples = triples[triples["apparent_elevation"] >= 0] # a function for rounding to the nearest integer c (e.g. 2, 5, 10...) def my_round(x, c): return c * np.round(x / c, 0) # create elevation and azimuth bins triples["elevation_angle"] = my_round(triples["apparent_elevation"], elevation_round) triples["azimuth_angle"] = my_round(triples["azimuth"], azimuth_round) # group by bins grouped = triples.groupby(["azimuth_angle", "elevation_angle"]) # calculation aggregation function for each bin grouped = grouped.agg(agg_func) # remove other columns grouped = grouped["normalized-power"] # convert tall data format to wide data format, fill misisng values # with zero, and transpose estimates = grouped.unstack().fillna(0).T # reverse the first axis estimates = estimates.iloc[::-1] self.tranformed_data = estimates
def test_ineichen_series(): tus = Location(32.2, -111, 'US/Arizona', 700) times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h') times_localized = times.tz_localize(tus.tz) ephem_data = solarposition.get_solarposition(times_localized, tus.latitude, tus.longitude) am = atmosphere.relativeairmass(ephem_data['apparent_zenith']) am = atmosphere.absoluteairmass(am, atmosphere.alt2pres(tus.altitude)) expected = pd.DataFrame( np.array([[0., 0., 0.], [0., 0., 0.], [91.12492792, 321.16092181, 51.17628184], [716.46580533, 888.90147035, 99.5050056], [1053.42066043, 953.24925854, 116.32868969], [863.54692781, 922.06124712, 106.95536561], [271.06382274, 655.44925241, 73.05968071], [0., 0., 0.], [0., 0., 0.]]), columns=['ghi', 'dni', 'dhi'], index=times_localized) out = clearsky.ineichen(ephem_data['apparent_zenith'], am, 3) assert_frame_equal(expected, out)
def test_get_solarposition_deltat(delta_t, method, expected_solpos_multi, golden): times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30), periods=2, freq='D', tz=golden.tz) with warnings.catch_warnings(): # don't warn on method reload or num threads warnings.simplefilter("ignore") ephem_data = solarposition.get_solarposition(times, golden.latitude, golden.longitude, pressure=82000, delta_t=delta_t, temperature=11, method=method) this_expected = expected_solpos_multi this_expected.index = times this_expected = np.round(this_expected, 5) ephem_data = np.round(ephem_data, 5) assert_frame_equal(this_expected, ephem_data[this_expected.columns])
def get_solarposition(arg_time, arg_lat, arg_long): return_data = {} solarpos = solarposition.get_solarposition(time=arg_time, latitude=arg_lat, longitude=arg_long, altitude=None, pressure=None) solarpos_data = json.loads(solarpos.to_json()) zenith_dict = solarpos_data["zenith"] elevation_dict = solarpos_data["elevation"] azimuth_dict = solarpos_data["azimuth"] azimuth = list(azimuth_dict.values())[0] zenith = list(zenith_dict.values())[0] return_data['zenith'] = zenith return_data['azimuth'] = azimuth return return_data
def get_solar_position(time, latitude, longitude, altitude, pvlib_method="nrel_numba", **kwargs): """ Get solar position Use pvlib library and use of numba by default :param time: :param latitude: :param longitude: :param altitude: :param pvlib_method: :return: """ return get_solarposition(time, latitude, longitude, altitude, method=pvlib_method, **kwargs)
def test_simplified_solis_series_elevation(): tus = Location(32.2, -111, 'US/Arizona', 700) times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h') times_localized = times.tz_localize(tus.tz) ephem_data = solarposition.get_solarposition(times_localized, tus.latitude, tus.longitude) expected = pd.DataFrame( np.array([[ 0. , 0. , 0. ], [ 0. , 0. , 0. ], [ 377.80060035, 79.91931339, 42.77453223], [ 869.47538184, 706.37903999, 110.05635962], [ 958.89448856, 1062.44821373, 129.02349236], [ 913.3209839 , 860.48978599, 118.94598678], [ 634.01066762, 256.00505836, 72.18396705], [ 0. , 0. , 0. ], [ 0. , 0. , 0. ]]), columns=['dni', 'ghi', 'dhi'], index=times_localized) expected = expected[['dhi', 'dni', 'ghi']] out = clearsky.simplified_solis(ephem_data['apparent_elevation']) assert_frame_equal(expected, out)
def get_from_pvgis(lat, lon, tz): """Return the pvgis data""" # PVGIS information info = get_pvgis_tmy(lat, lon) # Altitude alt = info[2]['location']['elevation'] # Get th principal information df = info[0] # Convert UTC to time zone df.index = df.index.tz_convert(tz) df.index.name = f'Time {tz}' # Get the solar position solpos = get_solarposition(time=df.index, latitude=lat, longitude=lon, altitude=alt, pressure=df.SP, temperature=df.T2m) # Create the schema data = df data = data.drop(df.columns, axis=1) data['HourOfDay'] = df.index.hour data['Zenith'] = solpos['zenith'] data['Temperature'] = df.T2m data['Humidity'] = df.RH data['WindSpeed'] = df.WS10m data['WindDirection'] = df.WD10m data['PrecipitableWater'] = gueymard94_pw(df.T2m, df.RH) data['Pressure'] = df.SP data['ExtraRadiation'] = get_extra_radiation(df.index) data['GHI'] = df['G(h)'] return data
def sun_position(dates=None, daydate=_day, latitude=_latitude, longitude=_longitude, altitude=_altitude, timezone=_timezone, filter_night=True): """ Sun position Args: dates: a pandas.DatetimeIndex specifying the dates at which sun position is required.If None, daydate is used and one position per hour is generated daydate: (str) yyyy-mm-dd (not used if dates is not None). latitude: float longitude: float altitude: (float) altitude in m timezone: a string identifying the timezone to be associated to dates if dates is not already localised. This args is not used if dates are already localised filter_night (bool) : Should positions of sun during night be filtered ? Returns: a pandas dataframe with sun position at requested dates indexed by localised dates. Sun azimtuth is given from North, positive clockwise. """ if dates is None: dates = pandas.date_range(daydate, periods=24, freq='H') if dates.tz is None: times = dates.tz_localize(timezone) else: times = dates df = get_solarposition(times, latitude, longitude, altitude) sunpos = pandas.DataFrame( {'elevation': df['apparent_elevation'], 'azimuth': df['azimuth'], 'zenith': df['apparent_zenith']}, index=df.index) if filter_night and sunpos is not None: sunpos = sunpos.loc[sunpos['elevation'] > 0, :] return sunpos
def test_solpos_calc(self): data={ 'lat': 38.2, 'lon': -122.1, 'freq': 'T', 'tz': -8, 'start': '2018-01-01 07:00', 'end': '2018-01-01 08:00' } r = self.client.get('/api/v1/pvlib/solarposition/', data) self.assertEqual(r.status_code, 200) s = pd.DataFrame(r.json()).T t = pd.DatetimeIndex(s.index) times = pd.DatetimeIndex( start=data['start'], end=data['end'], freq=data['freq'], tz='Etc/GMT{:+d}'.format(-data['tz'])) solpos = solarposition.get_solarposition( times, data['lat'], data['lon']) assert np.allclose( times.values.astype(int), t.values.astype(int)) assert np.allclose(solpos.apparent_zenith, s.apparent_zenith) assert np.allclose(solpos.azimuth, s.azimuth)
def test_ineichen_series(): tus = Location(32.2, -111, 'US/Arizona', 700) times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h') times_localized = times.tz_localize(tus.tz) ephem_data = solarposition.get_solarposition(times_localized, tus.latitude, tus.longitude) am = atmosphere.relativeairmass(ephem_data['apparent_zenith']) am = atmosphere.absoluteairmass(am, atmosphere.alt2pres(tus.altitude)) expected = pd.DataFrame(np. array([[ 0. , 0. , 0. ], [ 0. , 0. , 0. ], [ 91.12492792, 321.16092181, 51.17628184], [ 716.46580533, 888.90147035, 99.5050056 ], [ 1053.42066043, 953.24925854, 116.32868969], [ 863.54692781, 922.06124712, 106.95536561], [ 271.06382274, 655.44925241, 73.05968071], [ 0. , 0. , 0. ], [ 0. , 0. , 0. ]]), columns=['ghi', 'dni', 'dhi'], index=times_localized) out = clearsky.ineichen(ephem_data['apparent_zenith'], am, 3) assert_frame_equal(expected, out)
def detectLocation(pickleLocation, filePrefix): with open(f'{pickleLocation}/{filePrefix}-danceData.json', 'r') as f: waggles = json.load(f) date = dt.datetime.strptime(waggles['recordDate'], '%a %b %d %H:%M:%S %Y') lat, lon = (37.561343, -77.465806) solarposition = sp.get_solarposition(date, lat, lon) waggles['azimuth'] = str(solarposition['azimuth'][0]) keys = [k for k, v in waggles.items() if 'waggle-' in k] for waggle in keys: waggles[waggle]['distance'] = get_distance(waggles, waggle) prettyPrint(waggles) with open(f'{pickleLocation}/{filePrefix}-danceData.json', 'w') as f: json.dump(waggles, f)
def calc_radiation(self, data, cloud_type='total_clouds'): ''' Determines shortwave radiation values if they are missing from the model data. Parameters ---------- data: netcdf Query data formatted in netcdf format. cloud_type: string Type of cloud cover to use for calculating radiation values. ''' self.rad_type = {} if not self.lbox and cloud_type in self.modelvariables: cloud_prct = self.data[cloud_type] solpos = get_solarposition(self.time, self.location) self.zenith = np.array(solpos.zenith.tz_convert('UTC')) for rad in ['dni', 'dhi', 'ghi']: if self.model_name is 'HRRR_ESRL': # HRRR_ESRL is the only model with the # correct equation of time. if rad in self.modelvariables: self.data[rad] = pd.Series( data[self.variables[rad]][:].squeeze(), index=self.time) self.rad_type[rad] = 'forecast' self.data[rad].fillna(0, inplace=True) else: for rad in ['dni', 'dhi', 'ghi']: self.rad_type[rad] = 'liujordan' self.data[rad] = liujordan(self.zenith, cloud_prct)[rad] self.data[rad].fillna(0, inplace=True) for var in ['dni', 'dhi', 'ghi']: self.data[var].fillna(0, inplace=True) self.var_units[var] = '$W m^{-2}$'
def get_solarposition(self, times, pressure=None, temperature=12, **kwargs): """ Uses the :py:func:`solarposition.get_solarposition` function to calculate the solar zenith, azimuth, etc. at this location. Parameters ---------- times : pandas.DatetimeIndex Must be localized or UTC will be assumed. pressure : None, float, or array-like, default None If None, pressure will be calculated using :py:func:`atmosphere.alt2pres` and ``self.altitude``. temperature : None, float, or array-like, default 12 kwargs passed to :py:func:`solarposition.get_solarposition` Returns ------- solar_position : DataFrame Columns depend on the ``method`` kwarg, but always include ``zenith`` and ``azimuth``. """ if pressure is None: pressure = atmosphere.alt2pres(self.altitude) return solarposition.get_solarposition(times, latitude=self.latitude, longitude=self.longitude, altitude=self.altitude, pressure=pressure, temperature=temperature, **kwargs)
def calc_radiation(self, data, cloud_type='total_clouds'): ''' Determines shortwave radiation values if they are missing from the model data. Parameters ---------- data: netcdf Query data formatted in netcdf format. cloud_type: string Type of cloud cover to use for calculating radiation values. ''' self.rad_type = {} if not self.lbox and cloud_type in self.modelvariables: cloud_prct = self.data[cloud_type] solpos = get_solarposition(self.time, self.location) self.zenith = np.array(solpos.zenith.tz_convert('UTC')) for rad in ['dni','dhi','ghi']: if self.model_name is 'HRRR_ESRL': # HRRR_ESRL is the only model with the # correct equation of time. if rad in self.modelvariables: self.data[rad] = pd.Series( data[self.variables[rad]][:].squeeze(), index=self.time) self.rad_type[rad] = 'forecast' self.data[rad].fillna(0, inplace=True) else: for rad in ['dni','dhi','ghi']: self.rad_type[rad] = 'liujordan' self.data[rad] = liujordan(self.zenith, cloud_prct)[rad] self.data[rad].fillna(0, inplace=True) for var in ['dni', 'dhi', 'ghi']: self.data[var].fillna(0, inplace=True) self.var_units[var] = '$W m^{-2}$'
from pvlib import solarposition, atmosphere, clearsky import pandas as pd import pvlib as pvlib import matplotlib.pyplot as plt import pylab lat = 34.42 lon = -119.842 alt = 6200 date = '2019-12-06' # Must be longer than a day to make sure we see a continuous cycle index = pd.date_range(start=date, freq='1s', periods=24 * 60 * 60 * 2) # solar position data frame (first plot) solar_df = solarposition.get_solarposition(index, lat, lon) # this loops thru the elements and picks the left/right indexes for one continuous cycle based on the apparent_elevation # graph. These values are then used to trim the dataframes so we are only operating on data where the sun is present. left = -1 right = -1 prev = solar_df.iloc[0]["apparent_elevation"] for t in solar_df.itertuples(): curr = t.apparent_elevation if left == -1 and prev <= 0 and curr > 0: left = t.Index if left != -1 and right == -1 and prev >= 0 and curr < 0: right = t.Index break prev = curr
def calculate_overcast_spectrl2(): """ This example will loop over a range of cloud covers and latitudes (at longitude=0) for a specific date and calculate the spectral irradiance with and without accounting for clouds. Clouds are accounted for by applying the cloud opacity factor defined in [1]. Several steps are required: 1. Calculate the atmospheric and solar conditions for the location and time 2. Calculate the spectral irradiance using `pvlib.spectrum.spectrl2` for clear sky conditions 3. Calculate the dni, dhi, and ghi for cloudy conditions using `pvlib.irradiance.campbell_norman` 4. Determine total in-plane irradiance and its beam, sky diffuse and ground reflected components for cloudy conditions - `pvlib.irradiance.get_total_irradiance` 5. Calculate the dni, dhi, and ghi for clear sky conditions using `pvlib.irradiance.campbell_norman` 6. Determine total in-plane irradiance and its beam, sky diffuse and ground reflected components for clear sky conditions - `pvlib.irradiance.get_total_irradiance` 7. Calculate the cloud opacity factor [1] and scale the spectral results from step 4 - func cloud_opacity_factor 8. Plot the results - func plot_spectral_irradiance """ month = 2 hour_of_day = 12 altitude = 0.0 longitude = 0.0 latitudes = [10, 40] # cloud cover in fraction units cloud_covers = [0.2, 0.5] water_vapor_content = 0.5 tau500 = 0.1 ground_albedo = 0.06 ozone = 0.3 surface_tilt = 0.0 ctime, pv_system = setup_pv_system(month, hour_of_day) for cloud_cover in cloud_covers: for latitude in latitudes: sol = get_solarposition(ctime, latitude, longitude) az = sol['apparent_zenith'].to_numpy() airmass_relative = get_relative_airmass(az, model='kastenyoung1989') pressure = pvlib.atmosphere.alt2pres(altitude) az = sol['apparent_zenith'].to_numpy() azimuth = sol['azimuth'].to_numpy() surface_azimuth = pv_system['surface_azimuth'] transmittance = (1.0 - cloud_cover) * 0.75 calc_aoi = aoi(surface_tilt, surface_azimuth, az, azimuth) # day of year is an int64index array so access first item day_of_year = ctime.dayofyear[0] spectra = pvlib.spectrum.spectrl2( apparent_zenith=az, aoi=calc_aoi, surface_tilt=surface_tilt, ground_albedo=ground_albedo, surface_pressure=pressure, relative_airmass=airmass_relative, precipitable_water=water_vapor_content, ozone=ozone, aerosol_turbidity_500nm=tau500, dayofyear=day_of_year) irrads_clouds = campbell_norman(sol['zenith'].to_numpy(), transmittance) # Convert the irradiance to a plane with tilt zero # horizontal to the earth. This is done applying # tilt=0 to POA calculations using the output from # `campbell_norman`. The POA calculations include # calculating sky and ground diffuse light where # specific models can be selected (we use default). poa_irr_clouds = get_total_irradiance( surface_tilt=surface_tilt, surface_azimuth=pv_system['surface_azimuth'], dni=irrads_clouds['dni'], ghi=irrads_clouds['ghi'], dhi=irrads_clouds['dhi'], solar_zenith=sol['apparent_zenith'], solar_azimuth=sol['azimuth']) show_info(latitude, poa_irr_clouds) zen = sol['zenith'].to_numpy() irr_clearsky = campbell_norman(zen, transmittance=0.75) poa_irr_clearsky = get_total_irradiance( surface_tilt=surface_tilt, surface_azimuth=pv_system['surface_azimuth'], dni=irr_clearsky['dni'], ghi=irr_clearsky['ghi'], dhi=irr_clearsky['dhi'], solar_zenith=sol['apparent_zenith'], solar_azimuth=sol['azimuth']) show_info(latitude, poa_irr_clearsky) poa_dr = poa_irr_clouds['poa_direct'].values poa_diff = poa_irr_clouds['poa_diffuse'].values poa_global = poa_irr_clouds['poa_global'].values f_dir, f_diff = cloud_opacity_factor(poa_dr, poa_diff, poa_global, spectra) plot_spectral_irr(spectra, f_dir, f_diff, lat=latitude, doy=day_of_year, year=ctime.year[0], clouds=cloud_cover)
from pvlib import tmy from pvlib import pvsystem from pvlib import clearsky from pvlib import irradiance from pvlib import atmosphere from pvlib import solarposition from pvlib.location import Location latitude = 32.2 longitude = -111 tus = Location(latitude, longitude, 'US/Arizona', 700, 'Tucson') times = pd.date_range(start=datetime.datetime(2014,1,1), end=datetime.datetime(2014,1,2), freq='1Min') ephem_data = solarposition.get_solarposition(times, latitude=latitude, longitude=longitude, method='nrel_numpy') irrad_data = clearsky.ineichen(times, latitude=latitude, longitude=longitude, linke_turbidity=3, solarposition_method='nrel_numpy') aoi = irradiance.aoi(0, 0, ephem_data['apparent_zenith'], ephem_data['azimuth']) am = atmosphere.relativeairmass(ephem_data.apparent_zenith) meta = {'latitude': 37.8, 'longitude': -122.3, 'altitude': 10, 'Name': 'Oakland', 'State': 'CA', 'TZ': -8}
def basic_chain(times, latitude, longitude, module_parameters, inverter_parameters, irradiance=None, weather=None, surface_tilt=None, surface_azimuth=None, orientation_strategy=None, transposition_model='haydavies', solar_position_method='nrel_numpy', airmass_model='kastenyoung1989', altitude=None, pressure=None, **kwargs): """ An experimental function that computes all of the modeling steps necessary for calculating power or energy for a PV system at a given location. Parameters ---------- times : DatetimeIndex Times at which to evaluate the model. latitude : float. Positive is north of the equator. Use decimal degrees notation. longitude : float. Positive is east of the prime meridian. Use decimal degrees notation. module_parameters : None, dict or Series Module parameters as defined by the SAPM. inverter_parameters : None, dict or Series Inverter parameters as defined by the CEC. irradiance : None or DataFrame, default None If None, calculates clear sky data. Columns must be 'dni', 'ghi', 'dhi'. weather : None or DataFrame, default None If None, assumes air temperature is 20 C and wind speed is 0 m/s. Columns must be 'wind_speed', 'temp_air'. surface_tilt : None, float or Series, default None Surface tilt angles in decimal degrees. The tilt angle is defined as degrees from horizontal (e.g. surface facing up = 0, surface facing horizon = 90) surface_azimuth : None, float or Series, default None Surface azimuth angles in decimal degrees. The azimuth convention is defined as degrees east of north (North=0, South=180, East=90, West=270). orientation_strategy : None or str, default None The strategy for aligning the modules. If not None, sets the ``surface_azimuth`` and ``surface_tilt`` properties of the ``system``. Allowed strategies include 'flat', 'south_at_latitude_tilt'. Ignored for SingleAxisTracker systems. transposition_model : str, default 'haydavies' Passed to system.get_irradiance. solar_position_method : str, default 'nrel_numpy' Passed to solarposition.get_solarposition. airmass_model : str, default 'kastenyoung1989' Passed to atmosphere.relativeairmass. altitude : None or float, default None If None, computed from pressure. Assumed to be 0 m if pressure is also None. pressure : None or float, default None If None, computed from altitude. Assumed to be 101325 Pa if altitude is also None. **kwargs Arbitrary keyword arguments. See code for details. Returns ------- output : (dc, ac) Tuple of DC power (with SAPM parameters) (DataFrame) and AC power (Series). """ # use surface_tilt and surface_azimuth if provided, # otherwise set them using the orientation_strategy if surface_tilt is not None and surface_azimuth is not None: pass elif orientation_strategy is not None: surface_tilt, surface_azimuth = \ get_orientation(orientation_strategy, latitude=latitude) else: raise ValueError('orientation_strategy or surface_tilt and ' 'surface_azimuth must be provided') times = times if altitude is None and pressure is None: altitude = 0. pressure = 101325. elif altitude is None: altitude = atmosphere.pres2alt(pressure) elif pressure is None: pressure = atmosphere.alt2pres(altitude) solar_position = solarposition.get_solarposition( times, latitude, longitude, altitude=altitude, pressure=pressure, method=solar_position_method, **kwargs) # possible error with using apparent zenith with some models airmass = atmosphere.get_relative_airmass( solar_position['apparent_zenith'], model=airmass_model) airmass = atmosphere.get_absolute_airmass(airmass, pressure) dni_extra = pvlib.irradiance.get_extra_radiation(solar_position.index) aoi = pvlib.irradiance.aoi(surface_tilt, surface_azimuth, solar_position['apparent_zenith'], solar_position['azimuth']) if irradiance is None: linke_turbidity = clearsky.lookup_linke_turbidity( solar_position.index, latitude, longitude) irradiance = clearsky.ineichen(solar_position['apparent_zenith'], airmass, linke_turbidity, altitude=altitude, dni_extra=dni_extra) total_irrad = pvlib.irradiance.get_total_irradiance( surface_tilt, surface_azimuth, solar_position['apparent_zenith'], solar_position['azimuth'], irradiance['dni'], irradiance['ghi'], irradiance['dhi'], model=transposition_model, dni_extra=dni_extra) if weather is None: weather = {'wind_speed': 0, 'temp_air': 20} temps = pvsystem.sapm_celltemp(total_irrad['poa_global'], weather['wind_speed'], weather['temp_air']) effective_irradiance = pvsystem.sapm_effective_irradiance( total_irrad['poa_direct'], total_irrad['poa_diffuse'], airmass, aoi, module_parameters) dc = pvsystem.sapm(effective_irradiance, temps['temp_cell'], module_parameters) ac = pvsystem.snlinverter(dc['v_mp'], dc['p_mp'], inverter_parameters) return dc, ac
def ineichen(time, location, linke_turbidity=None, solarposition_method='pyephem', zenith_data=None, airmass_model='young1994', airmass_data=None, interp_turbidity=True): ''' Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model Implements the Ineichen and Perez clear sky model for global horizontal irradiance (GHI), direct normal irradiance (DNI), and calculates the clear-sky diffuse horizontal (DHI) component as the difference between GHI and DNI*cos(zenith) as presented in [1, 2]. A report on clear sky models found the Ineichen/Perez model to have excellent performance with a minimal input data set [3]. Default values for montly Linke turbidity provided by SoDa [4, 5]. Parameters ----------- time : pandas.DatetimeIndex location : pvlib.Location linke_turbidity : None or float If None, uses ``LinkeTurbidities.mat`` lookup table. solarposition_method : string Sets the solar position algorithm. See solarposition.get_solarposition() zenith_data : None or Series If None, ephemeris data will be calculated using ``solarposition_method``. airmass_model : string See pvlib.airmass.relativeairmass(). airmass_data : None or Series If None, absolute air mass data will be calculated using ``airmass_model`` and location.alitude. interp_turbidity : bool If ``True``, interpolates the monthly Linke turbidity values found in ``LinkeTurbidities.mat`` to daily values. Returns -------- DataFrame with the following columns: ``ghi, dni, dhi``. Notes ----- If you are using this function in a loop, it may be faster to load LinkeTurbidities.mat outside of the loop and feed it in as a keyword argument, rather than having the function open and process the file each time it is called. References ---------- [1] P. Ineichen and R. Perez, "A New airmass independent formulation for the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157, 2002. [2] R. Perez et. al., "A New Operational Model for Satellite-Derived Irradiances: Description and Validation", Solar Energy, vol 73, pp. 307-317, 2002. [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear Sky Models: Implementation and Analysis", Sandia National Laboratories, SAND2012-2389, 2012. [4] http://www.soda-is.com/eng/services/climat_free_eng.php#c5 (obtained July 17, 2012). [5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc. ISES Solar World Congress, June 2003. Goteborg, Sweden. ''' # Initial implementation of this algorithm by Matthew Reno. # Ported to python by Rob Andrews # Added functionality by Will Holmgren (@wholmgren) I0 = irradiance.extraradiation(time.dayofyear) if zenith_data is None: ephem_data = solarposition.get_solarposition( time, location, method=solarposition_method) time = ephem_data.index # fixes issue with time possibly not being tz-aware try: ApparentZenith = ephem_data['apparent_zenith'] except KeyError: ApparentZenith = ephem_data['zenith'] logger.warning('could not find apparent_zenith. using zenith') else: ApparentZenith = zenith_data #ApparentZenith[ApparentZenith >= 90] = 90 # can cause problems in edge cases if linke_turbidity is None: TL = lookup_linke_turbidity(time, location.latitude, location.longitude, interp_turbidity=interp_turbidity) else: TL = linke_turbidity # Get the absolute airmass assuming standard local pressure (per # alt2pres) using Kasten and Young's 1989 formula for airmass. if airmass_data is None: AMabsolute = atmosphere.absoluteairmass( airmass_relative=atmosphere.relativeairmass( ApparentZenith, airmass_model), pressure=atmosphere.alt2pres(location.altitude)) else: AMabsolute = airmass_data fh1 = np.exp(-location.altitude / 8000.) fh2 = np.exp(-location.altitude / 1250.) cg1 = 5.09e-05 * location.altitude + 0.868 cg2 = 3.92e-05 * location.altitude + 0.0387 logger.debug('fh1=%s, fh2=%s, cg1=%s, cg2=%s', fh1, fh2, cg1, cg2) # Dan's note on the TL correction: By my reading of the publication on # pages 151-157, Ineichen and Perez introduce (among other things) three # things. 1) Beam model in eqn. 8, 2) new turbidity factor in eqn 9 and # appendix A, and 3) Global horizontal model in eqn. 11. They do NOT appear # to use the new turbidity factor (item 2 above) in either the beam or GHI # models. The phrasing of appendix A seems as if there are two separate # corrections, the first correction is used to correct the beam/GHI models, # and the second correction is used to correct the revised turibidity # factor. In my estimation, there is no need to correct the turbidity # factor used in the beam/GHI models. # Create the corrected TL for TL < 2 # TLcorr = TL; # TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5); # This equation is found in Solar Energy 73, pg 311. # Full ref: Perez et. al., Vol. 73, pp. 307-317 (2002). # It is slightly different than the equation given in Solar Energy 73, pg 156. # We used the equation from pg 311 because of the existence of known typos # in the pg 156 publication (notably the fh2-(TL-1) should be fh2 * (TL-1)). cos_zenith = tools.cosd(ApparentZenith) clearsky_GHI = (cg1 * I0 * cos_zenith * np.exp(-cg2 * AMabsolute * (fh1 + fh2 * (TL - 1))) * np.exp(0.01 * AMabsolute**1.8)) clearsky_GHI[clearsky_GHI < 0] = 0 # BncI == "normal beam clear sky radiation" b = 0.664 + 0.163 / fh1 BncI = b * I0 * np.exp(-0.09 * AMabsolute * (TL - 1)) logger.debug('b=%s', b) # "empirical correction" SE 73, 157 & SE 73, 312. BncI_2 = (clearsky_GHI * (1 - (0.1 - 0.2 * np.exp(-TL)) / (0.1 + 0.882 / fh1)) / cos_zenith) clearsky_DNI = np.minimum(BncI, BncI_2) clearsky_DHI = clearsky_GHI - clearsky_DNI * cos_zenith df_out = pd.DataFrame({ 'ghi': clearsky_GHI, 'dni': clearsky_DNI, 'dhi': clearsky_DHI }) df_out.fillna(0, inplace=True) return df_out
rad_meteo = pd.read_csv(csvFilePath, sep=';', decimal = '.', parse_dates = [0], date_parser=dateparser) dateTimeRESET = pd.Series(reset(rad_meteo.iloc[:,0])).dt.round("H") #remove to have interger hour correction of datetime of winet rad_meteo['Data'] = dateTimeRESET #cambio vettore index data con data corretta RadDT = rad_meteo.set_index('Data') RadDT = pd.DataFrame(RadDT) RadDT.plot() RadDtzT = RadDT.tz_localize('CET') ## Calcola RADIAZIONE da PVLIB # pvlib.irradiance.poa_horizontal_ratio(84, 90, 15, 0) # pvlib.irradiance.isotropic(84, 300) lat = 44.549192 long = 11.416346 tus = Location(lat, long, 'Europe/Rome', 32, 'Cadriano') times = pd.date_range(start='2020-06-01', end='2020-10-20', freq='60min', tz=tus.tz) cs = tus.get_clearsky(times) # RadiationCalculated solpos = solarposition.get_solarposition(times, lat, long)#sun elevation frames = pd.concat([cs, solpos],axis = 1)#combine frames['dhi/ghiRatio'] = frames['dhi']/frames['ghi'] #calculate theoretical ghi/dhi cs[['dni', 'dhi']].plot(); plt.ylabel('Irradiance $W/m^2$'); plt.title('sun radiation parameters'); frames.plot(); plt.ylabel('degree'); plt.title('solpos'); #FACCIO MERGE dei dati rilevati dalla centralina winet e dei dati simulati con il MODELLO pvlib ##merge Radiazione From Winet with CalRadiation mergeRadiation = pd.merge(frames, RadDtzT, how='left', left_index=True, right_index=True) mergeRadiation[['par_wm2', 'ghi']].plot(); mergeRadiation['par_wm2_dhi'] = mergeRadiation['par_wm2']*mergeRadiation['dhi/ghiRatio'] # stimo la radiazione diffusa dal rapporto tra rad glo/rad diff in giornata senza nuvole da modello
# towards the sun as much as possible in order to maximize the cross section # presented towards incoming beam irradiance. from pvlib import solarposition, tracking import pandas as pd import matplotlib.pyplot as plt tz = 'US/Eastern' lat, lon = 40, -80 times = pd.date_range('2019-01-01', '2019-01-02', closed='left', freq='5min', tz=tz) solpos = solarposition.get_solarposition(times, lat, lon) truetracking_angles = tracking.singleaxis( apparent_zenith=solpos['apparent_zenith'], apparent_azimuth=solpos['azimuth'], axis_tilt=0, axis_azimuth=180, max_angle=90, backtrack=False, # for true-tracking gcr=0.5) # irrelevant for true-tracking truetracking_position = truetracking_angles['tracker_theta'].fillna(0) truetracking_position.plot(title='Truetracking Curve') plt.show()
def test_ephemeris_functional(): solarposition.get_solarposition( time=times, location=golden_mst, method='ephemeris')
goes_files = [ GOESFilename.from_path(f) for f in Path("/storage/projects/goes_alg/goes_data/west/CMIP").glob( "*MCMIPC*.nc") ] final_countdown = [] for gfile in goes_files: if gfile.start.hour < 13: continue logging.info("Processing file from %s", gfile.start) with xr.open_dataset(gfile.filename) as goes_ds: tomerge = [] for _, site in site_data.groupby("site"): tomerge.append(process_site(goes_ds, site)) final_countdown.append(xr.merge(tomerge)) output = xr.merge(final_countdown) m = [] for _, site in site_data.groupby("site"): da = (get_solarposition(output.time.data, site.latitude.data, site.longitude.data)[[ "zenith", "azimuth" ]].to_xarray().rename({"index": "time"})) da.coords["site"] = site.site m.append(da) solpos = xr.concat(m, "site").transpose("time", "site") fullout = xr.merge([output, solpos]) fullout.to_netcdf("combined_mcmip.nc")
def get_forecast_generation(self, dev_df, forecast_period=48): req_ts = max(dev_df.index).replace( microsecond=0, second=0, minute=0) + datetime.timedelta(hours=1) dev = list(dev_df.columns)[0] days = pd.date_range(start=req_ts, end=req_ts + datetime.timedelta(hours=forecast_period - 1), freq='1H') self.latest += "Doing forecast for Generation :" + dev + "\n" # Pv Info lat = -2.483535 long = 29.523457 # https://www.daftlogic.com/sandbox-google-maps-find-altitude.htm height = 2139 # typical temperature between 15 and 28 yearly, taking 22 as average typ_temp = 22 # Efficienct effic = 0.155 # Area m2 area = 16.37 # Surface surf_tilt = 15 surf_az = 128 # It might be more like 119.5 # effic-scalar scalar = 0.6 tus = Location(lat, long, "Africa/Kigali", height, 'Kigeme') try: cs = tus.get_clearsky( days, model='ineichen') # ineichen with climatology table by default except ImportError: self.latest += "- Import Error on Tables, using fixed turbidity - not a major problem" cs = tus.get_clearsky(days, model="ineichen", linke_turbidity=3) sun_pos = get_solarposition(cs.index, lat, long, altitude=height, pressure=None, method='nrel_numpy', temperature=typ_temp) # \[I_{tot} = I_{beam} + I_{sky} + I_{ground}\] total_irrad = pvlib.irradiance.get_total_irradiance( surf_tilt, surf_az, sun_pos['zenith'], sun_pos['azimuth'], cs['dni'], cs['ghi'], cs['dhi'], DNI_ET=None, AM=None, albedo=0.13, surface_type="grass", model='isotropic', model_perez='allsitescomposite1990') poa = total_irrad['poa_global'] * effic * area * scalar poa = poa.to_frame() poa['timestamp'] = poa.index poa = poa.rename(columns={'poa_global': dev}) return poa
def ineichen( time, location, linke_turbidity=None, solarposition_method="pyephem", zenith_data=None, airmass_model="young1994", airmass_data=None, interp_turbidity=True, ): """ Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model Implements the Ineichen and Perez clear sky model for global horizontal irradiance (GHI), direct normal irradiance (DNI), and calculates the clear-sky diffuse horizontal (DHI) component as the difference between GHI and DNI*cos(zenith) as presented in [1, 2]. A report on clear sky models found the Ineichen/Perez model to have excellent performance with a minimal input data set [3]. Default values for montly Linke turbidity provided by SoDa [4, 5]. Parameters ----------- time : pandas.DatetimeIndex location : pvlib.Location linke_turbidity : None or float If None, uses ``LinkeTurbidities.mat`` lookup table. solarposition_method : string Sets the solar position algorithm. See solarposition.get_solarposition() zenith_data : None or pandas.Series If None, ephemeris data will be calculated using ``solarposition_method``. airmass_model : string See pvlib.airmass.relativeairmass(). airmass_data : None or pandas.Series If None, absolute air mass data will be calculated using ``airmass_model`` and location.alitude. interp_turbidity : bool If ``True``, interpolates the monthly Linke turbidity values found in ``LinkeTurbidities.mat`` to daily values. Returns -------- DataFrame with the following columns: ``GHI, DNI, DHI``. Notes ----- If you are using this function in a loop, it may be faster to load LinkeTurbidities.mat outside of the loop and feed it in as a variable, rather than having the function open the file each time it is called. References ---------- [1] P. Ineichen and R. Perez, "A New airmass independent formulation for the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157, 2002. [2] R. Perez et. al., "A New Operational Model for Satellite-Derived Irradiances: Description and Validation", Solar Energy, vol 73, pp. 307-317, 2002. [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear Sky Models: Implementation and Analysis", Sandia National Laboratories, SAND2012-2389, 2012. [4] http://www.soda-is.com/eng/services/climat_free_eng.php#c5 (obtained July 17, 2012). [5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc. ISES Solar World Congress, June 2003. Goteborg, Sweden. """ # Initial implementation of this algorithm by Matthew Reno. # Ported to python by Rob Andrews # Added functionality by Will Holmgren I0 = irradiance.extraradiation(time.dayofyear) if zenith_data is None: ephem_data = solarposition.get_solarposition(time, location, method=solarposition_method) time = ephem_data.index # fixes issue with time possibly not being tz-aware try: ApparentZenith = ephem_data["apparent_zenith"] except KeyError: ApparentZenith = ephem_data["zenith"] logger.warning("could not find apparent_zenith. using zenith") else: ApparentZenith = zenith_data # ApparentZenith[ApparentZenith >= 90] = 90 # can cause problems in edge cases if linke_turbidity is None: # The .mat file 'LinkeTurbidities.mat' contains a single 2160 x 4320 x 12 # matrix of type uint8 called 'LinkeTurbidity'. The rows represent global # latitudes from 90 to -90 degrees; the columns represent global longitudes # from -180 to 180; and the depth (third dimension) represents months of # the year from January (1) to December (12). To determine the Linke # turbidity for a position on the Earth's surface for a given month do the # following: LT = LinkeTurbidity(LatitudeIndex, LongitudeIndex, month). # Note that the numbers within the matrix are 20 * Linke Turbidity, # so divide the number from the file by 20 to get the # turbidity. try: import scipy.io except ImportError: raise ImportError( "The Linke turbidity lookup table requires scipy. " + "You can still use clearsky.ineichen if you " + "supply your own turbidities." ) # consider putting this code at module level this_path = os.path.dirname(os.path.abspath(__file__)) logger.debug("this_path={}".format(this_path)) mat = scipy.io.loadmat(os.path.join(this_path, "data", "LinkeTurbidities.mat")) linke_turbidity = mat["LinkeTurbidity"] LatitudeIndex = np.round_(_linearly_scale(location.latitude, 90, -90, 1, 2160)) LongitudeIndex = np.round_(_linearly_scale(location.longitude, -180, 180, 1, 4320)) g = linke_turbidity[LatitudeIndex][LongitudeIndex] if interp_turbidity: logger.info("interpolating turbidity to the day") g2 = np.concatenate([[g[-1]], g, [g[0]]]) # wrap ends around days = np.linspace(-15, 380, num=14) # map day of year onto month (approximate) LT = pd.Series(np.interp(time.dayofyear, days, g2), index=time) else: logger.info("using monthly turbidity") ApplyMonth = lambda x: g[x[0] - 1] LT = pd.DataFrame(time.month, index=time) LT = LT.apply(ApplyMonth, axis=1) TL = LT / 20.0 logger.info("using TL=\n{}".format(TL)) else: TL = linke_turbidity # Get the absolute airmass assuming standard local pressure (per # alt2pres) using Kasten and Young's 1989 formula for airmass. if airmass_data is None: AMabsolute = atmosphere.absoluteairmass( AMrelative=atmosphere.relativeairmass(ApparentZenith, airmass_model), pressure=atmosphere.alt2pres(location.altitude), ) else: AMabsolute = airmass_data fh1 = np.exp(-location.altitude / 8000.0) fh2 = np.exp(-location.altitude / 1250.0) cg1 = 5.09e-05 * location.altitude + 0.868 cg2 = 3.92e-05 * location.altitude + 0.0387 logger.debug("fh1={}, fh2={}, cg1={}, cg2={}".format(fh1, fh2, cg1, cg2)) # Dan's note on the TL correction: By my reading of the publication on # pages 151-157, Ineichen and Perez introduce (among other things) three # things. 1) Beam model in eqn. 8, 2) new turbidity factor in eqn 9 and # appendix A, and 3) Global horizontal model in eqn. 11. They do NOT appear # to use the new turbidity factor (item 2 above) in either the beam or GHI # models. The phrasing of appendix A seems as if there are two separate # corrections, the first correction is used to correct the beam/GHI models, # and the second correction is used to correct the revised turibidity # factor. In my estimation, there is no need to correct the turbidity # factor used in the beam/GHI models. # Create the corrected TL for TL < 2 # TLcorr = TL; # TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5); # This equation is found in Solar Energy 73, pg 311. # Full ref: Perez et. al., Vol. 73, pp. 307-317 (2002). # It is slightly different than the equation given in Solar Energy 73, pg 156. # We used the equation from pg 311 because of the existence of known typos # in the pg 156 publication (notably the fh2-(TL-1) should be fh2 * (TL-1)). cos_zenith = tools.cosd(ApparentZenith) clearsky_GHI = ( cg1 * I0 * cos_zenith * np.exp(-cg2 * AMabsolute * (fh1 + fh2 * (TL - 1))) * np.exp(0.01 * AMabsolute ** 1.8) ) clearsky_GHI[clearsky_GHI < 0] = 0 # BncI == "normal beam clear sky radiation" b = 0.664 + 0.163 / fh1 BncI = b * I0 * np.exp(-0.09 * AMabsolute * (TL - 1)) logger.debug("b={}".format(b)) # "empirical correction" SE 73, 157 & SE 73, 312. BncI_2 = clearsky_GHI * (1 - (0.1 - 0.2 * np.exp(-TL)) / (0.1 + 0.882 / fh1)) / cos_zenith # return BncI, BncI_2 clearsky_DNI = np.minimum(BncI, BncI_2) # Will H: use np.minimum explicitly clearsky_DHI = clearsky_GHI - clearsky_DNI * cos_zenith df_out = pd.DataFrame({"GHI": clearsky_GHI, "DNI": clearsky_DNI, "DHI": clearsky_DHI}) df_out.fillna(0, inplace=True) # df_out['BncI'] = BncI # df_out['BncI_2'] = BncI return df_out
import numpy as np import pandas as pd import pytest from numpy.testing import assert_allclose from pvlib.location import Location from pvlib import solarposition from pvlib import atmosphere latitude, longitude, tz, altitude = 32.2, -111, 'US/Arizona', 700 times = pd.date_range(start='20140626', end='20140626', freq='6h', tz=tz) ephem_data = solarposition.get_solarposition(times, latitude, longitude) # need to add physical tests instead of just functional tests def test_pres2alt(): atmosphere.pres2alt(100000) def test_alt2press(): atmosphere.pres2alt(1000) @pytest.mark.parametrize("model", ['simple', 'kasten1966', 'youngirvine1967', 'kastenyoung1989', 'gueymard1993', 'young1994', 'pickering2002'])
def ineichen(time, location, linke_turbidity=None, solarposition_method='pyephem', zenith_data=None, airmass_model='young1994', airmass_data=None, interp_turbidity=True): ''' Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model Implements the Ineichen and Perez clear sky model for global horizontal irradiance (GHI), direct normal irradiance (DNI), and calculates the clear-sky diffuse horizontal (DHI) component as the difference between GHI and DNI*cos(zenith) as presented in [1, 2]. A report on clear sky models found the Ineichen/Perez model to have excellent performance with a minimal input data set [3]. Default values for montly Linke turbidity provided by SoDa [4, 5]. Parameters ----------- time : pandas.DatetimeIndex location : pvlib.Location linke_turbidity : None or float If None, uses ``LinkeTurbidities.mat`` lookup table. solarposition_method : string Sets the solar position algorithm. See solarposition.get_solarposition() zenith_data : None or pandas.Series If None, ephemeris data will be calculated using ``solarposition_method``. airmass_model : string See pvlib.airmass.relativeairmass(). airmass_data : None or pandas.Series If None, absolute air mass data will be calculated using ``airmass_model`` and location.alitude. interp_turbidity : bool If ``True``, interpolates the monthly Linke turbidity values found in ``LinkeTurbidities.mat`` to daily values. Returns -------- DataFrame with the following columns: ``GHI, DNI, DHI``. Notes ----- If you are using this function in a loop, it may be faster to load LinkeTurbidities.mat outside of the loop and feed it in as a variable, rather than having the function open the file each time it is called. References ---------- [1] P. Ineichen and R. Perez, "A New airmass independent formulation for the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157, 2002. [2] R. Perez et. al., "A New Operational Model for Satellite-Derived Irradiances: Description and Validation", Solar Energy, vol 73, pp. 307-317, 2002. [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear Sky Models: Implementation and Analysis", Sandia National Laboratories, SAND2012-2389, 2012. [4] http://www.soda-is.com/eng/services/climat_free_eng.php#c5 (obtained July 17, 2012). [5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc. ISES Solar World Congress, June 2003. Goteborg, Sweden. ''' # Initial implementation of this algorithm by Matthew Reno. # Ported to python by Rob Andrews # Added functionality by Will Holmgren I0 = irradiance.extraradiation(time.dayofyear) if zenith_data is None: ephem_data = solarposition.get_solarposition( time, location, method=solarposition_method) time = ephem_data.index # fixes issue with time possibly not being tz-aware try: ApparentZenith = ephem_data['apparent_zenith'] except KeyError: ApparentZenith = ephem_data['zenith'] logger.warning('could not find apparent_zenith. using zenith') else: ApparentZenith = zenith_data #ApparentZenith[ApparentZenith >= 90] = 90 # can cause problems in edge cases if linke_turbidity is None: # The .mat file 'LinkeTurbidities.mat' contains a single 2160 x 4320 x 12 # matrix of type uint8 called 'LinkeTurbidity'. The rows represent global # latitudes from 90 to -90 degrees; the columns represent global longitudes # from -180 to 180; and the depth (third dimension) represents months of # the year from January (1) to December (12). To determine the Linke # turbidity for a position on the Earth's surface for a given month do the # following: LT = LinkeTurbidity(LatitudeIndex, LongitudeIndex, month). # Note that the numbers within the matrix are 20 * Linke Turbidity, # so divide the number from the file by 20 to get the # turbidity. try: import scipy.io except ImportError: raise ImportError( 'The Linke turbidity lookup table requires scipy. ' + 'You can still use clearsky.ineichen if you ' + 'supply your own turbidities.') # consider putting this code at module level this_path = os.path.dirname(os.path.abspath(__file__)) logger.debug('this_path={}'.format(this_path)) mat = scipy.io.loadmat( os.path.join(this_path, 'data', 'LinkeTurbidities.mat')) linke_turbidity = mat['LinkeTurbidity'] LatitudeIndex = np.round_( _linearly_scale(location.latitude, 90, -90, 1, 2160)) LongitudeIndex = np.round_( _linearly_scale(location.longitude, -180, 180, 1, 4320)) g = linke_turbidity[LatitudeIndex][LongitudeIndex] if interp_turbidity: logger.info('interpolating turbidity to the day') g2 = np.concatenate([[g[-1]], g, [g[0]]]) # wrap ends around days = np.linspace( -15, 380, num=14) # map day of year onto month (approximate) LT = pd.Series(np.interp(time.dayofyear, days, g2), index=time) else: logger.info('using monthly turbidity') ApplyMonth = lambda x: g[x[0] - 1] LT = pd.DataFrame(time.month, index=time) LT = LT.apply(ApplyMonth, axis=1) TL = LT / 20. logger.info('using TL=\n{}'.format(TL)) else: TL = linke_turbidity # Get the absolute airmass assuming standard local pressure (per # alt2pres) using Kasten and Young's 1989 formula for airmass. if airmass_data is None: AMabsolute = atmosphere.absoluteairmass( AMrelative=atmosphere.relativeairmass(ApparentZenith, airmass_model), pressure=atmosphere.alt2pres(location.altitude)) else: AMabsolute = airmass_data fh1 = np.exp(-location.altitude / 8000.) fh2 = np.exp(-location.altitude / 1250.) cg1 = 5.09e-05 * location.altitude + 0.868 cg2 = 3.92e-05 * location.altitude + 0.0387 logger.debug('fh1={}, fh2={}, cg1={}, cg2={}'.format(fh1, fh2, cg1, cg2)) # Dan's note on the TL correction: By my reading of the publication on # pages 151-157, Ineichen and Perez introduce (among other things) three # things. 1) Beam model in eqn. 8, 2) new turbidity factor in eqn 9 and # appendix A, and 3) Global horizontal model in eqn. 11. They do NOT appear # to use the new turbidity factor (item 2 above) in either the beam or GHI # models. The phrasing of appendix A seems as if there are two separate # corrections, the first correction is used to correct the beam/GHI models, # and the second correction is used to correct the revised turibidity # factor. In my estimation, there is no need to correct the turbidity # factor used in the beam/GHI models. # Create the corrected TL for TL < 2 # TLcorr = TL; # TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5); # This equation is found in Solar Energy 73, pg 311. # Full ref: Perez et. al., Vol. 73, pp. 307-317 (2002). # It is slightly different than the equation given in Solar Energy 73, pg 156. # We used the equation from pg 311 because of the existence of known typos # in the pg 156 publication (notably the fh2-(TL-1) should be fh2 * (TL-1)). cos_zenith = tools.cosd(ApparentZenith) clearsky_GHI = cg1 * I0 * cos_zenith * np.exp( -cg2 * AMabsolute * (fh1 + fh2 * (TL - 1))) * np.exp(0.01 * AMabsolute**1.8) clearsky_GHI[clearsky_GHI < 0] = 0 # BncI == "normal beam clear sky radiation" b = 0.664 + 0.163 / fh1 BncI = b * I0 * np.exp(-0.09 * AMabsolute * (TL - 1)) logger.debug('b={}'.format(b)) # "empirical correction" SE 73, 157 & SE 73, 312. BncI_2 = clearsky_GHI * (1 - (0.1 - 0.2 * np.exp(-TL)) / (0.1 + 0.882 / fh1)) / cos_zenith #return BncI, BncI_2 clearsky_DNI = np.minimum(BncI, BncI_2) # Will H: use np.minimum explicitly clearsky_DHI = clearsky_GHI - clearsky_DNI * cos_zenith df_out = pd.DataFrame({ 'GHI': clearsky_GHI, 'DNI': clearsky_DNI, 'DHI': clearsky_DHI }) df_out.fillna(0, inplace=True) #df_out['BncI'] = BncI #df_out['BncI_2'] = BncI return df_out
from nose.tools import assert_almost_equals from pvlib.location import Location from pvlib import solarposition from pvlib import atmosphere # setup times and location to be tested. times = pd.date_range(start=datetime.datetime(2014, 6, 24), end=datetime.datetime(2014, 6, 26), freq='1Min') tus = Location(32.2, -111, 'US/Arizona', 700) times_localized = times.tz_localize(tus.tz) ephem_data = solarposition.get_solarposition(times_localized, tus.latitude, tus.longitude) # need to add physical tests instead of just functional tests def test_pres2alt(): atmosphere.pres2alt(100000) def test_alt2press(): atmosphere.pres2alt(1000) # two functions combined will generate unique unit tests for each model def test_airmasses(): models = [
def basic_chain(times, latitude, longitude, module_parameters, inverter_parameters, irradiance=None, weather=None, surface_tilt=None, surface_azimuth=None, orientation_strategy=None, transposition_model='haydavies', solar_position_method='nrel_numpy', airmass_model='kastenyoung1989', altitude=None, pressure=None, **kwargs): """ An experimental function that computes all of the modeling steps necessary for calculating power or energy for a PV system at a given location. Parameters ---------- times : DatetimeIndex Times at which to evaluate the model. latitude : float. Positive is north of the equator. Use decimal degrees notation. longitude : float. Positive is east of the prime meridian. Use decimal degrees notation. module_parameters : None, dict or Series Module parameters as defined by the SAPM. inverter_parameters : None, dict or Series Inverter parameters as defined by the CEC. irradiance : None or DataFrame If None, calculates clear sky data. Columns must be 'dni', 'ghi', 'dhi'. weather : None or DataFrame If None, assumes air temperature is 20 C and wind speed is 0 m/s. Columns must be 'wind_speed', 'temp_air'. surface_tilt : float or Series Surface tilt angles in decimal degrees. The tilt angle is defined as degrees from horizontal (e.g. surface facing up = 0, surface facing horizon = 90) surface_azimuth : float or Series Surface azimuth angles in decimal degrees. The azimuth convention is defined as degrees east of north (North=0, South=180, East=90, West=270). orientation_strategy : None or str The strategy for aligning the modules. If not None, sets the ``surface_azimuth`` and ``surface_tilt`` properties of the ``system``. transposition_model : str Passed to system.get_irradiance. solar_position_method : str Passed to location.get_solarposition. airmass_model : str Passed to location.get_airmass. altitude : None or float If None, computed from pressure. Assumed to be 0 m if pressure is also None. pressure : None or float If None, computed from altitude. Assumed to be 101325 Pa if altitude is also None. **kwargs Arbitrary keyword arguments. See code for details. Returns ------- output : (dc, ac) Tuple of DC power (with SAPM parameters) (DataFrame) and AC power (Series). """ # use surface_tilt and surface_azimuth if provided, # otherwise set them using the orientation_strategy if surface_tilt is not None and surface_azimuth is not None: pass elif orientation_strategy is not None: surface_tilt, surface_azimuth = \ get_orientation(orientation_strategy, latitude=latitude) else: raise ValueError('orientation_strategy or surface_tilt and ' + 'surface_azimuth must be provided') times = times if altitude is None and pressure is None: altitude = 0. pressure = 101325. elif altitude is None: altitude = atmosphere.pres2alt(pressure) elif pressure is None: pressure = atmosphere.alt2pres(altitude) solar_position = solarposition.get_solarposition(times, latitude, longitude, altitude=altitude, pressure=pressure, **kwargs) # possible error with using apparent zenith with some models airmass = atmosphere.relativeairmass(solar_position['apparent_zenith'], model=airmass_model) airmass = atmosphere.absoluteairmass(airmass, pressure) dni_extra = pvlib.irradiance.extraradiation(solar_position.index) dni_extra = pd.Series(dni_extra, index=solar_position.index) aoi = pvlib.irradiance.aoi(surface_tilt, surface_azimuth, solar_position['apparent_zenith'], solar_position['azimuth']) if irradiance is None: irradiance = clearsky.ineichen( solar_position.index, latitude, longitude, zenith_data=solar_position['apparent_zenith'], airmass_data=airmass, altitude=altitude) total_irrad = pvlib.irradiance.total_irrad( surface_tilt, surface_azimuth, solar_position['apparent_zenith'], solar_position['azimuth'], irradiance['dni'], irradiance['ghi'], irradiance['dhi'], model=transposition_model, dni_extra=dni_extra) if weather is None: weather = {'wind_speed': 0, 'temp_air': 20} temps = pvsystem.sapm_celltemp(total_irrad['poa_global'], weather['wind_speed'], weather['temp_air']) dc = pvsystem.sapm(module_parameters, total_irrad['poa_direct'], total_irrad['poa_diffuse'], temps['temp_cell'], airmass, aoi) ac = pvsystem.snlinverter(inverter_parameters, dc['v_mp'], dc['p_mp']) return dc, ac
def irrad_tilt(time, lat, long, panel_elev, panel_az, irrad, alb, alt=None, pressure=None, temp=None): ''' Function to calculate the solar irradiance on arbitrary tited surface/ PV panel and a arbitrary location at any given time. In order to obtain the solar position [1] is used and angle calculations are done according to [2] Parameters ---------- time : Panda DateTimeIndex Must be date from Panda DateTimeIndex --> date and time to be assessed. lat : float Latitude of location in decimal; South of equator is negative. long : float Longitude of location in decimal; West of Greenwhich is negative. panel_elev : float Elevation angle of the PV panel surface (0-90, while 0 is horizontal) in °. panel_az : float Azimuth angle of the PV panel surface (0-359, while 0 is north, 90 is east, 180 is south and 270 is west) in °. irrad : panda Dataframe Dataframe with panda DateTimeIndex, global horizontal irradiance ('G(h)'), direct normal irradiance ('Gb(n)') and diffuse horizontal irradiance ('Gd(h)') --> it is important that the collumns are named respectively. alb : float Albedo; Surface dependent reflection coefficient --> consult https://de.wikipedia.org/wiki/Albedo for values. alt : float or None, optional Altitude of location above sea level m. The default is None. pressure : float or None, optional In Pa The default is None. temp : float or None, optional Average temperature in °C of location. The default is None. Returns ------- irrad_t : float Irradiance on tilted surface for specific location and time. References ------- [1] William F. Holmgren, Clifford W. Hansen, and Mark A. Mikofski. “pvlib python: a python package for modeling solar energy systems.” Journal of Open Source Software, 3(29), 884, (2018). https://doi.org/10.21105/joss.00884 [2] Eiker Ursula. "Solare Technologien für Gebäude" Springer, (2012). https://doi.org/10.1007/978-3-8348-8237-0 ''' solar_pos = solarposition.get_solarposition(time, lat, long, alt, pressure, 'nrel_numpy', temp) sol_az = solar_pos.at[time, 'azimuth'] sol_zen = solar_pos.at[time, 'zenith'] cos_tilt = np.cos( np.radians(sol_zen) * np.cos(np.radians(panel_elev)) + np.sin(np.radians(sol_zen)) * np.sin(np.radians(panel_elev)) * np.cos(np.radians(sol_az - panel_az))) irrad_dir_t = irrad.at[time, "Gb(n)"] * np.maximum( 0, cos_tilt / np.sin(np.radians(90 - sol_zen))) irrad_diff_t = irrad.at[time, "Gd(h)"] * ( 1 + np.cos(np.radians(panel_elev))) * 0.5 irrad_ref_t = irrad.at[time, "G(h)"] * alb * ( 1 - np.cos(np.radians(panel_elev))) * 0.5 irrad_t = irrad_dir_t + irrad_diff_t + irrad_ref_t return irrad_t
from nose.tools import raises from numpy.testing import assert_almost_equal, assert_allclose from pandas.util.testing import assert_frame_equal, assert_series_equal from pvlib.location import Location from pvlib import clearsky from pvlib import solarposition from . import requires_scipy # setup times and location to be tested. tus = Location(32.2, -111, 'US/Arizona', 700) times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h') times_localized = times.tz_localize(tus.tz) ephem_data = solarposition.get_solarposition(times_localized, tus.latitude, tus.longitude) @requires_scipy def test_ineichen_required(): # the clearsky function should call lookup_linke_turbidity by default expected = pd.DataFrame( np.array([[ 0. , 0. , 0. ], [ 0. , 0. , 0. ], [ 51.47811191, 265.33462162, 84.48262202], [ 105.008507 , 832.29100407, 682.67761951], [ 121.97988054, 901.31821834, 1008.02102657], [ 112.57957512, 867.76297247, 824.61702926], [ 76.69672675, 588.8462898 , 254.5808329 ], [ 0. , 0. , 0. ], [ 0. , 0. , 0. ]]), columns=['dhi', 'dni', 'ghi'],
from pvlib.location import Location from pvlib import clearsky from pvlib import solarposition from pvlib import irradiance from pvlib import atmosphere from conftest import requires_ephem, requires_numba, needs_numpy_1_10 # setup times and location to be tested. tus = Location(32.2, -111, 'US/Arizona', 700) # must include night values times = pd.date_range(start='20140624', freq='6H', periods=4, tz=tus.tz) ephem_data = solarposition.get_solarposition(times, tus.latitude, tus.longitude, method='nrel_numpy') irrad_data = tus.get_clearsky(times, model='ineichen', linke_turbidity=3) dni_et = irradiance.extraradiation(times.dayofyear) ghi = irrad_data['ghi'] # setup for et rad test. put it here for readability timestamp = pd.Timestamp('20161026') dt_index = pd.DatetimeIndex([timestamp]) doy = timestamp.dayofyear dt_date = timestamp.date() dt_datetime = datetime.datetime.combine(dt_date, datetime.time(0)) dt_np64 = np.datetime64(dt_datetime)
import pandas as pd from nose.tools import raises from numpy.testing import assert_almost_equal from pandas.util.testing import assert_frame_equal, assert_series_equal from pvlib.location import Location from pvlib import clearsky from pvlib import solarposition # setup times and location to be tested. tus = Location(32.2, -111, 'US/Arizona', 700) times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='3h') times_localized = times.tz_localize(tus.tz) ephem_data = solarposition.get_solarposition(times, tus) def test_ineichen_required(): # the clearsky function should call lookup_linke_turbidity by default # will fail without scipy expected = pd.DataFrame( np.array([[0., 0., 0.], [0., 0., 0.], [40.53660309, 302.47614235, 78.1470311], [98.88372629, 865.98938602, 699.93403875], [122.57870881, 931.83716051, 1038.62116584], [109.30270612, 899.88002304, 847.68806472], [64.25699595, 629.91187925, 254.53048144], [0., 0., 0.], [0., 0., 0.]]), columns=['dhi', 'dni', 'ghi'], index=times_localized)
import pandas as pd from nose.tools import assert_equals, assert_almost_equals from pvlib import tmy from pvlib import pvsystem from pvlib import clearsky from pvlib import irradiance from pvlib import atmosphere from pvlib import solarposition from pvlib.location import Location tus = Location(32.2, -111, 'US/Arizona', 700, 'Tucson') times = pd.date_range(start=datetime.datetime(2014,1,1), end=datetime.datetime(2014,1,2), freq='1Min') ephem_data = solarposition.get_solarposition(times, tus, method='pyephem') irrad_data = clearsky.ineichen(times, tus, linke_turbidity=3, solarposition_method='pyephem') aoi = irradiance.aoi(0, 0, ephem_data['apparent_zenith'], ephem_data['apparent_azimuth']) am = atmosphere.relativeairmass(ephem_data.apparent_zenith) meta = {'latitude': 37.8, 'longitude': -122.3, 'altitude': 10, 'Name': 'Oakland', 'State': 'CA', 'TZ': -8} pvlib_abspath = os.path.dirname(os.path.abspath(inspect.getfile(tmy)))
def sun_position(dates=None, daydate=_day, latitude=_latitude, longitude=_longitude, altitude=_altitude, timezone=_timezone, method='pvlib', filter_night=True): """ Sun position Args: dates: a pandas.DatetimeIndex specifying the dates at which sun position is required.If None, daydate is used and one position per hour is generated daydate: (str) yyyy-mm-dd (not used if dates is not None). latitude: float longitude: float altitude: (float) altitude in m timezone: a string identifying the timezone to be associated to dates if dates is not already localised. This args is not used if dates are already localised method: (string) the method to use. default uses pvlib. other methods are 'ephem' (using ephem package) or 'astk' (using this module) filter_night (bool) : Should positions of sun during night be filtered ? Returns: a pandas dataframe with sun position at requested dates indexed by localised dates. Sun azimtuth is given from North, positive clockwise. """ if dates is None: dates = pandas.date_range(daydate, periods=24, freq='H') if dates.tz is None: times = dates.tz_localize(timezone) else: times = dates sunpos = None if method == 'pvlib': df = get_solarposition(times, latitude, longitude, altitude) sunpos = pandas.DataFrame( {'elevation': df['apparent_elevation'], 'azimuth': df['azimuth'], 'zenith': df['apparent_zenith']}, index=df.index) elif method == 'ephem': d = times.tz_convert('UTC') hUTC = d.hour + d.minute / 60. dayofyear = d.dayofyear year = d.year fun = numpy.frompyfunc(ephem_sun_position, 5, 2) alt, az = fun(hUTC, dayofyear, year, latitude, longitude) sunpos = pandas.DataFrame( {'elevation': alt.astype(float), 'azimuth': az.astype(float)}, index=times) sunpos['zenith'] = 90 - sunpos['elevation'] elif method == 'astk': d = times.tz_convert('UTC') hUTC = d.hour + d.minute / 60. dayofyear = d.dayofyear year = d.year el = sun_elevation(hUTC, dayofyear, year, latitude, longitude) az = sun_azimuth(hUTC, dayofyear, year, latitude, longitude) sunpos = pandas.DataFrame( {'elevation': el, 'zenith': 90 - el, 'azimuth': az}, index=times) else: raise ValueError( 'unknown method: ' + method + 'available methods are : pvlib, ephem and astk') if filter_night and sunpos is not None: sunpos = sunpos.loc[sunpos['elevation'] > 0, :] return sunpos
from nose.tools import raises from numpy.testing import assert_almost_equal from pvlib.location import Location from pvlib import clearsky from pvlib import solarposition # setup times and location to be tested. times = pd.date_range(start=datetime.datetime(2014, 6, 24), end=datetime.datetime(2014, 6, 26), freq="1Min") tus = Location(32.2, -111, "US/Arizona", 700) times_localized = times.tz_localize(tus.tz) ephem_data = solarposition.get_solarposition(times, tus) # test the ineichen clear sky model implementation in a few ways def test_ineichen_required(): # the clearsky function should lookup the linke turbidity on its own # will fail without scipy clearsky.ineichen(times, tus) def test_ineichen_supply_linke(): clearsky.ineichen(times, tus, linke_turbidity=3)
def ineichen(time, latitude, longitude, altitude=0, linke_turbidity=None, solarposition_method='nrel_numpy', zenith_data=None, airmass_model='young1994', airmass_data=None, interp_turbidity=True): ''' Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model Implements the Ineichen and Perez clear sky model for global horizontal irradiance (GHI), direct normal irradiance (DNI), and calculates the clear-sky diffuse horizontal (DHI) component as the difference between GHI and DNI*cos(zenith) as presented in [1, 2]. A report on clear sky models found the Ineichen/Perez model to have excellent performance with a minimal input data set [3]. Default values for montly Linke turbidity provided by SoDa [4, 5]. Parameters ----------- time : pandas.DatetimeIndex latitude : float longitude : float altitude : float linke_turbidity : None or float If None, uses ``LinkeTurbidities.mat`` lookup table. solarposition_method : string Sets the solar position algorithm. See solarposition.get_solarposition() zenith_data : None or Series If None, ephemeris data will be calculated using ``solarposition_method``. airmass_model : string See pvlib.airmass.relativeairmass(). airmass_data : None or Series If None, absolute air mass data will be calculated using ``airmass_model`` and location.alitude. interp_turbidity : bool If ``True``, interpolates the monthly Linke turbidity values found in ``LinkeTurbidities.mat`` to daily values. Returns -------- DataFrame with the following columns: ``ghi, dni, dhi``. Notes ----- If you are using this function in a loop, it may be faster to load LinkeTurbidities.mat outside of the loop and feed it in as a keyword argument, rather than having the function open and process the file each time it is called. References ---------- [1] P. Ineichen and R. Perez, "A New airmass independent formulation for the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157, 2002. [2] R. Perez et. al., "A New Operational Model for Satellite-Derived Irradiances: Description and Validation", Solar Energy, vol 73, pp. 307-317, 2002. [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear Sky Models: Implementation and Analysis", Sandia National Laboratories, SAND2012-2389, 2012. [4] http://www.soda-is.com/eng/services/climat_free_eng.php#c5 (obtained July 17, 2012). [5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc. ISES Solar World Congress, June 2003. Goteborg, Sweden. ''' # Initial implementation of this algorithm by Matthew Reno. # Ported to python by Rob Andrews # Added functionality by Will Holmgren (@wholmgren) I0 = irradiance.extraradiation(time.dayofyear) if zenith_data is None: ephem_data = solarposition.get_solarposition(time, latitude=latitude, longitude=longitude, altitude=altitude, method=solarposition_method) time = ephem_data.index # fixes issue with time possibly not being tz-aware try: ApparentZenith = ephem_data['apparent_zenith'] except KeyError: ApparentZenith = ephem_data['zenith'] logger.warning('could not find apparent_zenith. using zenith') else: ApparentZenith = zenith_data #ApparentZenith[ApparentZenith >= 90] = 90 # can cause problems in edge cases if linke_turbidity is None: TL = lookup_linke_turbidity(time, latitude, longitude, interp_turbidity=interp_turbidity) else: TL = linke_turbidity # Get the absolute airmass assuming standard local pressure (per # alt2pres) using Kasten and Young's 1989 formula for airmass. if airmass_data is None: AMabsolute = atmosphere.absoluteairmass(airmass_relative=atmosphere.relativeairmass(ApparentZenith, airmass_model), pressure=atmosphere.alt2pres(altitude)) else: AMabsolute = airmass_data fh1 = np.exp(-altitude/8000.) fh2 = np.exp(-altitude/1250.) cg1 = 5.09e-05 * altitude + 0.868 cg2 = 3.92e-05 * altitude + 0.0387 logger.debug('fh1=%s, fh2=%s, cg1=%s, cg2=%s', fh1, fh2, cg1, cg2) # Dan's note on the TL correction: By my reading of the publication on # pages 151-157, Ineichen and Perez introduce (among other things) three # things. 1) Beam model in eqn. 8, 2) new turbidity factor in eqn 9 and # appendix A, and 3) Global horizontal model in eqn. 11. They do NOT appear # to use the new turbidity factor (item 2 above) in either the beam or GHI # models. The phrasing of appendix A seems as if there are two separate # corrections, the first correction is used to correct the beam/GHI models, # and the second correction is used to correct the revised turibidity # factor. In my estimation, there is no need to correct the turbidity # factor used in the beam/GHI models. # Create the corrected TL for TL < 2 # TLcorr = TL; # TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5); # This equation is found in Solar Energy 73, pg 311. # Full ref: Perez et. al., Vol. 73, pp. 307-317 (2002). # It is slightly different than the equation given in Solar Energy 73, pg 156. # We used the equation from pg 311 because of the existence of known typos # in the pg 156 publication (notably the fh2-(TL-1) should be fh2 * (TL-1)). cos_zenith = tools.cosd(ApparentZenith) clearsky_GHI = ( cg1 * I0 * cos_zenith * np.exp(-cg2*AMabsolute*(fh1 + fh2*(TL - 1))) * np.exp(0.01*AMabsolute**1.8) ) clearsky_GHI[clearsky_GHI < 0] = 0 # BncI == "normal beam clear sky radiation" b = 0.664 + 0.163/fh1 BncI = b * I0 * np.exp( -0.09 * AMabsolute * (TL - 1) ) logger.debug('b=%s', b) # "empirical correction" SE 73, 157 & SE 73, 312. BncI_2 = ( clearsky_GHI * ( 1 - (0.1 - 0.2*np.exp(-TL))/(0.1 + 0.882/fh1) ) / cos_zenith ) clearsky_DNI = np.minimum(BncI, BncI_2) clearsky_DHI = clearsky_GHI - clearsky_DNI*cos_zenith df_out = pd.DataFrame({'ghi':clearsky_GHI, 'dni':clearsky_DNI, 'dhi':clearsky_DHI}) df_out.fillna(0, inplace=True) return df_out