Skip to content

Commit

Permalink
Generating Spectra
Browse files Browse the repository at this point in the history
New method `RadianceObject.generate_spectra`. Generates spectra directly from radiance object and saves the generated files in the "spectra" folder in the radiance scene directory. When passing `scale_albedo_nonspectral_sim` the `radianceObject.metdata.albedo` is automatically updated with the new weighted albedo values so there is no need to re-read weather to continue the simulation. Additionally, a datetime-indexed csv with the weighted albedo is placed in the "spectra" folder.
Spectra generation will now process for the entire length of metdata within the radiance object, so whatever datetime-range is specified in `readweather` will be processed by `generate_spectra`.
  • Loading branch information
mcbrown042 committed Jul 19, 2022
1 parent 14fa70c commit 8b73d11
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 104 deletions.
19 changes: 19 additions & 0 deletions bifacial_radiance/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2749,6 +2749,25 @@ def calculateResults(self, CECMod=None, glassglass=False, bifacialityfactor=None
self.trackerdict = trackerdict

return trackerdict

def generate_spectra(self, ground=None, scale_spectra=False, scale_albedo=False,
scale_albedo_nonspectral_sim=False, scale_upper_bound=2500):

from bifacial_radiance import spectral_utils as su
import os

spectra_path = 'spectra'
if not os.path.exists(spectra_path):
os.mkdir(spectra_path)

spectra = su.generate_spectra(self.metdata, self.path, ground=ground, spectra_folder=spectra_path,
scale_spectra=scale_spectra, scale_albedo=scale_albedo,
scale_albedo_nonspectral_sim=scale_albedo_nonspectral_sim,
scale_upper_bound=scale_upper_bound)

if scale_albedo_nonspectral_sim:
self.metdata.albedo = spectra['weightedALB']
return spectra

# End RadianceObj definition

Expand Down
231 changes: 127 additions & 104 deletions bifacial_radiance/spectral_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections.abc import Iterable
import os
from scipy import integrate
from tqdm import tqdm


class spectral_property(object):
Expand Down Expand Up @@ -150,7 +151,7 @@ def spectral_albedo_smarts(zen, azm, material, min_wavelength=300,
min_wvl=str(min_wavelength),
max_wvl=str(max_wavelength))

return spectral_property(smarts_res['Zonal_ground_reflectance'],
return spectral_property(smarts_res['Local_ground_reflectance'],
smarts_res['Wvlgth'], interpolation='linear')

def spectral_irradiance_smarts(zen, azm, material='LiteSoil', min_wavelength=300,
Expand Down Expand Up @@ -224,24 +225,25 @@ def spectral_albedo_smarts_SRRL(YEAR, MONTH, DAY, HOUR, ZONE,
RHOG=RHOG, material=material,
min_wvl=min_wvl, max_wvl=max_wvl)

return spectral_property(smarts_res['Zonal_ground_reflectance'],
return spectral_property(smarts_res['Local_ground_reflectance'],
smarts_res['Wvlgth'], interpolation='linear')


def generate_spectra(idx, metdata, material=None, spectra_folder=None, scale_spectra=False,
scale_albedo=False, scale_albedo_nonspectral_sim=False):
def generate_spectra(metdata, simulationPath, ground='Gravel', spectra_folder=None, scale_spectra=False,
scale_albedo=False, scale_albedo_nonspectral_sim=False, scale_upper_bound=2500):
"""
generate spectral curve for particular material. Requires pySMARTS
Parameters
----------
idx : int
index of the metdata file to run pySMARTS.
metdata : bifacial_radiance MetObj
DESCRIPTION.
material : string, optional
type of material for spectral simulation. Options include: Grass,
Gravel, etc. The default is None.
simulationPath: bifacial_radiance MetObj
path of simulation directory
ground : string, optional
type of ground material for spectral simulation. Options include:
Grass, Gravel, Snow, Seasonal etc.
The default is 'Gravel'.
spectra_folder : path, optional
location to save spectral data. The default is None.
scale_spectra : bool, optional
Expand All @@ -250,6 +252,9 @@ def generate_spectra(idx, metdata, material=None, spectra_folder=None, scale_spe
DESCRIPTION. The default is False.
scale_albedo_nonspectral_sim : bool, optional
DESCRIPTION. The default is False.
scale_upper_bound: integer, optional
Set an upper bound for the wavelength when taking the mean
or integral of any generated spectra.
Returns
-------
Expand All @@ -261,102 +266,120 @@ def generate_spectra(idx, metdata, material=None, spectra_folder=None, scale_spe
spectral_dhi.data: dataframe with frequency and magnitude data.
"""

if material is None:
material = 'Gravel'

# Extract data from metdata
dni = metdata.dni[idx]
dhi = metdata.dhi[idx]
ghi = metdata.ghi[idx]
try:
alb = metdata.albedo[idx]
except TypeError:
raise Exception("Error - No 'metdata.albedo' value passed.")

solpos = metdata.solpos.iloc[idx]
zen = float(solpos.zenith)
azm = float(solpos.azimuth) - 180
#lat = metdata.latitude
#lon = metdata.longitude
#elev = metdata.elevation / 1000
#t = metdata.datetime[idx]

# Verify sun up
if zen > 90:
print("Sun below horizon. Skipping.")
return None

# Define file suffix
# -- CHANGE --
suffix = f'_{idx:04}.txt'

# Generate/Load dni and dhi
dni_file = os.path.join(spectra_folder, "dni"+suffix)
dhi_file = os.path.join(spectra_folder, "dhi"+suffix)
ghi_file = os.path.join(spectra_folder, "ghi"+suffix)
spectral_dni, spectral_dhi, spectral_ghi = spectral_irradiance_smarts(zen, azm, min_wavelength=300)

# SCALING:
# If specifed, scale the irradiance spectra based on their respective
# measured value.
if scale_spectra:

dni_scale = dni / spectral_dni.data.apply(lambda g: integrate.trapz(spectral_dni.data.value, x=spectral_dni.data.index))
dhi_scale = dhi / spectral_dhi.data.apply(lambda g: integrate.trapz(spectral_dhi.data.value, x=spectral_dhi.data.index))
ghi_scale = ghi / spectral_ghi.data.apply(lambda g: integrate.trapz(spectral_ghi.data.value, x=spectral_ghi.data.index))
# dni_scale = dni / (10*np.sum(spectral_dni[range(280, 4000, 10)]))
# dhi_scale = dhi / (10*np.sum(spectral_dhi[range(280, 4000, 10)]))
# ghi_scale = ghi / (10*np.sum(spectral_ghi[range(280, 2501, 10)]))
spectral_dni.scale_values(dni_scale.value)
spectral_dhi.scale_values(dhi_scale.value)
spectral_ghi.scale_values(ghi_scale.value)

# Write irradiance spectra
#'''
spectral_dni.to_file(dni_file)
spectral_dhi.to_file(dhi_file)
spectral_ghi.to_file(ghi_file)
#'''

# Generate/Load albedo
alb_file = os.path.join(spectra_folder, "alb"+suffix)

if material == 'Seasonal':
MONTH = metdata.datetime[idx].month
if 4 <= MONTH <= 7:
material = 'Grass'
else:
material = 'DryGrass'
spectral_alb = spectral_albedo_smarts(zen, azm, material, min_wavelength=300)

# If specifed, scale the spectral albedo to have a mean value matching the
# measured albedo.
if scale_albedo:
# option A
denom = spectral_alb.data.value * spectral_ghi.data.value
# option B
#denom = spectral_alb.data

# TODO:
# Add test to
if alb > 1 or alb == 0:
print("albedo measured is an incorrect number, not scaling albedo generated")
else:
alb_scale = alb / denom.apply(lambda g: integrate.trapz(denom.values, x=spectral_alb.data.index))
spectral_alb.scale_values(alb_scale.values)
# make the datetime easily readable and indexed
dts = pd.Series(data=metdata.datetime)

# weighted albedo data frame
walb = pd.Series(index=np.array(metdata.datetime),dtype='float64')

# print useful reminders
if scale_albedo_nonspectral_sim:
sim_alb = np.sum(spectral_alb[range(280, 2501, 10)] * spectral_ghi[range(280, 2501, 10)])/np.sum(spectral_ghi[range(280, 2501, 10)])
print(' -= Non-Spectral Simulation =- \n Spectra files will NOT be saved.')
else:
print(' -= Spectral Simulation =- \n Spectra files will be saved.')

for dt in tqdm(dts,ncols=100,desc='Generating Spectra'):

# scrape all the necessary metadata
idx = dts.index[dts==dt][0]
dni = metdata.dni[idx]
dhi = metdata.dhi[idx]
ghi = metdata.ghi[idx]
alb = metdata.albedo[idx]
solpos = metdata.solpos.iloc[idx]
zen = float(solpos.zenith)
azm = float(solpos.azimuth) - 180
lat = metdata.latitude

# create file names
suffix = f'_{str(dt.year)[-2:]}_{dt.month:02}_{dt.day:02}_{dt.hour:02}.txt'
dni_file = os.path.join(simulationPath, spectra_folder, "dni"+suffix)
dhi_file = os.path.join(simulationPath, spectra_folder, "dhi"+suffix)
ghi_file = os.path.join(simulationPath, spectra_folder, "ghi"+suffix)
alb_file = os.path.join(simulationPath, spectra_folder, "alb"+suffix)

if alb > 1:
print("albedo measured is an incorrect number, not scaling albedo generated")
else:
alb_scale = alb / sim_alb
spectral_alb.scale_values(alb_scale)
print(alb, sim_alb, alb_scale)

# Write albedo file
spectral_alb.to_file(alb_file)
# generate the base spectra
try:
spectral_dni, spectral_dhi, spectral_ghi = spectral_irradiance_smarts(zen, azm, min_wavelength=280)
except:
if scale_albedo_nonspectral_sim:
walb[dt] = 0.0
continue

# limit dataframes for calculations by scaling upper bound
scale_upper_bound=1200
tdni = spectral_dni.data[spectral_dni.data.index <= scale_upper_bound]
tdhi = spectral_dhi.data[spectral_dhi.data.index <= scale_upper_bound]
tghi = spectral_ghi.data[spectral_ghi.data.index <= scale_upper_bound]

# scaling spectra
if scale_spectra:

dni_scale = dni / integrate.trapz(tdni.value, x=tdni.index)
dhi_scale = dhi / integrate.trapz(tdhi.value, x=tdhi.index)
ghi_scale = ghi / integrate.trapz(tghi.value, x=tghi.index)

spectral_dni.scale_values(dni_scale)
spectral_dhi.scale_values(dhi_scale)
spectral_ghi.scale_values(ghi_scale)

# Determine Seasonal ground cover, if necessary
north = [1,2,3,4,10,11,12]
south = [5,6,7,8,9,10]
if lat < 0: winter = north
if lat > 0: winter = south

if ground == 'Seasonal':
MONTH = metdata.datetime[idx].month
if MONTH in winter :
if alb >= 0.6:
ground = 'Snow'
else:
ground = 'DryGrass'
else:
ground = 'Grass'

# Generate base spectral albedo
spectral_alb = spectral_albedo_smarts(zen, azm, ground, min_wavelength=280)

# Limit albedo by upper bound wavelength
talb = spectral_alb.data[spectral_alb.data.index <= scale_upper_bound]

# scaling albedo
if scale_albedo:
#***
# Currently using simple scaling model (scale by mean)
#***
denom = talb.values.mean()
scale_factor = alb / denom
spectral_alb.scale_values(scale_factor)

# If performing a non-spectral simulation, generate single albedo weighted by spectra
if scale_albedo_nonspectral_sim:
#SR = SR[SR.index <= scale_upper_bound] # placeholder for Spectral Responsivity
num = talb * tghi #* SR
num = integrate.trapz(num.value, x=num.index)
denom = tghi #* SR
denom = integrate.trapz(denom.value, x=denom.index)
alb_weighted = num / denom

walb[dt] = alb_weighted

# only save the files if performing a spectral simulation
if not scale_albedo_nonspectral_sim:
spectral_alb.to_file(alb_file)
spectral_dhi.to_file(dhi_file)
spectral_dni.to_file(dni_file)
spectral_ghi.to_file(ghi_file)

# save a basic csv of weighted albedo, indexed by datetime
if scale_albedo_nonspectral_sim:
walbPath = os.path.join(simulationPath,spectra_folder,'albedo_scaled_nonSpec.csv')
walb.to_csv(walbPath)
print('Weighted albedo CSV saved.')

return {'specALB':spectral_alb, 'specDNI':spectral_dni, 'specDHI':spectral_dhi,'weightedALB':walb.values}

return (spectral_alb, spectral_dni, spectral_dhi)
#return (spectral_alb, spectral_dni, spectral_dhi)
return {'specALB':spectral_alb, 'specDNI':spectral_dni, 'specDHI':spectral_dhi}

1 comment on commit 8b73d11

@mcbrown042
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: changed generate_spectra's use of pySMARTS albedo from "Zonal_" to "Local_ground_reflectance". Though practically similar, Zonal is intended for applications above 10 square kilometers.

Please sign in to comment.