bifacial-radiance 0.5.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bifacial_radiance/HPCScripts/BasicSimulations/addNewModule.py +15 -0
- bifacial_radiance/HPCScripts/BasicSimulations/dask_on_node.sh +11 -0
- bifacial_radiance/HPCScripts/BasicSimulations/run_sbatch.sbatch +51 -0
- bifacial_radiance/HPCScripts/BasicSimulations/simulate_fixedtilt_gencumsky.py +110 -0
- bifacial_radiance/HPCScripts/BasicSimulations/simulate_fixedtilt_gendaylit.py +102 -0
- bifacial_radiance/HPCScripts/BasicSimulations/simulate_tracking_gendaylit.py +126 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/PuertoRico.py +168 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/PuertoRico_2.py +166 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/PuertoRico_Original.py +195 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/basic_module_sampling.py +154 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/compile_B.py +162 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/compile_Cases.py +122 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/compile_CasesMonth.py +142 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/compile_PRNew.py +91 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/compile_PRNewP2.py +95 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/compile_TreeResults.py +108 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/compile_basic_module_sampling.py +103 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/simulate_JackHourly.py +160 -0
- bifacial_radiance/HPCScripts/Other Examples (unorganized)/simulate_improvedArray_Oct2127.py +623 -0
- bifacial_radiance/TEMP/.gitignore +4 -0
- bifacial_radiance/__init__.py +24 -0
- bifacial_radiance/data/CEC Modules.csv +16860 -0
- bifacial_radiance/data/default.ini +65 -0
- bifacial_radiance/data/falsecolor.exe +0 -0
- bifacial_radiance/data/gencumsky/License.txt +54 -0
- bifacial_radiance/data/gencumsky/Makefile +17 -0
- bifacial_radiance/data/gencumsky/README.txt +9 -0
- bifacial_radiance/data/gencumsky/Solar Irradiation Modelling.doc +0 -0
- bifacial_radiance/data/gencumsky/Sun.cpp +118 -0
- bifacial_radiance/data/gencumsky/Sun.h +45 -0
- bifacial_radiance/data/gencumsky/average_val.awk +3 -0
- bifacial_radiance/data/gencumsky/cPerezSkyModel.cpp +238 -0
- bifacial_radiance/data/gencumsky/cPerezSkyModel.h +57 -0
- bifacial_radiance/data/gencumsky/cSkyVault.cpp +536 -0
- bifacial_radiance/data/gencumsky/cSkyVault.h +86 -0
- bifacial_radiance/data/gencumsky/climateFile.cpp +312 -0
- bifacial_radiance/data/gencumsky/climateFile.h +37 -0
- bifacial_radiance/data/gencumsky/cumulative.cal +177 -0
- bifacial_radiance/data/gencumsky/cumulative.rad +14 -0
- bifacial_radiance/data/gencumsky/cumulativesky_rotated.rad +2 -0
- bifacial_radiance/data/gencumsky/gencumulativesky +0 -0
- bifacial_radiance/data/gencumsky/gencumulativesky.cpp +269 -0
- bifacial_radiance/data/gencumsky/make_gencumskyexe.py +107 -0
- bifacial_radiance/data/gencumsky/paths.h +62 -0
- bifacial_radiance/data/gencumulativesky +0 -0
- bifacial_radiance/data/gencumulativesky.exe +0 -0
- bifacial_radiance/data/ground.rad +83 -0
- bifacial_radiance/data/module.json +103 -0
- bifacial_radiance/gui.py +1696 -0
- bifacial_radiance/images/fig1_fixed_small.gif +0 -0
- bifacial_radiance/images/fig2_tracked_small.gif +0 -0
- bifacial_radiance/load.py +1156 -0
- bifacial_radiance/main.py +5673 -0
- bifacial_radiance/mismatch.py +461 -0
- bifacial_radiance/modelchain.py +299 -0
- bifacial_radiance/module.py +1427 -0
- bifacial_radiance/performance.py +466 -0
- bifacial_radiance/spectral_utils.py +555 -0
- bifacial_radiance-0.5.1.dist-info/METADATA +129 -0
- bifacial_radiance-0.5.1.dist-info/RECORD +63 -0
- bifacial_radiance-0.5.1.dist-info/WHEEL +6 -0
- bifacial_radiance-0.5.1.dist-info/licenses/LICENSE +30 -0
- bifacial_radiance-0.5.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1427 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
@author: cdeline
|
|
4
|
+
|
|
5
|
+
ModuleObj class for defining module geometry
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pvlib
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from bifacial_radiance.main import _missingKeyWarning, _popen, DATA_PATH
|
|
14
|
+
|
|
15
|
+
class SuperClass:
|
|
16
|
+
def __repr__(self):
|
|
17
|
+
return str(self.getDataDict())
|
|
18
|
+
def getDataDict(self):
|
|
19
|
+
"""
|
|
20
|
+
return dictionary values from self. Originally stored as self.data
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
return dict(zip(self.keys,[getattr(self,k) for k in self.keys]))
|
|
24
|
+
|
|
25
|
+
class ModuleObj(SuperClass):
|
|
26
|
+
"""
|
|
27
|
+
Module object to store module & torque tube details.
|
|
28
|
+
Does the heavy lifting of demo.makeModule()
|
|
29
|
+
Module details are passed in and stored in module.json.
|
|
30
|
+
Pass this object into makeScene or makeScene1axis.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __repr__(self):
|
|
35
|
+
return str(type(self)) + ' : ' + str(self.getDataDict())
|
|
36
|
+
def __init__(self, name=None, x=None, y=None, z=None, bifi=1, modulefile=None,
|
|
37
|
+
text=None, customtext='', customObject='', xgap=0.01, ygap=0.0, zgap=0.1,
|
|
38
|
+
numpanels=1, rewriteModulefile=True, cellModule=None,
|
|
39
|
+
glass=False, glassEdge=0.01, modulematerial='black', tubeParams=None,
|
|
40
|
+
frameParams=None, omegaParams=None, CECMod=None, hpc=False):
|
|
41
|
+
"""
|
|
42
|
+
Add module details to the .JSON module config file module.json.
|
|
43
|
+
Module definitions assume that the module .rad file is defined
|
|
44
|
+
with zero tilt, centered along the x-axis and y-axis for the center
|
|
45
|
+
of rotation of the module (+X/2, -X/2, +Y/2, -Y/2 on each side).
|
|
46
|
+
Tip: to define a module that is in 'portrait' mode, y > x.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
------------
|
|
50
|
+
name : str
|
|
51
|
+
Input to name the module type
|
|
52
|
+
x : numeric
|
|
53
|
+
Width of module along the axis of the torque tube or rack. (meters)
|
|
54
|
+
y : numeric
|
|
55
|
+
Length of module (meters)
|
|
56
|
+
z : numeric
|
|
57
|
+
Thickness of the module (meters), or of the glass if glass = True,
|
|
58
|
+
in which case absorber thickness will be 0.001 and glass whatever
|
|
59
|
+
thickness is given, with absorber in the middle of the glass.
|
|
60
|
+
bifi : numeric
|
|
61
|
+
Bifaciality of the panel (used for calculatePerformance). Between 0 (monofacial)
|
|
62
|
+
and 1, default 1.
|
|
63
|
+
modulefile : str
|
|
64
|
+
Existing radfile location in \\objects. Otherwise a default value
|
|
65
|
+
is used
|
|
66
|
+
text : str
|
|
67
|
+
Text used in the radfile to generate the module. Manually passing
|
|
68
|
+
this value will overwrite module definition
|
|
69
|
+
customtext : str
|
|
70
|
+
Added-text used in the radfile to generate any
|
|
71
|
+
extra details in the racking/module. Does not overwrite
|
|
72
|
+
generated module (unlike "text"), but adds to it at the end.
|
|
73
|
+
customObject : str
|
|
74
|
+
Append to the module object file a pre-genereated radfile. This
|
|
75
|
+
must start with the file path name. Does not overwrite
|
|
76
|
+
generated module (unlike "text"), but adds to it at the end.
|
|
77
|
+
It automatically inserts radiance's text before the object name so
|
|
78
|
+
its inserted into scene properly ('!xform -rz 0')
|
|
79
|
+
rewriteModulefile : bool
|
|
80
|
+
Default True. Will rewrite module file each time makeModule is run.
|
|
81
|
+
numpanels : int
|
|
82
|
+
Number of modules arrayed in the Y-direction. e.g.
|
|
83
|
+
1-up or 2-up, etc. (supports any number for carport/Mesa
|
|
84
|
+
simulations)
|
|
85
|
+
xgap : float
|
|
86
|
+
Panel space in X direction. Separation between modules in a row.
|
|
87
|
+
ygap : float
|
|
88
|
+
Gap between modules arrayed in the Y-direction if any.
|
|
89
|
+
zgap : float
|
|
90
|
+
Distance behind the modules in the z-direction to the edge of the
|
|
91
|
+
torquetube (m)
|
|
92
|
+
glass : bool
|
|
93
|
+
Add 5mm front and back glass to the module (glass/glass). Warning:
|
|
94
|
+
glass increases the analysis variability. Recommend setting
|
|
95
|
+
accuracy='high' in AnalysisObj.analysis()
|
|
96
|
+
glassEdge: float
|
|
97
|
+
Difference in space between module size and absorber part of the
|
|
98
|
+
module (or if cell-level module, full cell-level module size;
|
|
99
|
+
value will be applied as extra glass 1/2 to each side on x and y.
|
|
100
|
+
cellModule : dict
|
|
101
|
+
Dictionary with input parameters for creating a cell-level module.
|
|
102
|
+
Shortcut for ModuleObj.addCellModule()
|
|
103
|
+
tubeParams : dict
|
|
104
|
+
Dictionary with input parameters for creating a torque tube as
|
|
105
|
+
part of the module. Shortcut for ModuleObj.addTorquetube()
|
|
106
|
+
frameParams : dict
|
|
107
|
+
Dictionary with input parameters for creating a frame as part of
|
|
108
|
+
the module. Shortcut for ModuleObj.addFrame()
|
|
109
|
+
omegaParams : dict
|
|
110
|
+
Dictionary with input parameters for creating a omega or module
|
|
111
|
+
support structure. Shortcut for ModuleObj.addOmega()
|
|
112
|
+
CECMod : Dictionary with performance parameters needed for self.calculatePerformance()
|
|
113
|
+
lpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, Adjust
|
|
114
|
+
hpc : bool (default False)
|
|
115
|
+
Set up module in HPC mode. Namely turn off read/write to module.json
|
|
116
|
+
and just pass along the details in the module object. Note that
|
|
117
|
+
calling e.g. addTorquetube() after this will tend to write to the
|
|
118
|
+
module.json so pass all geometry parameters at once in to makeModule
|
|
119
|
+
for best response.
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
self.keys = ['x', 'y', 'z', 'modulematerial', 'scenex', 'sceney',
|
|
124
|
+
'scenez', 'numpanels', 'bifi', 'text', 'modulefile', 'glass',
|
|
125
|
+
'glassEdge', 'offsetfromaxis', 'xgap', 'ygap', 'zgap' ]
|
|
126
|
+
|
|
127
|
+
#replace whitespace with underlines. what about \n and other weird characters?
|
|
128
|
+
# TODO: Address above comment?
|
|
129
|
+
self.name = str(name).strip().replace(' ', '_')
|
|
130
|
+
self.customtext = customtext
|
|
131
|
+
self.customObject = customObject
|
|
132
|
+
self._manual_text = text
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
if Efficiency is not None:
|
|
136
|
+
self.Efficiency = Efficiency
|
|
137
|
+
if Temp_coeff is not None:
|
|
138
|
+
self.Temp_coeff = Temp_coeff
|
|
139
|
+
if Peak_Power is not None:
|
|
140
|
+
self.Peak_Power = Peak_Power
|
|
141
|
+
if Module_name is not None:
|
|
142
|
+
self.Module_name = Module_name
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
# are we writing to JSON with passed data or just reading existing?
|
|
146
|
+
if (x is None) & (y is None) & (cellModule is None) & (text is None):
|
|
147
|
+
#just read in file. If .rad file doesn't exist, make it.
|
|
148
|
+
self.readModule(name=name)
|
|
149
|
+
if name is not None:
|
|
150
|
+
self._saveModule(savedata=None, json=False,
|
|
151
|
+
rewriteModulefile=False)
|
|
152
|
+
|
|
153
|
+
else:
|
|
154
|
+
# set initial variables that aren't passed in
|
|
155
|
+
scenex = sceney = scenez = offsetfromaxis = 0
|
|
156
|
+
"""
|
|
157
|
+
# TODO: this is kind of confusing and should probably be changed
|
|
158
|
+
# set torque tube internal dictionary
|
|
159
|
+
tubeBool = torquetube
|
|
160
|
+
torquetube = {'bool':tubeBool,
|
|
161
|
+
'diameter':tubeParams['diameter'],
|
|
162
|
+
'tubetype':tubeParams['tubetype'],
|
|
163
|
+
'material':tubeParams['material']
|
|
164
|
+
}
|
|
165
|
+
try:
|
|
166
|
+
self.axisofrotationTorqueTube = tubeParams['axisofrotation']
|
|
167
|
+
except AttributeError:
|
|
168
|
+
self.axisofrotationTorqueTube = False
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
# set data object attributes from datakey list.
|
|
172
|
+
for key in self.keys:
|
|
173
|
+
setattr(self, key, eval(key))
|
|
174
|
+
|
|
175
|
+
if tubeParams:
|
|
176
|
+
if 'bool' in tubeParams: # backward compatible with pre-0.4
|
|
177
|
+
tubeParams['visible'] = tubeParams.pop('bool')
|
|
178
|
+
if 'torqueTubeMaterial' in tubeParams: # pre-0.4
|
|
179
|
+
tubeParams['material'] = tubeParams.pop('torqueTubeMaterial')
|
|
180
|
+
self.addTorquetube(**tubeParams, recompile=False)
|
|
181
|
+
if omegaParams:
|
|
182
|
+
self.addOmega(**omegaParams, recompile=False)
|
|
183
|
+
|
|
184
|
+
if frameParams:
|
|
185
|
+
self.addFrame(**frameParams, recompile=False)
|
|
186
|
+
|
|
187
|
+
if cellModule:
|
|
188
|
+
self.addCellModule(**cellModule, recompile=False)
|
|
189
|
+
|
|
190
|
+
self.addCEC(CECMod, glass, bifi=bifi)
|
|
191
|
+
|
|
192
|
+
if self._manual_text:
|
|
193
|
+
print('Warning: Module text manually passed and not '
|
|
194
|
+
f'generated: {self._manual_text}')
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if self.modulefile is None:
|
|
200
|
+
self.modulefile = os.path.join('objects',
|
|
201
|
+
self.name + '.rad')
|
|
202
|
+
print("\nModule Name:", self.name)
|
|
203
|
+
|
|
204
|
+
if hpc:
|
|
205
|
+
self.compileText(rewriteModulefile, json=False)
|
|
206
|
+
else:
|
|
207
|
+
self.compileText(rewriteModulefile)
|
|
208
|
+
|
|
209
|
+
def compileText(self, rewriteModulefile=True, json=True):
|
|
210
|
+
"""
|
|
211
|
+
Generate the text for the module .rad file based on ModuleObj attributes.
|
|
212
|
+
Optionally save details to the module.json and module.rad files.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
------------
|
|
216
|
+
rewriteModulefile : bool (default True)
|
|
217
|
+
Overwrite the .rad file for the module
|
|
218
|
+
json : bool (default True)
|
|
219
|
+
Update the module.json file with ModuleObj attributes
|
|
220
|
+
|
|
221
|
+
"""
|
|
222
|
+
saveDict = self.getDataDict()
|
|
223
|
+
|
|
224
|
+
if hasattr(self,'cellModule'):
|
|
225
|
+
saveDict = {**saveDict, 'cellModule':self.cellModule.getDataDict()}
|
|
226
|
+
if hasattr(self,'torquetube'):
|
|
227
|
+
saveDict = {**saveDict, 'torquetube':self.torquetube.getDataDict()}
|
|
228
|
+
if hasattr(self,'omega'):
|
|
229
|
+
saveDict = {**saveDict, 'omegaParams':self.omega.getDataDict()}
|
|
230
|
+
if hasattr(self,'frame'):
|
|
231
|
+
saveDict = {**saveDict, 'frameParams':self.frame.getDataDict()}
|
|
232
|
+
if getattr(self, 'CECMod', None) is not None:
|
|
233
|
+
saveDict = {**saveDict, 'CECMod':self.CECMod.getDataDict()}
|
|
234
|
+
|
|
235
|
+
self._makeModuleFromDict(**saveDict)
|
|
236
|
+
|
|
237
|
+
#write JSON data out and write radfile if it doesn't exist
|
|
238
|
+
self._saveModule({**saveDict, **self.getDataDict()}, json=json,
|
|
239
|
+
rewriteModulefile=rewriteModulefile)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def readModule(self, name=None):
|
|
243
|
+
"""
|
|
244
|
+
Read in available modules in module.json. If a specific module name is
|
|
245
|
+
passed, return those details into the SceneObj. Otherwise
|
|
246
|
+
return available module list.
|
|
247
|
+
|
|
248
|
+
Parameters: name (str) Name of module to be read
|
|
249
|
+
|
|
250
|
+
Returns: moduleDict dictionary or list of modulenames if name is not passed in.
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
import json
|
|
254
|
+
filedir = os.path.join(DATA_PATH,'module.json')
|
|
255
|
+
with open( filedir ) as configfile:
|
|
256
|
+
data = json.load(configfile)
|
|
257
|
+
|
|
258
|
+
modulenames = data.keys()
|
|
259
|
+
if name is None:
|
|
260
|
+
return list(modulenames)
|
|
261
|
+
|
|
262
|
+
if name in modulenames:
|
|
263
|
+
moduleDict = data[name]
|
|
264
|
+
self.name = name
|
|
265
|
+
# BACKWARDS COMPATIBILITY - look for missing keys
|
|
266
|
+
if not 'scenex' in moduleDict:
|
|
267
|
+
moduleDict['scenex'] = moduleDict['x']
|
|
268
|
+
if not 'sceney' in moduleDict:
|
|
269
|
+
moduleDict['sceney'] = moduleDict['y']
|
|
270
|
+
if not 'offsetfromaxis' in moduleDict:
|
|
271
|
+
moduleDict['offsetfromaxis'] = 0
|
|
272
|
+
if not 'modulematerial' in moduleDict:
|
|
273
|
+
moduleDict['modulematerial'] = 'black'
|
|
274
|
+
if not 'glass' in moduleDict:
|
|
275
|
+
moduleDict['glass'] = False
|
|
276
|
+
if not 'glassEdge' in moduleDict:
|
|
277
|
+
moduleDict['glassEdge'] = 0.01
|
|
278
|
+
if not 'z' in moduleDict:
|
|
279
|
+
moduleDict['z'] = 0.02
|
|
280
|
+
# set ModuleObj attributes from moduleDict
|
|
281
|
+
#self.data = moduleDict
|
|
282
|
+
for keys in moduleDict:
|
|
283
|
+
setattr(self, keys, moduleDict[keys])
|
|
284
|
+
|
|
285
|
+
# Run torquetube, frame, omega, cellmodule
|
|
286
|
+
if moduleDict.get('torquetube'):
|
|
287
|
+
tubeParams = moduleDict['torquetube']
|
|
288
|
+
if 'bool' in tubeParams: # backward compatible with pre-0.4
|
|
289
|
+
tubeParams['visible'] = tubeParams.pop('bool')
|
|
290
|
+
if 'torqueTubeMaterial' in tubeParams: # pre-0.4
|
|
291
|
+
tubeParams['material'] = tubeParams.pop('torqueTubeMaterial')
|
|
292
|
+
self.addTorquetube(**tubeParams, recompile=False)
|
|
293
|
+
if moduleDict.get('cellModule'):
|
|
294
|
+
self.addCellModule(**moduleDict['cellModule'], recompile=False)
|
|
295
|
+
if moduleDict.get('omegaParams'):
|
|
296
|
+
self.addOmega(**moduleDict['omegaParams'], recompile=False)
|
|
297
|
+
if moduleDict.get('frameParams'):
|
|
298
|
+
self.addFrame(**moduleDict['frameParams'], recompile=False)
|
|
299
|
+
if moduleDict.get('CECMod'):
|
|
300
|
+
self.addCEC(moduleDict['CECMod'], moduleDict['glass'])
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
return moduleDict
|
|
304
|
+
else:
|
|
305
|
+
raise Exception('Error: module name "{}" doesnt exist'.format(name))
|
|
306
|
+
return {}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _saveModule(self, savedata, json=True, rewriteModulefile=True):
|
|
310
|
+
"""
|
|
311
|
+
write out changes to module.json and make radfile if it doesn't
|
|
312
|
+
exist. if rewriteModulefile is true, always overwrite Radfile.
|
|
313
|
+
|
|
314
|
+
Parameters
|
|
315
|
+
----------
|
|
316
|
+
json : bool, default is True. Save JSON
|
|
317
|
+
rewriteModulefile : bool, default is True.
|
|
318
|
+
|
|
319
|
+
"""
|
|
320
|
+
import json as jsonmodule
|
|
321
|
+
|
|
322
|
+
if json:
|
|
323
|
+
filedir = os.path.join(DATA_PATH, 'module.json')
|
|
324
|
+
with open(filedir) as configfile:
|
|
325
|
+
data = jsonmodule.load(configfile)
|
|
326
|
+
|
|
327
|
+
data.update({self.name:savedata})
|
|
328
|
+
with open(os.path.join(DATA_PATH, 'module.json') ,'w') as configfile:
|
|
329
|
+
jsonmodule.dump(data, configfile, indent=4, sort_keys=True,
|
|
330
|
+
cls=MyEncoder)
|
|
331
|
+
|
|
332
|
+
print('Module {} updated in module.json'.format(self.name))
|
|
333
|
+
# check that self.modulefile is not none
|
|
334
|
+
if self.modulefile is None:
|
|
335
|
+
self.modulefile = os.path.join('objects',
|
|
336
|
+
self.name + '.rad')
|
|
337
|
+
|
|
338
|
+
if rewriteModulefile & os.path.isfile(self.modulefile):
|
|
339
|
+
print(f"Pre-existing .rad file {self.modulefile} "
|
|
340
|
+
"will be overwritten\n")
|
|
341
|
+
os.remove(self.modulefile)
|
|
342
|
+
|
|
343
|
+
if not os.path.isfile(self.modulefile):
|
|
344
|
+
# py2 and 3 compatible: binary write, encode text first
|
|
345
|
+
try:
|
|
346
|
+
with open(self.modulefile, 'wb') as f:
|
|
347
|
+
f.write(self.text.encode('ascii'))
|
|
348
|
+
except FileNotFoundError:
|
|
349
|
+
raise Exception(f'ModuleObj Error: directory "/{os.path.dirname(self.modulefile)}" not found '\
|
|
350
|
+
f' in current path which is {os.getcwd()}. Cannot create '\
|
|
351
|
+
f'{os.path.basename(self.modulefile)}. '\
|
|
352
|
+
'Are you in a valid bifacial_radiance directory?')
|
|
353
|
+
|
|
354
|
+
def showModule(self):
|
|
355
|
+
"""
|
|
356
|
+
Method to call objview and render the module object
|
|
357
|
+
(visualize it).
|
|
358
|
+
|
|
359
|
+
Parameters: None
|
|
360
|
+
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
cmd = 'objview %s %s' % (os.path.join('materials', 'ground.rad'),
|
|
364
|
+
self.modulefile)
|
|
365
|
+
_,err = _popen(cmd,None)
|
|
366
|
+
if err is not None:
|
|
367
|
+
print('Error: {}'.format(err))
|
|
368
|
+
print('possible solution: install radwinexe binary package from '
|
|
369
|
+
'http://www.jaloxa.eu/resources/radiance/radwinexe.shtml'
|
|
370
|
+
' into your RADIANCE binaries path')
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
def saveImage(self, filename=None):
|
|
374
|
+
"""
|
|
375
|
+
Duplicate objview process to save an image of the module in /images/
|
|
376
|
+
|
|
377
|
+
Parameters:
|
|
378
|
+
filename : string, optional. name for image file, defaults to module name
|
|
379
|
+
|
|
380
|
+
"""
|
|
381
|
+
import tempfile
|
|
382
|
+
|
|
383
|
+
temp_dir = tempfile.TemporaryDirectory()
|
|
384
|
+
pid = os.getpid()
|
|
385
|
+
if filename is None:
|
|
386
|
+
filename = f'{self.name}'
|
|
387
|
+
# fake lighting temporary .radfile
|
|
388
|
+
ltfile = os.path.join(temp_dir.name, f'lt{pid}.rad')
|
|
389
|
+
with open(ltfile, 'w') as f:
|
|
390
|
+
f.write("void glow dim 0 0 4 .1 .1 .15 0\n" +\
|
|
391
|
+
"dim source background 0 0 4 0 0 1 360\n"+\
|
|
392
|
+
"void light bright 0 0 3 1000 1000 1000\n"+\
|
|
393
|
+
"bright source sun1 0 0 4 1 .2 1 5\n"+\
|
|
394
|
+
"bright source sun2 0 0 4 .3 1 1 5\n"+\
|
|
395
|
+
"bright source sun3 0 0 4 -1 -.7 1 5")
|
|
396
|
+
|
|
397
|
+
# make .rif and run RAD
|
|
398
|
+
riffile = os.path.join(temp_dir.name, f'ov{pid}.rif')
|
|
399
|
+
with open(riffile, 'w') as f:
|
|
400
|
+
f.write("scene= materials/ground.rad " +\
|
|
401
|
+
f"{self.modulefile} {ltfile}\n".replace("\\",'/') +\
|
|
402
|
+
"EXPOSURE= .5\nUP= Z\nview= XYZ\n" +\
|
|
403
|
+
#f"OCTREE= ov{pid}.oct\n"+\
|
|
404
|
+
f"oconv= -f\nPICT= images/{filename}")
|
|
405
|
+
_,err = _popen(["rad",'-s',riffile], None)
|
|
406
|
+
if err:
|
|
407
|
+
print(err)
|
|
408
|
+
else:
|
|
409
|
+
print(f'Module image saved: images/{filename}_XYZ.hdr')
|
|
410
|
+
|
|
411
|
+
temp_dir.cleanup()
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def addTorquetube(self, diameter=0.1, tubetype='Round', material='Metal_Grey',
|
|
416
|
+
axisofrotation=True, visible=True, recompile=True):
|
|
417
|
+
"""
|
|
418
|
+
For adding torque tubes to the module simulation.
|
|
419
|
+
|
|
420
|
+
Parameters
|
|
421
|
+
----------
|
|
422
|
+
diameter : float Tube diameter in meters. For square, diameter means
|
|
423
|
+
the length of one of the square-tube side. For Hex,
|
|
424
|
+
diameter is the distance between two vertices
|
|
425
|
+
(diameter of the circumscribing circle). Default 0.1
|
|
426
|
+
tubetype : str Options: 'Square', 'Round' (default), 'Hex' or 'Oct'
|
|
427
|
+
Tube cross section
|
|
428
|
+
material : str Options: 'Metal_Grey' or 'black'. Material for the
|
|
429
|
+
torque tube.
|
|
430
|
+
axisofrotation (bool) : Default True. IF true, creates geometry
|
|
431
|
+
so center of rotation is at the center of the
|
|
432
|
+
torquetube, with an offsetfromaxis equal to half the
|
|
433
|
+
torquetube diameter + the zgap. If there is no
|
|
434
|
+
torquetube (visible=False), offsetformaxis will
|
|
435
|
+
equal the zgap.
|
|
436
|
+
visible (bool) : Default True. If false, geometry is set
|
|
437
|
+
as if the torque tube were present (e.g. zgap,
|
|
438
|
+
axisofrotation) but no geometry for the tube is made
|
|
439
|
+
recompile : Bool Rewrite .rad file and module.json file (default True)
|
|
440
|
+
|
|
441
|
+
"""
|
|
442
|
+
self.torquetube = Tube(diameter=diameter, tubetype=tubetype,
|
|
443
|
+
material=material, axisofrotation=axisofrotation,
|
|
444
|
+
visible=visible)
|
|
445
|
+
if recompile:
|
|
446
|
+
self.compileText()
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def addOmega(self, omega_material='Metal_Grey', omega_thickness=0.004,
|
|
450
|
+
inverted=False, x_omega1=None, x_omega3=None, y_omega=None,
|
|
451
|
+
mod_overlap=None, recompile=True):
|
|
452
|
+
"""
|
|
453
|
+
Add the racking structure element `omega`, which connects
|
|
454
|
+
the frame to the torque tube.
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
Parameters
|
|
458
|
+
----------
|
|
459
|
+
|
|
460
|
+
omega_material : str The material the omega structure is made of.
|
|
461
|
+
Default: 'Metal_Grey'
|
|
462
|
+
x_omega1 : float The length of the module-adjacent arm of the
|
|
463
|
+
omega parallel to the x-axis of the module
|
|
464
|
+
mod_overlap : float The length of the overlap between omega and
|
|
465
|
+
module surface on the x-direction
|
|
466
|
+
y_omega : float Length of omega (Y-direction)
|
|
467
|
+
omega_thickness : float Omega thickness. Default 0.004
|
|
468
|
+
x_omega3 : float X-direction length of the torquetube adjacent
|
|
469
|
+
arm of omega
|
|
470
|
+
inverted : Bool Modifies the way the Omega is set on the Torquetbue
|
|
471
|
+
Looks like False: u vs True: n (default False)
|
|
472
|
+
NOTE: The part that bridges the x-gap for a False
|
|
473
|
+
regular orientation omega (inverted = False),
|
|
474
|
+
is the x_omega3;
|
|
475
|
+
and for inverted omegas (inverted=True) it is
|
|
476
|
+
x_omega1.
|
|
477
|
+
recompile : Bool Rewrite .rad file and module.json file (default True)
|
|
478
|
+
|
|
479
|
+
"""
|
|
480
|
+
self.omega = Omega(module=self, omega_material=omega_material,
|
|
481
|
+
omega_thickness=omega_thickness,
|
|
482
|
+
inverted=inverted, x_omega1=x_omega1,
|
|
483
|
+
x_omega3=x_omega3, y_omega=y_omega,
|
|
484
|
+
mod_overlap=mod_overlap)
|
|
485
|
+
if recompile:
|
|
486
|
+
self.compileText()
|
|
487
|
+
|
|
488
|
+
def addFrame(self, frame_material='Metal_Grey', frame_thickness=0.05,
|
|
489
|
+
frame_z=0.3, nSides_frame=4, frame_width=0.05, recompile=True):
|
|
490
|
+
"""
|
|
491
|
+
Add a metal frame geometry around the module.
|
|
492
|
+
|
|
493
|
+
Parameters
|
|
494
|
+
------------
|
|
495
|
+
|
|
496
|
+
frame_material : str The material the frame structure is made of
|
|
497
|
+
frame_thickness : float The profile thickness of the frame
|
|
498
|
+
frame_z : float The Z-direction length of the frame that extends
|
|
499
|
+
below the module plane
|
|
500
|
+
frame_width : float The length of the bottom frame that is bolted
|
|
501
|
+
with the omega
|
|
502
|
+
nSides_frame : int The number of sides of the module that are framed.
|
|
503
|
+
4 (default) or 2
|
|
504
|
+
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
self.frame = Frame(frame_material=frame_material,
|
|
508
|
+
frame_thickness=frame_thickness,
|
|
509
|
+
frame_z=frame_z, nSides_frame=nSides_frame,
|
|
510
|
+
frame_width=frame_width)
|
|
511
|
+
if recompile:
|
|
512
|
+
self.compileText()
|
|
513
|
+
|
|
514
|
+
def addCellModule(self, numcellsx, numcellsy ,xcell, ycell,
|
|
515
|
+
xcellgap=0.02, ycellgap=0.02, centerJB=None, recompile=True):
|
|
516
|
+
"""
|
|
517
|
+
Create a cell-level module, with individually defined cells and gaps
|
|
518
|
+
|
|
519
|
+
Parameters
|
|
520
|
+
------------
|
|
521
|
+
numcellsx : int Number of cells in the X-direction within the module
|
|
522
|
+
numcellsy : int Number of cells in the Y-direction within the module
|
|
523
|
+
xcell : float Width of each cell (X-direction) in the module
|
|
524
|
+
ycell : float Length of each cell (Y-direction) in the module
|
|
525
|
+
xcellgap : float Spacing between cells in the X-direction. 0.02 default
|
|
526
|
+
ycellgap : float Spacing between cells in the Y-direction. 0.02 default
|
|
527
|
+
centerJB : float (optional) Distance betwen both sides of cell arrays
|
|
528
|
+
in a center-JB half-cell module. If 0 or not provided,
|
|
529
|
+
module will not have the center JB spacing.
|
|
530
|
+
Only implemented for 'portrait' mode at the moment.
|
|
531
|
+
(numcellsy > numcellsx).
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
"""
|
|
535
|
+
import warnings
|
|
536
|
+
if centerJB:
|
|
537
|
+
warnings.warn(
|
|
538
|
+
'centerJB functionality is currently experimental and subject '
|
|
539
|
+
'to change in future releases. ' )
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
self.cellModule = CellModule(numcellsx=numcellsx, numcellsy=numcellsy,
|
|
543
|
+
xcell=xcell, ycell=ycell, xcellgap=xcellgap,
|
|
544
|
+
ycellgap=ycellgap, centerJB=centerJB)
|
|
545
|
+
|
|
546
|
+
if recompile:
|
|
547
|
+
self.compileText()
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _makeModuleFromDict(self, x=None, y=None, z=None, xgap=None, ygap=None,
|
|
552
|
+
zgap=None, numpanels=None, modulefile=None,
|
|
553
|
+
modulematerial=None, **kwargs):
|
|
554
|
+
|
|
555
|
+
"""
|
|
556
|
+
go through and generate the text required to make a module
|
|
557
|
+
"""
|
|
558
|
+
import warnings
|
|
559
|
+
#aliases for equations below
|
|
560
|
+
Ny = numpanels
|
|
561
|
+
_cc = 0 # cc is an offset given to the module when cells are used
|
|
562
|
+
# so that the sensors don't fall in air when numcells is even.
|
|
563
|
+
# For non cell-level modules default is 0.
|
|
564
|
+
# Update values for rotating system around torque tube.
|
|
565
|
+
diam=0
|
|
566
|
+
if hasattr(self, 'torquetube'):
|
|
567
|
+
diam = self.torquetube.diameter
|
|
568
|
+
if self.torquetube.axisofrotation is True:
|
|
569
|
+
self.offsetfromaxis = np.round(zgap + diam/2.0,8)
|
|
570
|
+
if hasattr(self, 'frame'):
|
|
571
|
+
self.offsetfromaxis = self.offsetfromaxis + self.frame.frame_z
|
|
572
|
+
|
|
573
|
+
# Adding the option to replace the module thickess
|
|
574
|
+
if self.glass:
|
|
575
|
+
print("\nWarning: module glass increases analysis variability. "
|
|
576
|
+
"Recommend setting `accuracy='high'` in AnalysisObj.analysis().\n")
|
|
577
|
+
if z is None:
|
|
578
|
+
zglass = 0.01
|
|
579
|
+
z = 0.001
|
|
580
|
+
else:
|
|
581
|
+
zglass = z
|
|
582
|
+
z = 0.001
|
|
583
|
+
|
|
584
|
+
else: # no glass
|
|
585
|
+
zglass = 0.0
|
|
586
|
+
if z is None:
|
|
587
|
+
z = 0.020
|
|
588
|
+
|
|
589
|
+
self.z = z
|
|
590
|
+
self.zglass = zglass
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
if modulematerial is None:
|
|
594
|
+
modulematerial = 'black'
|
|
595
|
+
self.modulematerial = 'black'
|
|
596
|
+
|
|
597
|
+
if self._manual_text is not None:
|
|
598
|
+
text = self._manual_text
|
|
599
|
+
self._manual_text = None
|
|
600
|
+
else:
|
|
601
|
+
|
|
602
|
+
if hasattr(self, 'cellModule'):
|
|
603
|
+
(text, x, y, _cc) = self.cellModule._makeCellLevelModule(self, z, Ny, ygap,
|
|
604
|
+
modulematerial)
|
|
605
|
+
else:
|
|
606
|
+
try:
|
|
607
|
+
text = '! genbox {} {} {} {} {} '.format(modulematerial,
|
|
608
|
+
self.name, x, y, z)
|
|
609
|
+
text +='| xform -t {} {} {} '.format(np.round(-x/2.0,6),
|
|
610
|
+
np.round((-y*Ny/2.0)-(ygap*(Ny-1)/2.0),6),
|
|
611
|
+
self.offsetfromaxis)
|
|
612
|
+
text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)
|
|
613
|
+
packagingfactor = 100.0
|
|
614
|
+
|
|
615
|
+
except Exception as err: # probably because no x or y passed
|
|
616
|
+
raise Exception('makeModule variable {}'.format(err.args[0])+
|
|
617
|
+
' and cellModule is None. '+
|
|
618
|
+
'One or the other must be specified.')
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
self.scenex = np.round(x + xgap, 6)
|
|
622
|
+
self.sceney = np.round(y*numpanels + ygap*(numpanels-1), 8)
|
|
623
|
+
self.scenez = np.round(zgap + diam / 2.0, 6)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
if hasattr(self, 'frame'):
|
|
627
|
+
_zinc, frametext = self.frame._makeFrames(
|
|
628
|
+
x=x,y=y, ygap=ygap,numpanels=Ny,
|
|
629
|
+
offsetfromaxis=self.offsetfromaxis- 0.5*zglass)
|
|
630
|
+
else:
|
|
631
|
+
frametext = ''
|
|
632
|
+
_zinc = 0 # z increment from frame thickness
|
|
633
|
+
_zinc = _zinc + 0.5 * zglass
|
|
634
|
+
|
|
635
|
+
if hasattr(self, 'omega'):
|
|
636
|
+
# This also defines scenex for length of the torquetube.
|
|
637
|
+
omega2omega_x, omegatext = self.omega._makeOmega(x=x,y=y, xgap=xgap,
|
|
638
|
+
zgap=zgap, z_inc=_zinc,
|
|
639
|
+
offsetfromaxis=self.offsetfromaxis)
|
|
640
|
+
if omega2omega_x > self.scenex:
|
|
641
|
+
self.scenex = omega2omega_x
|
|
642
|
+
else:
|
|
643
|
+
omegatext = ''
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
#if torquetube_bool is True:
|
|
647
|
+
if hasattr(self,'torquetube'):
|
|
648
|
+
if self.torquetube.visible:
|
|
649
|
+
text += self.torquetube._makeTorqueTube(cc=_cc, zgap=zgap,
|
|
650
|
+
z_inc=_zinc, scenex=self.scenex)
|
|
651
|
+
|
|
652
|
+
if self.glass:
|
|
653
|
+
if hasattr(self,'glassEdge'):
|
|
654
|
+
glassEdge = self.glassEdge
|
|
655
|
+
else:
|
|
656
|
+
glassEdge = 0.01
|
|
657
|
+
self.glassEdge = glassEdge
|
|
658
|
+
|
|
659
|
+
text = text+'\r\n! genbox stock_glass {} {} {} {} '.format(self.name+'_Glass',x+glassEdge, y+glassEdge, zglass)
|
|
660
|
+
text +='| xform -t {} {} {} '.format(round(-x/2.0-0.5*glassEdge + _cc, 6),
|
|
661
|
+
round((-y*Ny/2.0)-(ygap*(Ny-1)/2.0)-0.5*glassEdge, 6),
|
|
662
|
+
round(self.offsetfromaxis - 0.5*zglass, 6) )
|
|
663
|
+
text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
text += frametext
|
|
667
|
+
if hasattr(self, 'omega'):
|
|
668
|
+
text += self.omega.text
|
|
669
|
+
|
|
670
|
+
if self.customtext != "":
|
|
671
|
+
text += '\n' + self.customtext # For adding any other racking details at the module level that the user might want.
|
|
672
|
+
|
|
673
|
+
if self.customObject != "":
|
|
674
|
+
text += '\n!xform -rz 0 ' + self.customObject # For adding a specific .rad file
|
|
675
|
+
|
|
676
|
+
self.text = text
|
|
677
|
+
return text
|
|
678
|
+
#End of makeModuleFromDict()
|
|
679
|
+
|
|
680
|
+
def addCEC(self, CECMod, glassglass=None, bifi=None):
|
|
681
|
+
"""
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
Parameters
|
|
685
|
+
----------
|
|
686
|
+
CECMod : Dictionary or pandas.DataFrame including:
|
|
687
|
+
alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, Adjust
|
|
688
|
+
glassglass : Bool, optional. Create a glass-glass module w 5mm glass on each side
|
|
689
|
+
bifi : Float, bifaciality coefficient < 1
|
|
690
|
+
|
|
691
|
+
"""
|
|
692
|
+
keys = ['alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s', 'Adjust']
|
|
693
|
+
|
|
694
|
+
if glassglass is not None:
|
|
695
|
+
self.glassglass = glassglass
|
|
696
|
+
if bifi:
|
|
697
|
+
self.bifi = bifi
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
if type(CECMod) == pd.DataFrame:
|
|
701
|
+
# Check for attributes along the index and transpose
|
|
702
|
+
if 'alpha_sc' in CECMod.index:
|
|
703
|
+
CECMod = CECMod.T
|
|
704
|
+
if len(CECMod) > 1:
|
|
705
|
+
print('Warning: DataFrame with multiple rows passed to module.addCEC. '\
|
|
706
|
+
'Taking the first entry')
|
|
707
|
+
CECModDict = CECMod.iloc[0].to_dict()
|
|
708
|
+
try:
|
|
709
|
+
CECModDict['name'] = CECMod.iloc[0].name
|
|
710
|
+
except AttributeError:
|
|
711
|
+
CECModDict['name'] = None
|
|
712
|
+
elif type(CECMod) == pd.Series:
|
|
713
|
+
CECModDict = CECMod.to_dict()
|
|
714
|
+
elif type(CECMod) == dict:
|
|
715
|
+
CECModDict = CECMod
|
|
716
|
+
elif type(CECMod) == str:
|
|
717
|
+
raise Exception('Error: string-based module selection is not yet enabled. '\
|
|
718
|
+
'Try back later!')
|
|
719
|
+
return
|
|
720
|
+
elif CECMod is None:
|
|
721
|
+
self.CECMod = None
|
|
722
|
+
return
|
|
723
|
+
else:
|
|
724
|
+
raise Exception(f"Unrecognized type '{type(CECMod)}' passed into addCEC ")
|
|
725
|
+
|
|
726
|
+
for key in keys:
|
|
727
|
+
if key not in CECModDict:
|
|
728
|
+
raise KeyError(f"Error: required key '{key}' not passed into module.addCEC")
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
self.CECMod = CECModule(**CECModDict)
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def calculatePerformance(self, effective_irradiance, CECMod=None,
|
|
736
|
+
temp_air=None, wind_speed=1, temp_cell=None, glassglass=None):
|
|
737
|
+
'''
|
|
738
|
+
The module parameters are given at the reference condition.
|
|
739
|
+
Use pvlib.pvsystem.calcparams_cec() to generate the five SDM
|
|
740
|
+
parameters at your desired irradiance and temperature to use
|
|
741
|
+
with pvlib.pvsystem.singlediode() to calculate the IV curve information.:
|
|
742
|
+
|
|
743
|
+
Inputs
|
|
744
|
+
------
|
|
745
|
+
effective_irradiance : numeric
|
|
746
|
+
Dataframe or single value. Must be same length as temp_cell
|
|
747
|
+
CECMod : Dict
|
|
748
|
+
Dictionary with CEC Module PArameters for the module selected. Must
|
|
749
|
+
contain at minimum alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref,
|
|
750
|
+
R_s, Adjust
|
|
751
|
+
temp_air : numeric
|
|
752
|
+
Ambient temperature in Celsius. Dataframe or single value to calculate.
|
|
753
|
+
Must be same length as effective_irradiance. Default = 20C
|
|
754
|
+
wind_speed : numeric
|
|
755
|
+
Wind speed at a height of 10 meters [m/s]. Default = 1 m/s
|
|
756
|
+
temp_cell : numeric
|
|
757
|
+
Back of module temperature. If provided, overrides temp_air and
|
|
758
|
+
wind_speed calculation. Default = None
|
|
759
|
+
glassglass : boolean
|
|
760
|
+
If module is glass-glass package (vs glass-polymer) to select correct
|
|
761
|
+
thermal coefficients for module temperature calculation
|
|
762
|
+
|
|
763
|
+
'''
|
|
764
|
+
|
|
765
|
+
if CECMod is None:
|
|
766
|
+
if getattr(self, 'CECMod', None) is not None:
|
|
767
|
+
CECMod = self.CECMod
|
|
768
|
+
else:
|
|
769
|
+
print("No CECModule data passed; using default for Prism Solar BHC72-400")
|
|
770
|
+
#url = 'https://raw.githubusercontent.com/NREL/SAM/patch/deploy/libraries/CEC%20Modules.csv'
|
|
771
|
+
url = os.path.join(DATA_PATH,'CEC Modules.csv')
|
|
772
|
+
db = pd.read_csv(url, index_col=0) # Reading this might take 1 min or so, the database is big.
|
|
773
|
+
modfilter2 = db.index.str.startswith('Pr') & db.index.str.endswith('BHC72-400')
|
|
774
|
+
CECMod = db[modfilter2]
|
|
775
|
+
self.addCEC(CECMod)
|
|
776
|
+
|
|
777
|
+
if hasattr(self, 'glassglass') and glassglass is None:
|
|
778
|
+
glassglass = self.glassglass
|
|
779
|
+
|
|
780
|
+
from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS
|
|
781
|
+
|
|
782
|
+
# Setting temperature_model_parameters
|
|
783
|
+
if glassglass:
|
|
784
|
+
temp_model_params = (
|
|
785
|
+
TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_glass'])
|
|
786
|
+
else:
|
|
787
|
+
temp_model_params = (
|
|
788
|
+
TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_polymer'])
|
|
789
|
+
|
|
790
|
+
if temp_cell is None:
|
|
791
|
+
if temp_air is None:
|
|
792
|
+
temp_air = 25 # STC
|
|
793
|
+
|
|
794
|
+
temp_cell = pvlib.temperature.sapm_cell(effective_irradiance, temp_air,
|
|
795
|
+
wind_speed,
|
|
796
|
+
temp_model_params['a'],
|
|
797
|
+
temp_model_params['b'],
|
|
798
|
+
temp_model_params['deltaT'])
|
|
799
|
+
|
|
800
|
+
if isinstance(CECMod, pd.DataFrame):
|
|
801
|
+
#CECMod.to_pickle("CECMod.pkl")
|
|
802
|
+
if len(CECMod) == 1:
|
|
803
|
+
CECMod = CECMod.iloc[0]
|
|
804
|
+
else:
|
|
805
|
+
print("More than one Module passed. Error, using 1st one")
|
|
806
|
+
CECMod = CECMod.iloc[0]
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
IL, I0, Rs, Rsh, nNsVth = pvlib.pvsystem.calcparams_cec(
|
|
810
|
+
effective_irradiance=effective_irradiance,
|
|
811
|
+
temp_cell=temp_cell,
|
|
812
|
+
alpha_sc=float(CECMod.alpha_sc),
|
|
813
|
+
a_ref=float(CECMod.a_ref),
|
|
814
|
+
I_L_ref=float(CECMod.I_L_ref),
|
|
815
|
+
I_o_ref=float(CECMod.I_o_ref),
|
|
816
|
+
R_sh_ref=float(CECMod.R_sh_ref),
|
|
817
|
+
R_s=float(CECMod.R_s),
|
|
818
|
+
Adjust=float(CECMod.Adjust)
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
IVcurve_info = pvlib.pvsystem.singlediode(
|
|
822
|
+
photocurrent=IL,
|
|
823
|
+
saturation_current=I0,
|
|
824
|
+
resistance_series=Rs,
|
|
825
|
+
resistance_shunt=Rsh,
|
|
826
|
+
nNsVth=nNsVth
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
return IVcurve_info['p_mp']
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
# end of ModuleObj
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
class Omega(SuperClass):
|
|
837
|
+
|
|
838
|
+
def __init__(self, module, omega_material='Metal_Grey', omega_thickness=0.004,
|
|
839
|
+
inverted=False, x_omega1=None, x_omega3=None, y_omega=None,
|
|
840
|
+
mod_overlap=None):
|
|
841
|
+
"""
|
|
842
|
+
==================== ===============================================
|
|
843
|
+
Keys : type Description
|
|
844
|
+
================ ===============================================
|
|
845
|
+
module : ModuleObj Parent object with details related to geometry
|
|
846
|
+
omega_material : str The material the omega structure is made of
|
|
847
|
+
omega_thickness : float Omega thickness
|
|
848
|
+
inverted : Bool Modifies the way the Omega is set on the Torquetbue
|
|
849
|
+
Looks like False: u vs True: n (default False)
|
|
850
|
+
x_omega1 : float The length of the module-adjacent arm of the
|
|
851
|
+
omega parallel to the x-axis of the module
|
|
852
|
+
y_omega : float Length of omega (Y-direction)
|
|
853
|
+
x_omega3 : float X-direction length of the torquetube adjacent
|
|
854
|
+
arm of omega
|
|
855
|
+
mod_overlap : float The length of the overlap between omega and
|
|
856
|
+
module surface on the x-direction
|
|
857
|
+
|
|
858
|
+
===================== ===============================================
|
|
859
|
+
|
|
860
|
+
"""
|
|
861
|
+
self.keys = ['omega_material', 'x_omega1', 'mod_overlap', 'y_omega',
|
|
862
|
+
'omega_thickness','x_omega3','inverted']
|
|
863
|
+
|
|
864
|
+
if x_omega1 is None:
|
|
865
|
+
if inverted:
|
|
866
|
+
x_omega1 = module.xgap*0.5
|
|
867
|
+
else:
|
|
868
|
+
x_omega1 = module.xgap*0.5*0.6
|
|
869
|
+
_missingKeyWarning('Omega', 'x_omega1', x_omega1)
|
|
870
|
+
|
|
871
|
+
if x_omega3 is None:
|
|
872
|
+
if inverted:
|
|
873
|
+
x_omega3 = module.xgap*0.5*0.3
|
|
874
|
+
else:
|
|
875
|
+
x_omega3 = module.xgap*0.5
|
|
876
|
+
_missingKeyWarning('Omega', 'x_omega3', x_omega3)
|
|
877
|
+
|
|
878
|
+
if y_omega is None:
|
|
879
|
+
y_omega = module.y/2
|
|
880
|
+
_missingKeyWarning('Omega', 'y_omega', y_omega)
|
|
881
|
+
|
|
882
|
+
if mod_overlap is None:
|
|
883
|
+
mod_overlap = x_omega1*0.6
|
|
884
|
+
_missingKeyWarning('Omega', 'mod_overlap', mod_overlap)
|
|
885
|
+
|
|
886
|
+
# set data object attributes from datakey list.
|
|
887
|
+
for key in self.keys:
|
|
888
|
+
setattr(self, key, eval(key))
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
def _makeOmega(self, x, y, xgap, zgap, offsetfromaxis, z_inc = 0, **kwargs):
|
|
893
|
+
"""
|
|
894
|
+
Helper function for creating a module that includes the racking
|
|
895
|
+
structure element `omega`.
|
|
896
|
+
|
|
897
|
+
TODO: remove some or all of this documentation since this is an internal function
|
|
898
|
+
|
|
899
|
+
Parameters
|
|
900
|
+
------------
|
|
901
|
+
x : numeric
|
|
902
|
+
Width of module along the axis of the torque tube or racking structure. (meters).
|
|
903
|
+
y : numeric
|
|
904
|
+
Length of module (meters)
|
|
905
|
+
xgap : float
|
|
906
|
+
Panel space in X direction. Separation between modules in a row.
|
|
907
|
+
zgap : float
|
|
908
|
+
Distance behind the modules in the z-direction to the edge of the tube (m)
|
|
909
|
+
offsetfromaxis : float
|
|
910
|
+
Internally defined variable in makeModule that specifies how much
|
|
911
|
+
the module is offset from the Axis of Rotation due to zgap and or
|
|
912
|
+
frame thickness.
|
|
913
|
+
z_inc : dict
|
|
914
|
+
Internally defined variable in makeModule that specifies how much
|
|
915
|
+
the module is offseted by the Frame.
|
|
916
|
+
|
|
917
|
+
"""
|
|
918
|
+
|
|
919
|
+
# set local variables
|
|
920
|
+
omega_material = self.omega_material
|
|
921
|
+
x_omega1 = self.x_omega1
|
|
922
|
+
mod_overlap = self.mod_overlap
|
|
923
|
+
y_omega = self.y_omega
|
|
924
|
+
omega_thickness = self.omega_thickness
|
|
925
|
+
x_omega3 = self.x_omega3
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
z_omega2 = zgap
|
|
929
|
+
x_omega2 = omega_thickness
|
|
930
|
+
z_omega1 = omega_thickness
|
|
931
|
+
z_omega3 = omega_thickness
|
|
932
|
+
|
|
933
|
+
#naming the omega pieces
|
|
934
|
+
name1 = 'mod_adj'
|
|
935
|
+
name2 = 'verti'
|
|
936
|
+
name3 = 'tt_adj'
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
# defining the module adjacent member of omega
|
|
940
|
+
x_translate1 = -x/2 - x_omega1 + mod_overlap
|
|
941
|
+
y_translate = -y_omega/2 #common for all the pieces
|
|
942
|
+
z_translate1 = round(offsetfromaxis-z_omega1, 6)
|
|
943
|
+
|
|
944
|
+
#defining the vertical (zgap) member of the omega
|
|
945
|
+
x_translate2 = x_translate1
|
|
946
|
+
z_translate2 = round(offsetfromaxis-z_omega2, 6)
|
|
947
|
+
|
|
948
|
+
#defining the torquetube adjacent member of omega
|
|
949
|
+
x_translate3 = round(x_translate1-x_omega3, 6)
|
|
950
|
+
z_translate3 = z_translate2
|
|
951
|
+
|
|
952
|
+
if z_inc != 0:
|
|
953
|
+
z_translate1 = round(z_translate1 - z_inc, 6)
|
|
954
|
+
z_translate2 = round(z_translate2 - z_inc, 6)
|
|
955
|
+
z_translate3 = round(z_translate3 - z_inc, 6)
|
|
956
|
+
|
|
957
|
+
# for this code, only the translations need to be shifted for the inverted omega
|
|
958
|
+
|
|
959
|
+
if self.inverted == True:
|
|
960
|
+
# shifting the non-inv omega shape of west as inv omega shape of east
|
|
961
|
+
x_translate1_inv_east = x/2-mod_overlap
|
|
962
|
+
x_shift_east = x_translate1_inv_east - x_translate1
|
|
963
|
+
|
|
964
|
+
# shifting the non-inv omega shape of west as inv omega shape of east
|
|
965
|
+
x_translate1_inv_west = -x_translate1_inv_east - x_omega1
|
|
966
|
+
x_shift_west = -x_translate1_inv_west + (-x_translate1-x_omega1)
|
|
967
|
+
|
|
968
|
+
#customizing the East side of the module for omega_inverted
|
|
969
|
+
|
|
970
|
+
omegatext = '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, x_translate1_inv_east, y_translate, z_translate1)
|
|
971
|
+
omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, x_translate2 + x_shift_east, y_translate, z_translate2)
|
|
972
|
+
omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, x_translate3 + x_shift_east, y_translate, z_translate3)
|
|
973
|
+
|
|
974
|
+
#customizing the West side of the module for omega_inverted
|
|
975
|
+
|
|
976
|
+
omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, x_translate1_inv_west, y_translate, z_translate1)
|
|
977
|
+
omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, -x_translate2-x_omega2 -x_shift_west, y_translate, z_translate2)
|
|
978
|
+
omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, -x_translate3-x_omega3 - x_shift_west, y_translate, z_translate3)
|
|
979
|
+
|
|
980
|
+
omega2omega_x = round(-x_translate1_inv_east*2,6)
|
|
981
|
+
|
|
982
|
+
else:
|
|
983
|
+
|
|
984
|
+
#customizing the West side of the module for omega
|
|
985
|
+
|
|
986
|
+
omegatext = '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, x_translate1, y_translate, z_translate1)
|
|
987
|
+
omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, x_translate2, y_translate, z_translate2)
|
|
988
|
+
omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, x_translate3, y_translate, z_translate3)
|
|
989
|
+
|
|
990
|
+
#customizing the East side of the module for omega
|
|
991
|
+
|
|
992
|
+
omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name1, x_omega1, y_omega, z_omega1, -x_translate1-x_omega1, y_translate, z_translate1)
|
|
993
|
+
omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name2, x_omega2, y_omega, z_omega2, -x_translate2-x_omega2, y_translate, z_translate2)
|
|
994
|
+
omegatext += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(omega_material, name3, x_omega3, y_omega, z_omega3, round(-x_translate3-x_omega3,6), y_translate, z_translate3)
|
|
995
|
+
|
|
996
|
+
omega2omega_x = round(-x_translate3*2,6)
|
|
997
|
+
self.text = omegatext
|
|
998
|
+
self.omega2omega_x = omega2omega_x
|
|
999
|
+
return omega2omega_x,omegatext
|
|
1000
|
+
|
|
1001
|
+
class Frame(SuperClass):
|
|
1002
|
+
|
|
1003
|
+
def __init__(self, frame_material='Metal_Grey', frame_thickness=0.05,
|
|
1004
|
+
frame_z=None, nSides_frame=4, frame_width=0.05):
|
|
1005
|
+
"""
|
|
1006
|
+
Parameters
|
|
1007
|
+
------------
|
|
1008
|
+
|
|
1009
|
+
frame_material : str The material the frame structure is made of
|
|
1010
|
+
frame_thickness : float The profile thickness of the frame
|
|
1011
|
+
frame_z : float The Z-direction length of the frame that extends
|
|
1012
|
+
below the module plane
|
|
1013
|
+
frame_width : float The length of the bottom frame that is bolted
|
|
1014
|
+
with the omega
|
|
1015
|
+
nSides_frame : int The number of sides of the module that are framed.
|
|
1016
|
+
4 (default) or 2
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
"""
|
|
1020
|
+
self.keys = ['frame_material', 'frame_thickness', 'frame_z', 'frame_width',
|
|
1021
|
+
'nSides_frame']
|
|
1022
|
+
|
|
1023
|
+
if frame_z is None:
|
|
1024
|
+
frame_z = 0.03
|
|
1025
|
+
_missingKeyWarning('Frame', 'frame_z', frame_z)
|
|
1026
|
+
|
|
1027
|
+
# set data object attributes from datakey list.
|
|
1028
|
+
for key in self.keys:
|
|
1029
|
+
setattr(self, key, eval(key))
|
|
1030
|
+
|
|
1031
|
+
def _makeFrames(self, x, y, ygap, numpanels, offsetfromaxis):
|
|
1032
|
+
"""
|
|
1033
|
+
Helper function for creating a module that includes the frames attached to the module,
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
Parameters
|
|
1037
|
+
------------
|
|
1038
|
+
frameParams : dict
|
|
1039
|
+
Dictionary with input parameters for creating a frame as part of the module.
|
|
1040
|
+
See details below for keys needed.
|
|
1041
|
+
x : numeric
|
|
1042
|
+
Width of module along the axis of the torque tube or racking structure. (meters).
|
|
1043
|
+
y : numeric
|
|
1044
|
+
Length of module (meters)
|
|
1045
|
+
ygap : float
|
|
1046
|
+
Gap between modules arrayed in the Y-direction if any.
|
|
1047
|
+
numpanels : int
|
|
1048
|
+
Number of modules arrayed in the Y-direction. e.g.
|
|
1049
|
+
1-up or 2-up, etc. (supports any number for carport/Mesa simulations)
|
|
1050
|
+
offsetfromaxis : float
|
|
1051
|
+
Internally defined variable in makeModule that specifies how much
|
|
1052
|
+
the module is offset from the Axis of Rotation due to zgap and or
|
|
1053
|
+
frame thickness.
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
"""
|
|
1057
|
+
|
|
1058
|
+
#
|
|
1059
|
+
if self.nSides_frame == 2 and x>y:
|
|
1060
|
+
print("Development Warning: Frames has only 2 sides and module is"+
|
|
1061
|
+
"in ladscape. This functionality is not working properly yet"+
|
|
1062
|
+
"for this release. We are overwriting nSide_frame = 4 to continue."+
|
|
1063
|
+
"If this functionality is pivotal to you we can prioritize adding it but"+
|
|
1064
|
+
"please comunicate with the development team. Thank you.")
|
|
1065
|
+
self.nSides_frame = 4
|
|
1066
|
+
|
|
1067
|
+
#Defining internal names
|
|
1068
|
+
frame_material = self.frame_material
|
|
1069
|
+
f_thickness = self.frame_thickness
|
|
1070
|
+
f_height = self.frame_z
|
|
1071
|
+
n_frame = self.nSides_frame
|
|
1072
|
+
fl_x = self.frame_width
|
|
1073
|
+
|
|
1074
|
+
y_trans_shift = 0 #pertinent to the case of x>y with 2-sided frame
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
# Recalculating width ignoring the thickness of the aluminum
|
|
1078
|
+
# for internal positioining and sizing of hte pieces
|
|
1079
|
+
fl_x = fl_x-f_thickness
|
|
1080
|
+
|
|
1081
|
+
if x>y and n_frame==2:
|
|
1082
|
+
x_temp,y_temp = y,x
|
|
1083
|
+
rotframe = 90
|
|
1084
|
+
frame_y = x
|
|
1085
|
+
y_trans_shift = x/2-y/2
|
|
1086
|
+
else:
|
|
1087
|
+
x_temp,y_temp = x,y
|
|
1088
|
+
frame_y = y
|
|
1089
|
+
rotframe = 0
|
|
1090
|
+
|
|
1091
|
+
Ny = numpanels
|
|
1092
|
+
y_half = (y*Ny/2)+(ygap*(Ny-1)/2)
|
|
1093
|
+
|
|
1094
|
+
# taking care of lengths and translation points
|
|
1095
|
+
# The pieces are same and symmetrical for west and east
|
|
1096
|
+
|
|
1097
|
+
# naming the frame pieces
|
|
1098
|
+
nameframe1 = 'frameside'
|
|
1099
|
+
nameframe2 = 'frameleg'
|
|
1100
|
+
|
|
1101
|
+
#frame sides
|
|
1102
|
+
few_x = f_thickness
|
|
1103
|
+
few_y = frame_y
|
|
1104
|
+
few_z = f_height
|
|
1105
|
+
|
|
1106
|
+
fw_xt = -x_temp/2 # in case of x_temp = y this doesn't reach panel edge
|
|
1107
|
+
fe_xt = x_temp/2-f_thickness
|
|
1108
|
+
few_yt = -y_half-y_trans_shift
|
|
1109
|
+
few_zt = offsetfromaxis-f_height
|
|
1110
|
+
|
|
1111
|
+
#frame legs for east-west
|
|
1112
|
+
|
|
1113
|
+
flw_xt = -x_temp/2 + f_thickness
|
|
1114
|
+
fle_xt = round(x_temp/2 - f_thickness-fl_x,6)
|
|
1115
|
+
flew_yt = -y_half-y_trans_shift
|
|
1116
|
+
flew_zt = offsetfromaxis-f_height
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
#pieces for the shorter side (north-south in this case)
|
|
1120
|
+
|
|
1121
|
+
#filler
|
|
1122
|
+
|
|
1123
|
+
fns_x = x_temp-2*f_thickness
|
|
1124
|
+
fns_y = f_thickness
|
|
1125
|
+
fns_z = f_height-f_thickness
|
|
1126
|
+
|
|
1127
|
+
fns_xt = -x_temp/2+f_thickness
|
|
1128
|
+
fn_yt = round(-y_half+y-f_thickness,6)
|
|
1129
|
+
fs_yt = -y_half
|
|
1130
|
+
fns_zt = offsetfromaxis-f_height+f_thickness
|
|
1131
|
+
|
|
1132
|
+
# the filler legs
|
|
1133
|
+
|
|
1134
|
+
filleg_x = round(x_temp-2*f_thickness-2*fl_x, 6)
|
|
1135
|
+
filleg_y = f_thickness + fl_x
|
|
1136
|
+
filleg_z = f_thickness
|
|
1137
|
+
|
|
1138
|
+
filleg_xt = round(-x_temp/2+f_thickness+fl_x, 6)
|
|
1139
|
+
fillegn_yt = round(-y_half+y-f_thickness-fl_x, 6)
|
|
1140
|
+
fillegs_yt = -y_half
|
|
1141
|
+
filleg_zt = offsetfromaxis-f_height
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
# making frames: west side
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
frame_text = '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, few_x, few_y, few_z, fw_xt, few_yt, few_zt)
|
|
1148
|
+
frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
|
|
1149
|
+
|
|
1150
|
+
frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, fl_x, frame_y, f_thickness, flw_xt, flew_yt, flew_zt)
|
|
1151
|
+
frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
|
|
1152
|
+
|
|
1153
|
+
# making frames: east side
|
|
1154
|
+
|
|
1155
|
+
frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, few_x, few_y, few_z, fe_xt, few_yt, few_zt)
|
|
1156
|
+
frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
|
|
1157
|
+
|
|
1158
|
+
frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, fl_x, frame_y, f_thickness, fle_xt, flew_yt, flew_zt)
|
|
1159
|
+
frame_text += ' -a {} -t 0 {} 0 | xform -rz {}'.format(Ny, y_temp+ygap, rotframe)
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
if n_frame == 4:
|
|
1163
|
+
#making frames: north side
|
|
1164
|
+
|
|
1165
|
+
frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, fns_x, fns_y, fns_z, fns_xt, fn_yt, fns_zt)
|
|
1166
|
+
frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, filleg_x, filleg_y, filleg_z, filleg_xt, fillegn_yt, filleg_zt)
|
|
1170
|
+
frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
|
|
1171
|
+
|
|
1172
|
+
#making frames: south side
|
|
1173
|
+
|
|
1174
|
+
frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe1, fns_x, fns_y, fns_z, fns_xt, fs_yt, fns_zt)
|
|
1175
|
+
frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
|
|
1176
|
+
|
|
1177
|
+
frame_text += '\r\n! genbox {} {} {} {} {} | xform -t {} {} {}'.format(frame_material, nameframe2, filleg_x, filleg_y, filleg_z, filleg_xt, fillegs_yt, filleg_zt)
|
|
1178
|
+
frame_text += ' -a {} -t 0 {} 0'.format(Ny, y+ygap)
|
|
1179
|
+
|
|
1180
|
+
z_inc = f_height
|
|
1181
|
+
|
|
1182
|
+
return z_inc, frame_text
|
|
1183
|
+
|
|
1184
|
+
class Tube(SuperClass):
|
|
1185
|
+
|
|
1186
|
+
def __init__(self, diameter=0.1, tubetype='Round', material='Metal_Grey',
|
|
1187
|
+
axisofrotation=True, visible=True):
|
|
1188
|
+
"""
|
|
1189
|
+
================ ====================================================
|
|
1190
|
+
Keys : type Description
|
|
1191
|
+
================ ====================================================
|
|
1192
|
+
diameter : float Tube diameter in meters. For square, diameter means
|
|
1193
|
+
the length of one of the square-tube side. For Hex,
|
|
1194
|
+
diameter is the distance between two vertices
|
|
1195
|
+
(diameter of the circumscribing circle). Default 0.1
|
|
1196
|
+
tubetype : str Options: 'Square', 'Round' (default), 'Hex' or 'Oct'
|
|
1197
|
+
Tube cross section
|
|
1198
|
+
material : str Options: 'Metal_Grey' or 'black'. Material for the
|
|
1199
|
+
torque tube.
|
|
1200
|
+
axisofrotation (bool) : Default True. IF true, creates geometry
|
|
1201
|
+
so center of rotation is at the center of the
|
|
1202
|
+
torquetube, with an offsetfromaxis equal to half the
|
|
1203
|
+
torquetube diameter + the zgap. If there is no
|
|
1204
|
+
torquetube (visible=False), offsetformaxis will
|
|
1205
|
+
equal the zgap.
|
|
1206
|
+
visible (bool) : Default True. If false, geometry is set
|
|
1207
|
+
as if the torque tube were present (e.g. zgap,
|
|
1208
|
+
axisofrotation) but no geometry for the tube is made
|
|
1209
|
+
================ ====================================================
|
|
1210
|
+
"""
|
|
1211
|
+
|
|
1212
|
+
self.keys = ['diameter', 'tubetype', 'material', 'visible'] # what about axisofrotation?
|
|
1213
|
+
|
|
1214
|
+
self.axisofrotation = axisofrotation
|
|
1215
|
+
# set data object attributes from datakey list.
|
|
1216
|
+
for key in self.keys:
|
|
1217
|
+
setattr(self, key, eval(key))
|
|
1218
|
+
|
|
1219
|
+
def _makeTorqueTube(self, cc, z_inc, zgap, scenex):
|
|
1220
|
+
"""
|
|
1221
|
+
Return text string for generating the torque tube geometry
|
|
1222
|
+
|
|
1223
|
+
Parameters
|
|
1224
|
+
|
|
1225
|
+
cc = module._cc #horizontal offset to center of a cell
|
|
1226
|
+
"""
|
|
1227
|
+
import math
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
text = ''
|
|
1231
|
+
tto = 0 # Torquetube Offset. Default = 0 if axisofrotationTT == True
|
|
1232
|
+
diam = self.diameter #alias
|
|
1233
|
+
material = self.material #alias
|
|
1234
|
+
|
|
1235
|
+
if self.tubetype.lower() == 'square':
|
|
1236
|
+
if self.axisofrotation == False:
|
|
1237
|
+
tto = -z_inc-zgap-diam/2.0
|
|
1238
|
+
text += '\r\n! genbox {} tube1 {} {} {} '.format(material,
|
|
1239
|
+
scenex, diam, diam)
|
|
1240
|
+
text += '| xform -t {} {} {}'.format(round(-(scenex)/2.0+cc, 6),
|
|
1241
|
+
round(-diam/2.0, 6), round( -diam/2.0+tto, 6))
|
|
1242
|
+
|
|
1243
|
+
elif self.tubetype.lower() == 'round':
|
|
1244
|
+
if self.axisofrotation == False:
|
|
1245
|
+
tto = -z_inc-zgap-diam/2.0
|
|
1246
|
+
text += '\r\n! genrev {} tube1 t*{} {} '.format(material, scenex, round(diam/2.0, 6))
|
|
1247
|
+
text += '32 | xform -ry 90 -t {} {} {}'.format(round(-(scenex)/2.0+cc, 6), 0, tto)
|
|
1248
|
+
|
|
1249
|
+
elif self.tubetype.lower() == 'hex':
|
|
1250
|
+
radius = 0.5*diam
|
|
1251
|
+
|
|
1252
|
+
if self.axisofrotation == False:
|
|
1253
|
+
tto = -z_inc-radius*math.sqrt(3.0)/2.0-zgap
|
|
1254
|
+
|
|
1255
|
+
text += '\r\n! genbox {} hextube1a {} {} {} | xform -t {} {} {}'.format(
|
|
1256
|
+
material, scenex, radius, round(radius*math.sqrt(3), 6),
|
|
1257
|
+
round(-(scenex)/2.0+cc, 6), round(-radius/2.0, 6), round(-radius*math.sqrt(3.0)/2.0+tto, 6)) #ztran -radius*math.sqrt(3.0)-tto
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
# Create, translate to center, rotate, translate back to prev. position and translate to overal module position.
|
|
1261
|
+
text = text+'\r\n! genbox {} hextube1b {} {} {} | xform -t {} {} {} -rx 60 -t 0 0 {}'.format(
|
|
1262
|
+
material, scenex, radius, np.round(radius*math.sqrt(3), 6), np.round(-(scenex)/2.0+cc, 6),
|
|
1263
|
+
round(-radius/2.0, 6), np.round(radius*math.sqrt(3.0)/2.0, 6), tto) #ztran (radius*math.sqrt(3.0)/2.0)-radius*math.sqrt(3.0)-tto)
|
|
1264
|
+
|
|
1265
|
+
text = text+'\r\n! genbox {} hextube1c {} {} {} | xform -t {} {} {} -rx -60 -t 0 0 {}'.format(
|
|
1266
|
+
material, scenex, radius, round(radius*math.sqrt(3), 6),
|
|
1267
|
+
round(-(scenex)/2.0+cc, 6), round( -radius/2.0, 6), round(-radius*math.sqrt(3.0)/2, 6), tto) #ztran (radius*math.sqrt(3.0)/2.0)-radius*math.sqrt(3.0)-tto)
|
|
1268
|
+
|
|
1269
|
+
elif self.tubetype.lower()=='oct':
|
|
1270
|
+
radius = 0.5*diam
|
|
1271
|
+
s = diam / (1+math.sqrt(2.0)) #
|
|
1272
|
+
|
|
1273
|
+
if self.axisofrotation == False:
|
|
1274
|
+
tto = -z_inc-radius-zgap
|
|
1275
|
+
|
|
1276
|
+
text = text+'\r\n! genbox {} octtube1a {} {} {} | xform -t {} {} {}'.format(
|
|
1277
|
+
material, scenex, s, diam, round(-(scenex)/2.0, 6), round(-s/2.0, 6), -radius+tto)
|
|
1278
|
+
|
|
1279
|
+
# Create, translate to center, rotate, translate back to prev. position and translate to overal module position.
|
|
1280
|
+
text = text+'\r\n! genbox {} octtube1b {} {} {} | xform -t {} {} {} -rx 45 -t 0 0 {}'.format(
|
|
1281
|
+
material, scenex, s, diam, round(-(scenex)/2.0+cc, 6), round(-s/2.0, 6), -radius, tto)
|
|
1282
|
+
|
|
1283
|
+
text = text+'\r\n! genbox {} octtube1c {} {} {} | xform -t {} {} {} -rx 90 -t 0 0 {}'.format(
|
|
1284
|
+
material, scenex, s, diam, round(-(scenex)/2.0+cc, 6), -s/2.0, -radius, tto)
|
|
1285
|
+
|
|
1286
|
+
text = text+'\r\n! genbox {} octtube1d {} {} {} | xform -t {} {} {} -rx 135 -t 0 0 {} '.format(
|
|
1287
|
+
material, scenex, s, diam, -(scenex)/2.0+cc, -s/2.0, -radius, tto)
|
|
1288
|
+
|
|
1289
|
+
|
|
1290
|
+
else:
|
|
1291
|
+
raise Exception("Incorrect torque tube type. "+
|
|
1292
|
+
"Available options: 'square' 'oct' 'hex' or 'round'."+
|
|
1293
|
+
" Value entered: {}".format(self.tubetype))
|
|
1294
|
+
self.text = text
|
|
1295
|
+
return text
|
|
1296
|
+
|
|
1297
|
+
class CellModule(SuperClass):
|
|
1298
|
+
|
|
1299
|
+
def __init__(self, numcellsx, numcellsy,
|
|
1300
|
+
xcell, ycell, xcellgap=0.02, ycellgap=0.02, centerJB=None):
|
|
1301
|
+
"""
|
|
1302
|
+
For creating a cell-level module, the following input parameters should
|
|
1303
|
+
be in ``cellModule``:
|
|
1304
|
+
|
|
1305
|
+
================ ====================================================
|
|
1306
|
+
Keys : type Description
|
|
1307
|
+
================ ====================================================
|
|
1308
|
+
numcellsx : int Number of cells in the X-direction within the module
|
|
1309
|
+
numcellsy : int Number of cells in the Y-direction within the module
|
|
1310
|
+
xcell : float Width of each cell (X-direction) in the module
|
|
1311
|
+
ycell : float Length of each cell (Y-direction) in the module
|
|
1312
|
+
xcellgap : float Spacing between cells in the X-direction. 0.02 default
|
|
1313
|
+
ycellgap : float Spacing between cells in the Y-direction. 0.02 default
|
|
1314
|
+
centerJB : float (optional) Distance betwen both sides of cell arrays
|
|
1315
|
+
in a center-JB half-cell module. If 0 or not provided,
|
|
1316
|
+
module will not have the center JB spacing.
|
|
1317
|
+
Only implemented for 'portrait' mode at the moment.
|
|
1318
|
+
(numcellsy > numcellsx).
|
|
1319
|
+
cc : float center cell offset from x so scan is not at a gap
|
|
1320
|
+
between cells
|
|
1321
|
+
================ ====================================================
|
|
1322
|
+
|
|
1323
|
+
"""
|
|
1324
|
+
self.keys = ['numcellsx', 'numcellsy', 'xcell', 'ycell', 'xcellgap',
|
|
1325
|
+
'ycellgap','centerJB']
|
|
1326
|
+
|
|
1327
|
+
# set data object attributes from datakey list.
|
|
1328
|
+
for key in self.keys:
|
|
1329
|
+
setattr(self, key, eval(key))
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
def _makeCellLevelModule(self, module, z, Ny, ygap,
|
|
1333
|
+
modulematerial):
|
|
1334
|
+
""" Calculate the .radfile generation text for a cell-level module.
|
|
1335
|
+
"""
|
|
1336
|
+
offsetfromaxis = module.offsetfromaxis
|
|
1337
|
+
c = self.getDataDict()
|
|
1338
|
+
|
|
1339
|
+
# For half cell modules with the JB on the center:
|
|
1340
|
+
if c['centerJB'] is not None:
|
|
1341
|
+
centerJB = c['centerJB']
|
|
1342
|
+
y = np.round(c['numcellsy']*c['ycell'] + (c['numcellsy']-2)*c['ycellgap'] + centerJB, 6)
|
|
1343
|
+
else:
|
|
1344
|
+
centerJB = 0
|
|
1345
|
+
y = np.round(c['numcellsy']*c['ycell'] + (c['numcellsy']-1)*c['ycellgap'], 6)
|
|
1346
|
+
|
|
1347
|
+
x = np.round(c['numcellsx']*c['xcell'] + (c['numcellsx']-1)*c['xcellgap'], 6)
|
|
1348
|
+
|
|
1349
|
+
#center cell -
|
|
1350
|
+
if c['numcellsx'] % 2 == 0:
|
|
1351
|
+
_cc = np.round(c['xcell']/2.0, 6)
|
|
1352
|
+
print("Module was shifted by {} in X to avoid sensors on air".format(_cc))
|
|
1353
|
+
else:
|
|
1354
|
+
_cc = 0
|
|
1355
|
+
|
|
1356
|
+
text = '! genbox {} cellPVmodule {} {} {} | '.format(modulematerial,
|
|
1357
|
+
c['xcell'], c['ycell'], z)
|
|
1358
|
+
text +='xform -t {} {} {} '.format(round(-x/2.0 + _cc, 6),
|
|
1359
|
+
round((-y*Ny / 2.0)-(ygap*(Ny-1) / 2.0)-centerJB/2.0, 6),
|
|
1360
|
+
round(offsetfromaxis, 6))
|
|
1361
|
+
|
|
1362
|
+
text += '-a {} -t {} 0 0 '.format(c['numcellsx'], c['xcell'] + c['xcellgap'])
|
|
1363
|
+
|
|
1364
|
+
if centerJB != 0:
|
|
1365
|
+
trans0 = c['ycell'] + c['ycellgap']
|
|
1366
|
+
text += '-a {} -t 0 {} 0 '.format(round(c['numcellsy']/2, 6), trans0)
|
|
1367
|
+
#TODO: Continue playing with the y translation of the array in the next two lines
|
|
1368
|
+
# Until it matches. Close but not there.
|
|
1369
|
+
# This is 0 spacing
|
|
1370
|
+
#ytrans1 = y/2.0-c['ycell']/2.0-c['ycellgap']+centerJB/2.0 # Creating the 2nd array with the right Jbox distance
|
|
1371
|
+
ytrans1 = y/2.0-c['ycell']/2.0-c['ycellgap']+centerJB/2.0 + centerJB
|
|
1372
|
+
ytrans2= c['ycell'] - centerJB/2.0 + c['ycellgap']/2.0
|
|
1373
|
+
text += '-a {} -t 0 {} 0 '.format(2, round(ytrans1,6))
|
|
1374
|
+
text += '| xform -t 0 {} 0 '.format(round(ytrans2, 6))
|
|
1375
|
+
|
|
1376
|
+
else:
|
|
1377
|
+
text += '-a {} -t 0 {} 0 '.format(c['numcellsy'], c['ycell'] + c['ycellgap'])
|
|
1378
|
+
|
|
1379
|
+
text += '-a {} -t 0 {} 0'.format(Ny, y+ygap)
|
|
1380
|
+
|
|
1381
|
+
# OPACITY CALCULATION
|
|
1382
|
+
packagingfactor = np.round((c['xcell']*c['ycell']*c['numcellsx']*c['numcellsy'])/(x*y), 2)
|
|
1383
|
+
print("This is a Cell-Level detailed module with Packaging "+
|
|
1384
|
+
"Factor of {} ".format(packagingfactor))
|
|
1385
|
+
|
|
1386
|
+
module.x = x
|
|
1387
|
+
module.y = y
|
|
1388
|
+
self.text = text
|
|
1389
|
+
|
|
1390
|
+
return(text, x, y, _cc)
|
|
1391
|
+
|
|
1392
|
+
class CECModule(SuperClass):
|
|
1393
|
+
|
|
1394
|
+
def __init__(self, alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, Adjust, **kwargs):
|
|
1395
|
+
"""
|
|
1396
|
+
For storing module performance parameters to be fed into pvlib.pvsystem.singlediode()
|
|
1397
|
+
Required passed parameters: alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, Adjust
|
|
1398
|
+
Usage: pass in **dictionary with at minimum these keys: 'alpha_sc', 'a_ref', 'I_L_ref',
|
|
1399
|
+
'I_o_ref', 'R_sh_ref', 'R_s', 'Adjust', 'name' (opt)
|
|
1400
|
+
|
|
1401
|
+
"""
|
|
1402
|
+
self.keys = ['alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s',
|
|
1403
|
+
'Adjust', 'name']
|
|
1404
|
+
name = kwargs.get('name')
|
|
1405
|
+
if name is None:
|
|
1406
|
+
name = kwargs.get('Name')
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
# set data object attributes from datakey list.
|
|
1410
|
+
for key in self.keys:
|
|
1411
|
+
setattr(self, key, eval(key))
|
|
1412
|
+
|
|
1413
|
+
|
|
1414
|
+
|
|
1415
|
+
# deal with Int32 JSON incompatibility
|
|
1416
|
+
# https://www.programmerall.com/article/57461489186/
|
|
1417
|
+
import json
|
|
1418
|
+
class MyEncoder(json.JSONEncoder):
|
|
1419
|
+
def default(self, obj):
|
|
1420
|
+
if isinstance(obj, np.integer):
|
|
1421
|
+
return int(obj)
|
|
1422
|
+
elif isinstance(obj, np.floating):
|
|
1423
|
+
return float(obj)
|
|
1424
|
+
elif isinstance(obj, np.ndarray):
|
|
1425
|
+
return obj.tolist()
|
|
1426
|
+
else:
|
|
1427
|
+
return super(MyEncoder, self).default(obj)
|