Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored code to include functions for ground analysis #499

Merged
merged 28 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e460768
Added groundscan
Dec 14, 2023
7afac5e
Fixed save results
Dec 15, 2023
99fea31
Routine testing
Dec 15, 2023
cde5524
Analysis.analysis modified to accept just one frontscan.
cdeline Dec 19, 2023
320de0b
Merge branch 'development2' of https://github.com/NREL/bifacial_radia…
cdeline Dec 19, 2023
0300a57
Groundscan analysis refactoring
Dec 27, 2023
ab63422
Update main.py
Dec 27, 2023
ef1b423
cleanup conflicts on main
shirubana Dec 27, 2023
8081ed2
groundanalsysi reduction
shirubana Dec 27, 2023
e9905c3
removing duplicate groundAnalysis??
shirubana Dec 27, 2023
ac13015
cleanup main more
shirubana Dec 27, 2023
7ea2c92
cleanup temp files that were there...
shirubana Dec 27, 2023
a12bb68
Testing for 1axis and fixedtilt
Dec 27, 2023
d5001f6
Merge branch 'development2' of https://github.com/NREL/bifacial_radia…
Dec 27, 2023
642f119
journal cleanup
shirubana Dec 27, 2023
317ccac
Fixed moduleAnalysis and analysis1axis back
Dec 27, 2023
cd9ff14
Fixed journals to fit refactored code
Dec 27, 2023
9450ea1
update sphinx pending.rst
cdeline Feb 21, 2024
21097b9
Merge branch 'development' into development2
cdeline Mar 25, 2024
12a073a
revert savekey. add pytests.
cdeline Mar 25, 2024
04692d7
Merge remote-tracking branch 'remotes/NREL/development' into developm…
cdeline Apr 25, 2024
615fab5
update analysis1axisground to push Wm2Ground and sensorsground into A…
cdeline Apr 25, 2024
6de9283
notebook examples updated
cdeline Apr 26, 2024
d32e148
set coveralls to debug
cdeline Apr 26, 2024
4c86807
update docstrings.
cdeline Apr 26, 2024
886c8fb
Add rowWanted, moduleWanted and correct calcs to analysis.groundAnaly…
cdeline Apr 27, 2024
ae4127c
Remove coveralls debug mode. Add x,y,z to getResults output.
cdeline Apr 29, 2024
a4feffd
update whatsnew
cdeline Apr 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
SMARTSPATH: /home/runner/work/bifacial_radiance/bifacial_radiance/SMARTS_295_Linux

- name: Coveralls
if: matrix.python-version == 3.11 && ${{ matrix.env }} == '-r requirements.txt .[all]'
if: matrix.python-version == 3.11 # && ${{ matrix.env }} == '-r requirements.txt .[all]'
run: |
coveralls --service=github
env:
Expand Down
Binary file added bifacial_radiance/.DS_Store
Binary file not shown.
Binary file not shown.
18 changes: 18 additions & 0 deletions bifacial_radiance/data/module.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
{
"PVmod": {
"bifi": 1,
"glass": false,
"modulefile": "objects/PVmod.rad",
"modulematerial": "black",
"numpanels": 1,
"offsetfromaxis": 0,
"scenex": 2.01,
"sceney": 1.0,
"scenez": 0.1,
"text": "! genbox black PVmod 2 1 0.02 | xform -t -1.0 -0.5 0 -a 1 -t 0 1.0 0",
"x": 2,
"xgap": 0.01,
"y": 1,
"ygap": 0.0,
"z": 0.02,
"zgap": 0.1
},
"PrismSolar-Bi60": {
"bifi": 1,
"cellModule": {
Expand Down
236 changes: 212 additions & 24 deletions bifacial_radiance/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2837,7 +2837,7 @@ def analysis1axis(self, trackerdict=None, singleindex=None, accuracy='low',
Activates internal printing of the function to help debugging.
sceneNum : int
Index of the scene number in the list of scenes per trackerdict. default 0
Append : Bool (default True)
append : Bool (default True)
Append trackerdict['AnalysisObj'] to list. Otherwise over-write any
AnalysisObj's and start 1axis analysis from scratch

Expand Down Expand Up @@ -2936,8 +2936,115 @@ def analysis1axis(self, trackerdict=None, singleindex=None, accuracy='low',
self.trackerdict = trackerdict
return trackerdict

def analysis1axisground(self, trackerdict=None, singleindex=None, accuracy='low',
customname=None, modWanted=None, rowWanted=None, sensorsground=None,
sensorsgroundx=1, sceneNum=0, append=True):
"""
uses :py:class:`bifacial_radiance.AnalysisObj`.groundAnalysis to run a
single ground scan along the entire row-row pitch.

Parameters
----------
trackerdict : optional
singleindex : str
For single-index mode, just the one index we want to run (new in 0.2.3).
Example format '21_06_14_12_30' for 2021 June 14th 12:30 pm
accuracy : str
'low' (default) or 'high', resolution option used during _irrPlot and rtrace
customname : str
Custom text string to be added to the file name for the results .CSV files
modWanted : int
Module to be sampled. Index starts at 1.
rowWanted : int
Row to be sampled. Index starts at 1. (row 1)
sensorsground : int (default None)
Number of scan points along the scene pitch. Default every 20cm
sensorsgroundx : int (default 1)
Number of scans in the x dimension
sceneNum : int
Index of the scene number in the list of scenes per trackerdict. default 0
append : Bool (default True)
Append trackerdict['AnalysisObj'] to list. Otherwise over-write any
AnalysisObj's and start 1axis analysis from scratch

Returns
-------
trackerdict is returned with :py:class:`bifacial_radiance.AnalysisObj`
for each timestamp:

trackerdict.key.'AnalysisObj' : analysis object for this tracker theta
to get a dictionary of results, run :py:class:`bifacial_radiance.AnalysisObj`.getResults
:py:class:`bifacial_radiance.AnalysisObj`.getResults returns the following keys:
'Wm2Ground' : np.array of Wm-2 irradiances along the ground, len=sensorsground
'sensorsground' : int of number of ground scan points

"""

import warnings, itertools

if customname is None:
customname = ''

if trackerdict == None:
try:
trackerdict = self.trackerdict
except AttributeError:
print('No trackerdict value passed or available in self')

if not append:
warnings.warn('Append=False. Over-writing any existing `AnalysisObj` in trackerdict.')
for key in trackerdict:
trackerdict[key]['AnalysisObj'] = []

if singleindex is None: # run over all values in trackerdict
trackerkeys = sorted(trackerdict.keys())
else: # run in single index mode.
trackerkeys = [singleindex]

for index in trackerkeys: # either full list of trackerdict keys, or single index
octfile = trackerdict[index]['octfile']
scene = trackerdict[index]['scenes'][sceneNum]
name = '1axis_groundscan_%s%s'%(index,customname)
trackerdict[index]['Results'] = []
if octfile is None:
continue # don't run analysis if the octfile is none

#Results = {'Groundscan':customname}
try: # look for missing data
analysis = AnalysisObj(octfile,name)
analysis.sceneNum = sceneNum
#name = '1axis_%s%s'%(index,customname)
groundscanid = analysis.groundAnalysis(scene=scene, modWanted=modWanted,
rowWanted=rowWanted,
sensorsground=sensorsground)
analysis.analysis(octfile=octfile,name=name,
frontscan=groundscanid, accuracy=accuracy)
#Results['AnalysisObj']=analysis
# try to push Wm2Ground and sensorsground into the AnalysisObj...
analysis.Wm2Ground = analysis.Wm2Front
del analysis.Wm2Front
analysis.sensorsground = analysis.Wm2Ground.__len__()
trackerdict[index]['AnalysisObj'].append(analysis)
except Exception as e: # problem with file. TODO: only catch specific error types here.
warnings.warn('Index: {}. Problem with file. Error: {}. Skipping'.format(index,e), Warning)
return
"""
try: #on error, trackerdict[index] is returned empty
Results['Wm2Ground'] = analysis.Wm2Front
Results['sensorsground'] = analysis.Wm2Front.__len__()
except AttributeError as e: # no key Wm2Front.
warnings.warn('Index: {}. Trackerdict key not found: {}. Skipping'.format(index,e), Warning)
return
trackerdict[index]['Results'].append(Results)
"""
try:
print('Index: {}. Wm2Ground: {}. sensorsground: {}'.format(index,
np.mean(analysis.Wm2Ground), sensorsground))
except AttributeError: #no Wm2Front
warnings.warn('AnalysisObj not successful.')
return trackerdict


def calculateResults(self, CECMod=None, glassglass=False, bifacialityfactor=None,
CECMod2=None, agriPV=False):
'''
Expand Down Expand Up @@ -4331,10 +4438,11 @@ def getResults(self):
-------
Results : dict. irradiance scan results
"""
keylist = ['rowWanted', 'modWanted', 'sceneNum', 'name',
'Wm2Front', 'Wm2Back', 'backRatio', 'mattype', 'rearMat' ]
keylist = ['rowWanted', 'modWanted', 'sceneNum', 'name', 'x', 'y','z',
'Wm2Front', 'Wm2Back', 'Wm2Ground', 'backRatio', 'mattype', 'rearMat' ]
resultdict = {k: v for k, v in self.__dict__.items() if k in keylist}
return pd.DataFrame.from_dict(resultdict, orient='index').T.rename(columns={'modWanted':'modNum', 'rowWanted':'rowNum'})
return pd.DataFrame.from_dict(resultdict, orient='index').T.rename(
columns={'modWanted':'modNum', 'rowWanted':'rowNum'})



Expand Down Expand Up @@ -4639,7 +4747,7 @@ def _saveResults(self, data=None, reardata=None, savefile=None, RGB = False):
for col in df.columns:
setattr(self, col, np.array(df[col])) #cdeline: changed from list to np.array on 3/16/24
# only save a subset
df = df.drop(columns=['rearX','rearY','backRatio'], errors='ignore')
df = df.drop(columns=['backRatio'], errors='ignore')
df.to_csv(os.path.join("results", savefile), sep = ',',
index = False)

Expand Down Expand Up @@ -4699,7 +4807,7 @@ def moduleAnalysis(self, scene, modWanted=None, rowWanted=None,
sensorsy=9, sensorsx=1,
frontsurfaceoffset=0.001, backsurfaceoffset=0.001,
modscanfront=None, modscanback=None, relative=False,
debug=False, sensorsground=None):
debug=False):

"""
Handler function that decides how to handle different number of front
Expand Down Expand Up @@ -5058,21 +5166,97 @@ def _checkSensors(sensors):
else:
backscan2 = backscan.copy()

if sensorsground is not None:
groundscan = frontscan2.copy()
groundsensorspacing = pitch / (sensorsground - 1)
groundscan['xstart'] = x1
groundscan['ystart'] = y1
groundscan['zstart'] = 0.05 # Set it 5 cm from the ground.
groundscan['xinc'] = groundsensorspacing * np.sin(azimuth)
groundscan['yinc'] = groundsensorspacing * (-1 * np.cos(azimuth))
groundscan['Ny'] = sensorsground
groundscan['Nz'] = 1
groundscan['orient'] = '0 0 -1'
return frontscan2, backscan2

def groundAnalysis(self, scene, modWanted=None, rowWanted=None,
sensorsground=None, sensorsgroundx=1):
"""
run a single ground scan along the entire row-row pitch of the scene.

Parameters
----------
scene : ``SceneObj``
Generated with :py:class:`~bifacial_radiance.RadianceObj.makeScene`.
modWanted : int
Module wanted to sample. If none, defaults to center module (rounding down)
rowWanted : int
Row wanted to sample. If none, defaults to center row (rounding down)
sensorsground : int (default None)
Number of scan points along the scene pitch. Default every 20cm
sensorsgroundx : int (default 1)
Number of scans in the x dimension, the side perpendicular
to the collector width (CW) of the module(s)

return frontscan2, backscan2, groundscan
Returns
-------
groundscan : dictionary
Scan dictionary for the ground including beneath modules. Used to pass into
:py:class:`~bifacial_radiance.AnalysisObj.analysis` function

return frontscan2, backscan2
"""

dtor = np.pi/180.0

# Internal scene parameters are stored in scene.sceneDict. Load these into local variables
sceneDict = scene.sceneDict

azimuth = sceneDict['azimuth']
#tilt = sceneDict['tilt']
nMods = sceneDict['nMods']
nRows = sceneDict['nRows']
originx = sceneDict['originx']
originy = sceneDict['originy']

sceney = scene.module.sceney
scenex = scene.module.scenex

# x needed for sensorsx>1 case
#x = scene.module.x

## Check for proper input variables in sceneDict
if 'pitch' in sceneDict:
pitch = sceneDict['pitch']
elif 'gcr' in sceneDict:
pitch = sceney / sceneDict['gcr']
else:
raise Exception("Error: no 'pitch' or 'gcr' passed in sceneDict" )

if sensorsground is None:
sensorsground = max(1,round(pitch * 5)) # scan every 20 cm
if modWanted is None:
modWanted = round(nMods / 1.99)
if rowWanted is None:
rowWanted = round(nRows / 1.99)
self.modWanted = modWanted
self.rowWanted = rowWanted


x0 = (modWanted-1)*scenex - (scenex*(round(nMods/1.99)*1.0-1))
y0 = (rowWanted-1)*pitch - (pitch*(round(nRows / 1.99)*1.0-1))

x1 = x0 * np.cos ((180-azimuth)*dtor) - y0 * np.sin((180-azimuth)*dtor)
y1 = x0 * np.sin ((180-azimuth)*dtor) + y0 * np.cos((180-azimuth)*dtor)

xstart = x1 + originx
ystart = y1 + originy
zstart = 0.05

ground_orient = '0 0 -1'

groundsensorspacing = pitch / (sensorsground - 1)
xinc = groundsensorspacing * np.sin((azimuth)*dtor)
yinc = groundsensorspacing * np.cos((azimuth)*dtor)
zinc = 0

groundscan = {'xstart': xstart, 'ystart': ystart,
'zstart': zstart,
'xinc':xinc, 'yinc': yinc, 'zinc':zinc,
'sx_xinc':0, 'sx_yinc':0,
'sx_zinc':0,
'Nx': sensorsgroundx, 'Ny':sensorsground, 'Nz':1,
'orient':ground_orient }

return groundscan

def analyzeRow(self, octfile, scene, rowWanted=None, name=None,
sensorsy=None, sensorsx=None ):
Expand Down Expand Up @@ -5217,9 +5401,7 @@ def analyzeField(self, octfile, scene, name=None,

return result



def analysis(self, octfile, name, frontscan, backscan,
def analysis(self, octfile, name, frontscan, backscan=None,
plotflag=False, accuracy='low', RGB=False):
"""
General analysis function, where linepts are passed in for calling the
Expand All @@ -5237,9 +5419,9 @@ def analysis(self, octfile, name, frontscan, backscan,
frontscan : scene.frontscan object
Object with the sensor location information for the
front of the module
backscan : scene.backscan object
backscan : scene.backscan object. (optional)
Object with the sensor location information for the
rear side of the module
rear side of the module.
plotflag : boolean
Include plot of resulting irradiance
accuracy : string
Expand All @@ -5266,10 +5448,16 @@ def analysis(self, octfile, name, frontscan, backscan,
frontDict = self._irrPlot(octfile, linepts, name+'_Front',
plotflag=plotflag, accuracy=accuracy)

if backscan is None: #only one scan
if frontDict is not None:
self.Wm2Front = np.mean(frontDict['Wm2'])
self._saveResults(frontDict, reardata=None, savefile='irr_%s.csv'%(name), RGB=RGB)
return frontDict
#bottom view.
linepts = self._linePtsMakeDict(backscan)
backDict = self._irrPlot(octfile, linepts, name+'_Back',
plotflag=plotflag, accuracy=accuracy)

# don't save if _irrPlot returns an empty file.
if frontDict is not None:
if len(frontDict['Wm2']) != len(backDict['Wm2']):
Expand Down
Binary file added docs/.DS_Store
Binary file not shown.
Loading
Loading