Skip to content

Commit

Permalink
Merge remote-tracking branch 'remotes/NREL/main' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
cdeline committed Sep 25, 2024
2 parents 0f9461c + 9486645 commit f74bb03
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 81 deletions.
15 changes: 8 additions & 7 deletions bifacial_radiance/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2083,22 +2083,23 @@ def gendaylit1axis(self, metdata=None, trackerdict=None, startdate=None,
count = 0 # counter to get number of skyfiles created, just for giggles

trackerdict2={}
for i in range(0, len(trackerdict.keys())):
#for i in range(0, len(trackerdict.keys())):
for key in trackerdict.keys():
time_target = pd.to_datetime(key, format="%Y-%m-%d_%H%M").tz_localize(int(self.metdata.timezone*3600))
try:
time = metdata.datetime[i]
i = metdata.datetime.index(time_target)
except IndexError: #out of range error
break #
#filename = str(time)[5:-12].replace('-','_').replace(' ','_')
filename = time.strftime('%Y-%m-%d_%H%M')
self.name = filename
self.name = key

#check for GHI > 0
#if metdata.ghi[i] > 0:
if (metdata.ghi[i] > 0) & (~np.isnan(metdata.tracker_theta[i])):
skyfile = self.gendaylit(metdata=metdata,timeindex=i, debug=debug)
skyfile = self.gendaylit(metdata=metdata, timeindex=i, debug=debug)
# trackerdict2 reduces the dict to only the range specified.
trackerdict2[filename] = trackerdict[filename]
trackerdict2[filename]['skyfile'] = skyfile
trackerdict2[key] = trackerdict[key]
trackerdict2[key]['skyfile'] = skyfile
count +=1

print('Created {} skyfiles in /skies/'.format(count))
Expand Down
117 changes: 49 additions & 68 deletions bifacial_radiance/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ class ModuleObj(SuperClass):

def __repr__(self):
return str(type(self)) + ' : ' + str(self.getDataDict())
def __init__(self, name=None, x=None, y=None, z=None, bifi=1, modulefile=None,
def __init__(self, name=None, x=None, y=None, z=None, bifi=1, modulefile=None,
text=None, customtext='', customObject='', xgap=0.01, ygap=0.0, zgap=0.1,
numpanels=1, rewriteModulefile=True, cellModule=None,
glass=False, modulematerial='black', tubeParams=None,
numpanels=1, rewriteModulefile=True, cellModule=None,
glass=False, glassEdge=0.01, modulematerial='black', tubeParams=None,
frameParams=None, omegaParams=None, CECMod=None, hpc=False):
"""
Add module details to the .JSON module config file module.json.
Module definitions assume that the module .rad file is defined
with zero tilt, centered along the x-axis and y-axis for the center
of rotation of the module (+X/2, -X/2, +Y/2, -Y/2 on each side).
Tip: to define a module that is in 'portrait' mode, y > x.
Tip: to define a module that is in 'portrait' mode, y > x.
Parameters
------------
Expand All @@ -53,11 +53,16 @@ def __init__(self, name=None, x=None, y=None, z=None, bifi=1, modulefile=None,
Width of module along the axis of the torque tube or rack. (meters)
y : numeric
Length of module (meters)
z : numeric
Thickness of the module (meters), or of the glass if glass = True,
in which case absorber thickness will be 0.001 and glass whatever
thickness is given, with absorber in the middle of the glass.
bifi : numeric
Bifaciality of the panel (used for calculatePerformance). Between 0 (monofacial)
and 1, default 1.
modulefile : str
Existing radfile location in \objects. Otherwise a default value is used
Existing radfile location in \\objects. Otherwise a default value
is used
text : str
Text used in the radfile to generate the module. Manually passing
this value will overwrite module definition
Expand All @@ -75,29 +80,35 @@ def __init__(self, name=None, x=None, y=None, z=None, bifi=1, modulefile=None,
Default True. Will rewrite module file each time makeModule is run.
numpanels : int
Number of modules arrayed in the Y-direction. e.g.
1-up or 2-up, etc. (supports any number for carport/Mesa simulations)
1-up or 2-up, etc. (supports any number for carport/Mesa
simulations)
xgap : float
Panel space in X direction. Separation between modules in a row.
ygap : float
Gap between modules arrayed in the Y-direction if any.
zgap : float
Distance behind the modules in the z-direction to the edge of the tube (m)
Distance behind the modules in the z-direction to the edge of the
torquetube (m)
glass : bool
Add 5mm front and back glass to the module (glass/glass). Warning:
glass increases the analysis variability. Recommend setting
accuracy='high' in AnalysisObj.analysis()
glassEdge: float
Difference in space between module size and absorber part of the
module (or if cell-level module, full cell-level module size;
value will be applied as extra glass 1/2 to each side on x and y.
cellModule : dict
Dictionary with input parameters for creating a cell-level module.
Shortcut for ModuleObj.addCellModule()
tubeParams : dict
Dictionary with input parameters for creating a torque tube as part of the
module. Shortcut for ModuleObj.addTorquetube()
Dictionary with input parameters for creating a torque tube as
part of the module. Shortcut for ModuleObj.addTorquetube()
frameParams : dict
Dictionary with input parameters for creating a frame as part of the module.
Shortcut for ModuleObj.addFrame()
Dictionary with input parameters for creating a frame as part of
the module. Shortcut for ModuleObj.addFrame()
omegaParams : dict
Dictionary with input parameters for creating a omega or module support
structure. Shortcut for ModuleObj.addOmega()
Dictionary with input parameters for creating a omega or module
support structure. Shortcut for ModuleObj.addOmega()
CECMod : Dictionary with performance parameters needed for self.calculatePerformance()
lpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, Adjust
hpc : bool (default False)
Expand All @@ -109,9 +120,9 @@ def __init__(self, name=None, x=None, y=None, z=None, bifi=1, modulefile=None,
"""

self.keys = ['x', 'y', 'z', 'modulematerial', 'scenex','sceney',
'scenez','numpanels','bifi','text','modulefile', 'glass',
'offsetfromaxis','xgap','ygap','zgap']
self.keys = ['x', 'y', 'z', 'modulematerial', 'scenex', 'sceney',
'scenez', 'numpanels', 'bifi', 'text', 'modulefile', 'glass',
'glassEdge', 'offsetfromaxis', 'xgap', 'ygap', 'zgap' ]

#replace whitespace with underlines. what about \n and other weird characters?
# TODO: Address above comment?
Expand Down Expand Up @@ -259,6 +270,8 @@ def readModule(self, name=None):
moduleDict['modulematerial'] = 'black'
if not 'glass' in moduleDict:
moduleDict['glass'] = False
if not 'glassEdge' in moduleDict:
moduleDict['glassEdge'] = 0.01
if not 'z' in moduleDict:
moduleDict['z'] = 0.02
# set ModuleObj attributes from moduleDict
Expand Down Expand Up @@ -553,34 +566,21 @@ def _makeModuleFromDict(self, x=None, y=None, z=None, xgap=None, ygap=None,
self.offsetfromaxis = np.round(zgap + diam/2.0,8)
if hasattr(self, 'frame'):
self.offsetfromaxis = self.offsetfromaxis + self.frame.frame_z
# TODO: make sure the above is consistent with old version below
"""
if torquetube:
diam = torquetube['diameter']
torquetube_bool = torquetube['bool']
else:
diam=0
torquetube_bool = False
if self.axisofrotationTorqueTube == True:
if torquetube_bool == True:
self.offsetfromaxis = np.round(zgap + diam/2.0,8)
else:
self.offsetfromaxis = zgap
if hasattr(self, 'frame'):
self.offsetfromaxis = self.offsetfromaxis + self.frame.frame_z
"""

# Adding the option to replace the module thickess
if self.glass:
zglass = 0.01
print("\nWarning: module glass increases analysis variability. "
"Recommend setting `accuracy='high'` in AnalysisObj.analysis().\n")
else:
zglass = 0.0

if z is None:
if self.glass:
if z is None:
zglass = 0.01
z = 0.001
else:
zglass = z
z = 0.001

else: # no glass
zglass = 0.0
if z is None:
z = 0.020

self.z = z
Expand Down Expand Up @@ -637,47 +637,28 @@ def _makeModuleFromDict(self, x=None, y=None, z=None, xgap=None, ygap=None,
offsetfromaxis=self.offsetfromaxis)
if omega2omega_x > self.scenex:
self.scenex = omega2omega_x

# TODO: is the above line better than below?
# I think this causes it's own set of problems, need to check.
"""
if self.scenex <x:
scenex = x+xgap #overwriting scenex to maintain torquetube continuity
print ('Warning: Omega values have been provided, but' +
'the distance between modules with the omega'+
'does not match the x-gap provided.'+
'Setting x-gap to be the space between modules'+
'from the omega.')
else:
print ('Warning: Using omega-to-omega distance to define'+
'gap between modules'
+'xgap value not being used')
"""
else:
omegatext = ''

# Defining scenex if it was not defined by the Omegas,
# after the module has been created in case it is a
# cell-level Module, in which the "x" gets calculated internally.
# Also sanity check in case omega-to-omega distance is smaller
# than module.

#if torquetube_bool is True:
if hasattr(self,'torquetube'):
if self.torquetube.visible:
text += self.torquetube._makeTorqueTube(cc=_cc, zgap=zgap,
z_inc=_zinc, scenex=self.scenex)

# TODO: should there be anything updated here like scenez?
# YES.
if self.glass:
edge = 0.01
text = text+'\r\n! genbox stock_glass {} {} {} {} '.format(self.name+'_Glass',x+edge, y+edge, zglass)
text +='| xform -t {} {} {} '.format(-x/2.0-0.5*edge + _cc,
(-y*Ny/2.0)-(ygap*(Ny-1)/2.0)-0.5*edge,
self.offsetfromaxis - 0.5*zglass)
text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)
if hasattr(self,'glassEdge'):
glassEdge = self.glassEdge
else:
glassEdge = 0.01
self.glassEdge = glassEdge

text = text+'\r\n! genbox stock_glass {} {} {} {} '.format(self.name+'_Glass',x+glassEdge, y+glassEdge, zglass)
text +='| xform -t {} {} {} '.format(-x/2.0-0.5*glassEdge + _cc,
(-y*Ny/2.0)-(ygap*(Ny-1)/2.0)-0.5*glassEdge,
self.offsetfromaxis - 0.5*zglass)
text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)


text += frametext
Expand Down
10 changes: 5 additions & 5 deletions docs/sphinx/source/whatsnew/v0.4.3.rst
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
.. _whatsnew_0430:

v0.4.3 (08 / 27 / 2024)
v0.4.3 (Aug 27 2024)
------------------------
Bugfix Release ...


API Changes
~~~~~~~~~~~~
* A new function can now be called to compile results and report out final irradiance and performance data: :py:class:`~bifacial_radiance.RadianceObj.compileResults`.
* A new function can now be called to compile results and report out final irradiance and performance data: :func:`bifacial_radiance.RadianceObj.compileResults`. (This is a temporary function soon to be deprecated)
* Multiple modules and rows can now be selected in a single analysis scan. ``modWanted`` and ``rowWanted`` inputs in :py:class:`~bifacial_radiance.RadianceObj.analysis1axis` can now be a list, to select multiple rows and modules for scans. (:issue:`405`)(:pull:`408`)
* To support multiple modules and row scans for 1axis simulations, outputs like Wm2Front are now stored in ``trackerdict``.``Results`` (:issue:`405`)(:pull:`408`)
* ``mismatch.mad_fn`` has new functionality and input parameter `axis`. If a 2D matrix or dataframe is passed in as data, MAD is calculated along the row (default) or along the columns by passing 'axis=1'
* :func:`.mismatch.mad_fn` has new functionality and input parameter `axis`. If a 2D matrix or dataframe is passed in as data, MAD is calculated along the row (default) or along the columns by passing 'axis=1'
* :func:`bifacial_radiance.mismatch.mismatch_fit3` has been deprecated in favour of :func:`bifacial_radiance.mismatch.mismatch_fit2` which has a greater agreement with anual energy yield data (:issue:`520`)

Enhancements
Expand All @@ -20,8 +20,8 @@ Enhancements

Bug fixes
~~~~~~~~~
* Fixed error passing all of `sceneDict` into :py:class:`~bifacial_radiance.makeScene1axis`. (:issue:`502`)
* Fixed Pandas 2.0 errors by re-factoring ``mismatch.mad_fn`` (:issue:`449`)
* Fixed error passing all of `sceneDict` into :func:`~bifacial_radiance.RadianceObj.makeScene1axis`. (:issue:`502`)
* Fixed Pandas 2.0 errors by re-factoring :func:`.mismatch.mad_fn` (:issue:`449`)
* Switch from un-supported Versioneer to setuptools_scm (:issue:`519`)
* Numpy 2.0 compatibility bug (:issue:`521`)
* Fixed bug in :func:`bifacial_radiance.mismatch.mismatch_fit3` where the function was not returning the correct values. It has also been deprecated in favour of :func:`bifacial_radiance.mismatch.mismatch_fit2` which has a greater agreement with anual energy yield data (:issue:`520`)
Expand Down
4 changes: 3 additions & 1 deletion docs/sphinx/source/whatsnew/v0.4.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Bugfix Release ...

API Changes
~~~~~~~~~~~~
*
* New input parameter to :py:class:`~bifacial_radiance.ModuleObj and :py:class:`~bifacial_radiance.RadianceObj.makeModule`: `glassEdge`. If :py:class:`~bifacial_radiance.RadianceObj.makeModule` `glass` = True, then this extends the glass past the absorber edge by this total amount (half in each x and y direction). Default 10mm.
* Module glass thickness can be changed. In :py:class:`~bifacial_radiance.RadianceObj.makeModule`, if `glass` = True, then setting the `z` parameter will indicate the total (front + back) glass thickness with the 1mm absorber in the middle. The default is z = 10mm.

Enhancements
~~~~~~~~~~~~
Expand All @@ -16,6 +17,7 @@ Enhancements

Bug fixes
~~~~~~~~~
* Fixed a major error with indexing the irradiance conditions with :py:func:`~bifacial_radiance.RadianceObj.gendaylit1axis`. This could result in the trackerdict entry being mismatched from the metdata resource. (:issue:`441`)
* versioning with setuptools_scm- set fallback_version to bifirad v0.4.3 to prevent crashes if git is not present (:issue:`535`)(:pull:`539`)

Documentation
Expand Down
16 changes: 16 additions & 0 deletions tests/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,27 @@ def test_moduleFrameandOmegas():
# test cellModulescan (sensorsy = numellsy)
module.glass=True
module.addCellModule(**cellParams)
# re-load the module to make sure all of the params are the same
module2 = bifacial_radiance.ModuleObj(name='test-module')
assert module.text == module2.text
scene = demo.makeScene(module, sceneDict)
analysis = bifacial_radiance.AnalysisObj() # return an analysis object including the scan dimensions for back irradiance
frontscan, backscan = analysis.moduleAnalysis(scene, sensorsy=10) # Gives us the dictionaries with coordinates
assert backscan['xstart'] == pytest.approx(0.792)

def test_GlassModule():
# test the cell-level module generation
name = "_test_GlassModule"
# default glass=True with .001 absorber and 0.01 glass
demo = bifacial_radiance.RadianceObj(name) # Create a RadianceObj 'object'
module = demo.makeModule(name='test-module', rewriteModulefile=True, glass=True, x=1, y=2)
assert module.text == '! genbox black test-module 1 2 0.001 | xform -t -0.5 -1.0 0 -a 1 -t 0 2.0' +\
' 0\r\n! genbox stock_glass test-module_Glass 1.01 2.01 0.01 | xform -t -0.505 -1.005 -0.005 -a 1 -t 0 2.0 0'
# custom glass=True with .001 absorber and 0.005 glass and 0.02 glass edge
module = demo.makeModule(name='test-module', glass=True, x=1, y=2, z=0.005, glassEdge=0.02)
assert module.text == '! genbox black test-module 1 2 0.001 | xform -t -0.5 -1.0 0 -a 1 -t 0 2.0' +\
' 0\r\n! genbox stock_glass test-module_Glass 1.02 2.02 0.005 | xform -t -0.51 -1.01 -0.0025 -a 1 -t 0 2.0 0'

def test_CECmodule():
# Test adding CEC module in various ways
CECMod1 = pd.read_csv(os.path.join(TESTDIR, 'Canadian_Solar_Inc__CS5P_220M.csv'),
Expand Down

0 comments on commit f74bb03

Please sign in to comment.