Skip to content

Commit 59e501b

Browse files
author
Kempe
committed
Update standards.py
Massive changes here but for the most part it is all about the standoff calculation which is working. One change to be aware of is that the prior calculations were using the module surface temperature for the standoff calculation but now it is using the cell temperature calculation.
1 parent 7b310b6 commit 59e501b

File tree

1 file changed

+93
-119
lines changed

1 file changed

+93
-119
lines changed

pvdeg/standards.py

+93-119
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,35 @@ def eff_gap_parameters(
4040
Weather data for a single location.
4141
meta : pd.DataFrame
4242
Meta data for a single location.
43-
tilt : float,
44-
Tilt angle of PV system relative to horizontal.
45-
azimuth : float, optional
46-
Azimuth angle of PV system relative to north.
43+
4744
sky_model : str, optional
4845
Options: 'isotropic', 'klucher', 'haydavies', 'reindl', 'king', 'perez'.
4946
temp_model : str, optional
5047
Options: 'sapm'. 'pvsyst' and 'faiman' will be added later.
51-
conf_0 : str, optional
48+
Performs the calculation for the cell temperature.
49+
conf_0 : str, optional Model for the high temperature module on the exponential decay curve.
5250
Default: 'insulated_back_glass_polymer'
5351
conf_inf : str, optional
52+
Model for the lowest temperature module on the exponential decay curve.
5453
Default: 'open_rack_glass_polymer'
5554
wind_speed_factor : float, optional
5655
Wind speed correction factor to account for different wind speed measurement heights
5756
between weather database (e.g. NSRDB) and the tempeature model (e.g. SAPM)
5857
The NSRD provides calculations at 2m (i.e module height) but SAPM uses a 10m height.
59-
It is recommended that a power-law relationship between height and wind speed of 0.33 be used.
60-
This results in a wind_speed_factor of 1.7. It is acknowledged that this can vary significantly.
61-
58+
It is recommended that a power-law relationship between height and wind speed of 0.33
59+
be used. This results in a wind_speed_factor of 1.7. It is acknowledged that this can
60+
vary significantly.
61+
62+
Meta data
63+
------------------
64+
tilt : float,
65+
Tilt angle of PV system relative to horizontal. [°] Required
66+
azimuth : float,
67+
Azimuth angle of PV system relative to north. [°] Required
68+
Anemomenter_Height_m : float,
69+
if wind_speed_factor is "None", it will run a calculation based on this height and a
70+
power factor of 0.33. If neither are supplied a wind speed factor of 1 will be used.
71+
6272
Returns
6373
-------
6474
T_0 : float
@@ -69,10 +79,12 @@ def eff_gap_parameters(
6979
An array of temperature values for a module that is rack mounted, [°C].
7080
T_measured : float
7181
An array of values for the test module in the system, [°C] interest.
82+
poa : float
83+
An array of values for the plane of array irradiance, [W/m²]
7284
7385
"""
7486

75-
parameters = ["temp_air", "wind_speed", "dhi", "ghi", "dni", "temp_measured"]
87+
parameters = ["temp_air", "wind_speed", "dhi", "ghi", "dni", "Module_Temperature"]
7688

7789
if isinstance(weather_df, dd.DataFrame):
7890
weather_df = weather_df[parameters].compute()
@@ -87,15 +99,22 @@ def eff_gap_parameters(
8799
weather_df,
88100
meta,
89101
sol_position=solar_position,
90-
tilt=meta.tilt,
91-
azimuth=meta.azimuth,
102+
tilt=float(meta['Tilt']),
103+
azimuth=float(meta['Azimuth']),
92104
sky_model=sky_model, )
93-
T_0 = temperature.module(
94-
weather_df, meta, poa, temp_model, conf_0, wind_speed_factor )
95-
T_inf = temperature.module(
96-
weather_df, meta, poa, temp_model, conf_inf, wind_speed_factor )
105+
if wind_speed_factor is None:
106+
if 'Anemometer_Height_m' in meta():
107+
wind_speed_factor = (meta('Anemometer_Height_m')/2) ** 0.33 #Assumes a 2 meter height used in the module temperature model
108+
else:
109+
wind_speed_factor = 1
110+
T_0 = temperature.cell(
111+
weather_df=weather_df, meta=meta, poa=poa, temp_model=temp_model, conf=conf_0,
112+
wind_speed_factor=wind_speed_factor )
113+
T_inf = temperature.cell(
114+
weather_df=weather_df, meta=meta, poa=poa, temp_model=temp_model, conf=conf_inf,
115+
wind_speed_factor=wind_speed_factor )
97116
T_measured = weather_df.Module_Temperature
98-
T_ambient = weather_df.temperature
117+
T_ambient = weather_df.temp_air
99118

100119
return T_0, T_inf, T_measured, T_ambient, poa
101120

@@ -118,7 +137,7 @@ def eff_gap(T_0, T_inf, T_measured, T_ambient, poa, x_0=6.5, poa_min=100, t_amb_
118137
An array of values for the measured module temperature, [°C].
119138
T_ambient : float
120139
An array of values for the ambient temperature, [°C].
121-
poa : float
140+
poa : float
122141
An array of values for the plane of array irradiance, [W/m²]
123142
x_0 : float, optional
124143
Thermal decay constant [cm], [Kempe, PVSC Proceedings 2023].
@@ -139,18 +158,18 @@ def eff_gap(T_0, T_inf, T_measured, T_ambient, poa, x_0=6.5, poa_min=100, t_amb_
139158

140159
n = 0
141160
summ = 0
142-
for i in range(0, len(t_0)):
143-
if T_ambient > t_amb_min:
144-
if poa > poa_min:
161+
for i in range(0, len(T_0)):
162+
if T_ambient.iloc[i] > t_amb_min:
163+
if poa.poa_global.iloc[i] > poa_min:
145164
n = n + 1
146-
summ = summ + (T_0[i] - T_module[i]) / (T_0[i] - T_inf[i])
165+
summ = summ + (T_0.iloc[i] - T_measured.iloc[i]) / (T_0.iloc[i] - T_inf.iloc[i])
147166

148167
try:
149168
x_eff = -x_0 * np.log(1 - summ/n)
150169
except RuntimeWarning as e:
151170
x_eff = np.nan # results if the specified T₉₈ is cooler than an open_rack temperature
152-
if x<0:
153-
x_eff=0
171+
if x_eff < 0:
172+
x_eff = 0
154173

155174
return x_eff
156175

@@ -159,20 +178,23 @@ def standoff(
159178
weather_df=None,
160179
meta=None,
161180
weather_kwarg=None,
162-
tilt=None,
163-
azimuth=180,
181+
tilt=0,
182+
azimuth=None,
164183
sky_model="isotropic",
165184
temp_model="sapm",
166-
#module_type="glass_polymer", # This is removed forcing one to specify alternatives if desired
167185
conf_0="insulated_back_glass_polymer",
168186
conf_inf="open_rack_glass_polymer",
169-
#level=1, # I am removing this because it is unnecessarily complicated
170187
T98=70, # [°C]
171188
x_0=6.5, # [cm]
172189
wind_speed_factor=1.7,
173190
):
174191
"""
175-
Calculate a minimum standoff distance for roof mounded PV systems.
192+
Calculate a minimum standoff distance for roof mounded PV systems.
193+
Will default to horizontal tilt. If the azimuth is not provided, it
194+
will use equator facing.
195+
You can use customized temperature models for the building integrated
196+
and the rack mounted configuration, but it will still assume an
197+
exponential decay.
176198
177199
Parameters
178200
----------
@@ -181,16 +203,19 @@ def standoff(
181203
meta : pd.DataFrame
182204
Meta data for a single location.
183205
tilt : float, optional
184-
Tilt angle of PV system relative to horizontal.
206+
Tilt angle of PV system relative to horizontal. [°]
185207
azimuth : float, optional
186-
Azimuth angle of PV system relative to north.
208+
Azimuth angle of PV system relative to north. [°]
187209
sky_model : str, optional
188210
Options: 'isotropic', 'klucher', 'haydavies', 'reindl', 'king', 'perez'.
189211
temp_model : str, optional
190212
Options: 'sapm'. 'pvsyst' and 'faiman' will be added later.
213+
Performs the calculations for the cell temperature.
191214
conf_0 : str, optional
215+
Model for the high temperature module on the exponential decay curve.
192216
Default: 'insulated_back_glass_polymer'
193217
conf_inf : str, optional
218+
Model for the lowest temperature module on the exponential decay curve.
194219
Default: 'open_rack_glass_polymer'
195220
x0 : float, optional
196221
Thermal decay constant (cm), [Kempe, PVSC Proceedings 2023]
@@ -208,14 +233,19 @@ def standoff(
208233
Effective gap "x" for the lower limit for Level 1 or Level 0 modules (IEC TS 63216)
209234
T98_0 : float [°C]
210235
This is the 98th percential temperature of a theoretical module with no standoff.
211-
T98_inf : float [°C]
236+
T98_inf : float [°C]
212237
This is the 98th percential temperature of a theoretical rack mounted module.
213238
214239
References
215240
----------
216241
M. Kempe, et al. Close Roof Mounted System Temperature Estimation for Compliance
217242
to IEC TS 63126, PVSC Proceedings 2023
218243
"""
244+
if azimuth == None: #Sets the default orientation to equator facing.
245+
if float(meta['latitude']) < 0:
246+
azimuth=0
247+
else:
248+
azimuth=180
219249

220250
parameters = ["temp_air", "wind_speed", "dhi", "ghi", "dni"]
221251

@@ -229,17 +259,17 @@ def standoff(
229259

230260
solar_position = spectral.solar_position(weather_df, meta)
231261
poa = spectral.poa_irradiance(
232-
weather_df,
233-
meta,
262+
weather_df=weather_df,
263+
meta=meta,
234264
sol_position=solar_position,
235265
tilt=tilt,
236266
azimuth=azimuth,
237267
sky_model=sky_model, )
238-
T_0 = temperature.module(
239-
weather_df, meta, poa, temp_model, conf_0, wind_speed_factor )
268+
T_0 = temperature.cell(
269+
weather_df=weather_df, meta=meta, poa=poa, temp_model=temp_model, conf=conf_0, wind_speed_factor=wind_speed_factor )
240270
T98_0 = T_0.quantile(q=0.98, interpolation="linear")
241-
T_inf = temperature.module(
242-
weather_df, meta, poa, temp_model, conf_inf, wind_speed_factor )
271+
T_inf = temperature.cell(
272+
weather_df=weather_df, meta=meta, poa=poa, temp_model=temp_model, conf=conf_inf, wind_speed_factor=wind_speed_factor )
243273
T98_inf = T_inf.quantile(q=0.98, interpolation="linear")
244274

245275
try:
@@ -265,8 +295,10 @@ def T98_estimate(
265295
conf_0="insulated_back_glass_polymer",
266296
conf_inf="open_rack_glass_polymer",
267297
wind_speed_factor=1.7,
298+
tilt=0,
299+
azimuth=None,
268300
x_eff=None,
269-
x_0=6.5):
301+
x_0=6.5,):
270302

271303
"""
272304
Calculate and set up data necessary to calculate the effective standoff distance for rooftop mounded PV system
@@ -276,24 +308,27 @@ def T98_estimate(
276308
Parameters
277309
----------
278310
x_eff : float
279-
This is the effective module standoff distance according to the model.
311+
This is the effective module standoff distance according to the model. [cm]
280312
x_0 : float, optional
281-
Thermal decay constant [cm],
313+
Thermal decay constant. [cm]
282314
weather_df : pd.DataFrame
283-
Weather data for a single location.
315+
Weather data for a single location.
284316
meta : pd.DataFrame
285317
Meta data for a single location.
286318
tilt : float,
287-
Tilt angle of PV system relative to horizontal.
319+
Tilt angle of PV system relative to horizontal. [°]
288320
azimuth : float, optional
289-
Azimuth angle of PV system relative to north.
321+
Azimuth angle of PV system relative to north. [°]
290322
sky_model : str, optional
291323
Options: 'isotropic', 'klucher', 'haydavies', 'reindl', 'king', 'perez'.
292324
temp_model : str, optional
293325
Options: 'sapm'. 'pvsyst' and 'faiman' will be added later.
326+
Performs the calculations for the cell temperature.
294327
conf_0 : str, optional
328+
Model for the high temperature module on the exponential decay curve.
295329
Default: 'insulated_back_glass_polymer'
296330
conf_inf : str, optional
331+
Model for the lowest temperature module on the exponential decay curve.
297332
Default: 'open_rack_glass_polymer'
298333
wind_speed_factor : float, optional
299334
Wind speed correction factor to account for different wind speed measurement heights
@@ -308,8 +343,13 @@ def T98_estimate(
308343
This is the 98th percential temperature for the module at the given tilt, azimuth, and x_eff.
309344
310345
"""
346+
if azimuth == None: #Sets the default orientation to equator facing.
347+
if float(meta['latitude']) < 0:
348+
azimuth=0
349+
else:
350+
azimuth=180
311351

312-
parameters = ["temp_air", "wind_speed", "dhi", "ghi", "dni", "temp_measured"]
352+
parameters = ["temp_air", "wind_speed", "dhi", "ghi", "dni"]
313353

314354
if isinstance(weather_df, dd.DataFrame):
315355
weather_df = weather_df[parameters].compute()
@@ -321,89 +361,23 @@ def T98_estimate(
321361

322362
solar_position = spectral.solar_position(weather_df, meta)
323363
poa = spectral.poa_irradiance(
324-
weather_df,
325-
meta,
364+
weather_df=weather_df,
365+
meta=meta,
326366
sol_position=solar_position,
327-
tilt=meta.tilt,
328-
azimuth=meta.azimuth,
367+
tilt=tilt,
368+
azimuth=azimuth,
329369
sky_model=sky_model, )
330-
T_0 = temperature.module(
331-
weather_df, meta, poa, temp_model, conf_0, wind_speed_factor )
370+
T_0 = temperature.cell(
371+
weather_df, meta, poa, temp_model, conf_0, wind_speed_factor, )
332372
T98_0 = T_0.quantile(q=0.98, interpolation="linear")
333-
T_inf = temperature.module(
334-
weather_df, meta, poa, temp_model, conf_inf, wind_speed_factor )
373+
T_inf = temperature.cell(
374+
weather_df, meta, poa, temp_model, conf_inf, wind_speed_factor, )
335375
T98_inf = T_inf.quantile(q=0.98, interpolation="linear")
336376

337-
T98 = T_0 - (T_0-T_inf)*(1-np.exp(-x_eff/x_0))
377+
T98 = T98_0 - (T98_0-T98_inf)*(1-np.exp(-x_eff/x_0))
338378

339379
return T98
340380

341-
def standoff_tilt_azimuth_scan(
342-
weather_df=None,
343-
meta=None,
344-
weather_kwarg=None,
345-
tilt_count=18,
346-
azimuth_count=72,
347-
sky_model="isotropic",
348-
temp_model="sapm",
349-
conf_0="insulated_back_glass_polymer",
350-
conf_inf="open_rack_glass_polymer",
351-
T98=70, # [°C]
352-
x_0=6.5, # [cm]
353-
wind_speed_factor=1.7):
354-
355-
"""
356-
Calculate a minimum standoff distance for roof mounded PV systems as a function of tilt and azimuth.
357-
358-
Parameters
359-
----------
360-
weather_df : pd.DataFrame
361-
Weather data for a single location.
362-
meta : pd.DataFrame
363-
Meta data for a single location.
364-
tilt_count : integer
365-
Step in degrees of change in tilt angle of PV system between calculations.
366-
azimuth_count : integer
367-
Step in degrees of change in Azimuth angle of PV system relative to north.
368-
sky_model : str, optional
369-
Options: 'isotropic', 'klucher', 'haydavies', 'reindl', 'king', 'perez'.
370-
temp_model : str, optional
371-
Options: 'sapm'. 'pvsyst' and 'faiman' will be added later.
372-
conf_0 : str, optional
373-
Default: 'insulated_back_glass_polymer'
374-
conf_inf : str, optional
375-
Default: 'open_rack_glass_polymer'
376-
x_0 : float, optional
377-
Thermal decay constant [cm], [Kempe, PVSC Proceedings 2023]
378-
wind_speed_factor : float, optional
379-
Wind speed correction factor to account for different wind speed measurement heights
380-
between weather database (e.g. NSRDB) and the tempeature model (e.g. SAPM)
381-
The NSRD provides calculations at 2m (i.e module height) but SAPM uses a 10m height.
382-
It is recommended that a power-law relationship between height and wind speed of 0.33 be used.
383-
This results in a wind_speed_factor of 1.7. It is acknowledged that this can vary significantly.
384-
Returns
385-
standoff_series : 2-D array with each row consiting of tilt, azimuth, then standoff
386-
"""
387-
388-
standoff_series=np.array([(azimuth_count+1)*(tilt_count+1)][3])
389-
for x in range (0, azimuth_count+1):
390-
for y in range (0,tilt_count+1):
391-
standoff_series[x+y][0]=azimuth_count*180/azimuth_count
392-
standoff_series[x+y][1]=tilt_count*90/azimuth_count
393-
standoff_series[x+y][2]=standards.standoff(
394-
weather_df=weather_df,
395-
meta=meta,
396-
T98=T98,
397-
tilt=y*90/tilt_count,
398-
azimuth=x*180/azimuth_count,
399-
sky_model=sky_model,
400-
temp_model=temp_model,
401-
conf_0=conf_0,
402-
conf_inf=conf_inf,
403-
x_0=x_0,
404-
wind_speed_factor=wind_speed_factor)
405-
406-
return standoff_series
407381

408382
# def run_calc_standoff(
409383
# project_points,

0 commit comments

Comments
 (0)