• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NREL / bifacial_radiance / 9194573527

22 May 2024 03:56PM UTC coverage: 70.326% (+0.06%) from 70.268%
9194573527

push

github

cdeline
Fix pytests

3728 of 5301 relevant lines covered (70.33%)

1.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.89
/bifacial_radiance/performance.py
1
# -*- coding: utf-8 -*-
2
"""
2✔
3
Created on Tue April 27 06:29:02 2021
4

5
@author: sayala
6
"""
7

8
import pvlib
2✔
9

10
"""
2✔
11
def calculatePerformance(effective_irradiance, CECMod, temp_air=None,
12
                         wind_speed=1, temp_cell=None,  glassglass=False):
13
    '''
14
    DEPRECATED IN FAVOR OF `module.calculatePerformance`
15
    The module parameters are given at the reference condition.
16
    Use pvlib.pvsystem.calcparams_cec() to generate the five SDM
17
    parameters at your desired irradiance and temperature to use
18
    with pvlib.pvsystem.singlediode() to calculate the IV curve information.:
19

20
    Inputs
21
    ------
22
    effective_irradiance : numeric
23
        Dataframe or single value. Must be same length as temp_cell
24
    CECMod : Dict
25
        Dictionary with CEC Module PArameters for the module selected. Must
26
        contain at minimum  alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref,
27
        R_s, Adjust
28
    temp_air : numeric
29
        Ambient temperature in Celsius. Dataframe or single value to calculate.
30
        Must be same length as effective_irradiance.  Default = 20C
31
    wind_speed : numeric
32
        Wind speed at a height of 10 meters [m/s].  Default = 1 m/s
33
    temp_cell : numeric
34
        Back of module temperature.  If provided, overrides temp_air and
35
        wind_speed calculation.  Default = None
36
    glassglass : boolean
37
        If module is glass-glass package (vs glass-polymer) to select correct
38
        thermal coefficients for module temperature calculation
39

40
    '''
41

42
    from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS
43

44
    # Setting temperature_model_parameters
45
    if glassglass:
46
        temp_model_params = (
47
            TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_glass'])
48
    else:
49
        temp_model_params = (
50
            TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_polymer'])
51

52
    if temp_cell is None:
53
        if temp_air is None:
54
            temp_air = 25  # STC
55

56
        temp_cell = pvlib.temperature.sapm_cell(effective_irradiance, temp_air,
57
                                                wind_speed,
58
                                                temp_model_params['a'],
59
                                                temp_model_params['b'],
60
                                                temp_model_params['deltaT'])
61

62
    IL, I0, Rs, Rsh, nNsVth = pvlib.pvsystem.calcparams_cec(
63
        effective_irradiance=effective_irradiance,
64
        temp_cell=temp_cell,
65
        alpha_sc=float(CECMod.alpha_sc),
66
        a_ref=float(CECMod.a_ref),
67
        I_L_ref=float(CECMod.I_L_ref),
68
        I_o_ref=float(CECMod.I_o_ref),
69
        R_sh_ref=float(CECMod.R_sh_ref),
70
        R_s=float(CECMod.R_s),
71
        Adjust=float(CECMod.Adjust)
72
        )
73

74
    IVcurve_info = pvlib.pvsystem.singlediode(
75
        photocurrent=IL,
76
        saturation_current=I0,
77
        resistance_series=Rs,
78
        resistance_shunt=Rsh,
79
        nNsVth=nNsVth
80
        )
81

82
    return IVcurve_info['p_mp']
83
"""
84

85
def MBD(meas, model):
2✔
86
    """
87
    This function calculates the MEAN BIAS DEVIATION of measured vs. modeled
88
    data and returns it as a Percentage [%].
89

90
    MBD=100∙[((1⁄(m)∙∑〖(y_i-x_i)]÷[(1⁄(m)∙∑〖x_i]〗)〗)
91

92
    Parameters
93
    ----------
94
    meas : numeric list
95
        Measured data.
96
    model : numeric list
97
        Modeled data
98

99
    Returns
100
    -------
101
    out : numeric
102
        Percentage [%] Mean Bias Deviation between the measured and the modeled
103
        data.
104

105
    """
106
    import pandas as pd
2✔
107
    df = pd.DataFrame({'model': model, 'meas': meas})
2✔
108
    # rudimentary filtering of modeled irradiance
109
    df = df.dropna()
2✔
110
    minirr = meas.min()
2✔
111
    df = df[df.model > minirr]
2✔
112
    m = df.__len__()
2✔
113
    out = 100*((1/m)*sum(df.model-df.meas))/df.meas.mean()
2✔
114
    return out
2✔
115

116

117
def RMSE(meas, model):
2✔
118
    """
119
    This function calculates the ROOT MEAN SQUARE ERROR of measured vs. modeled
120
    data and returns it as a Percentage [%].
121

122
    RMSD=100∙〖[(1⁄(m)∙∑▒(y_i-x_i )^2 )]〗^(1⁄2)÷[(1⁄(m)∙∑▒〖x_i]〗)
123

124
    Parameters
125
    ----------
126
    meas : numeric list
127
        Measured data.
128
    model : numeric list
129
        Modeled data
130

131
    Returns
132
    -------
133
    out : numeric
134
        Percentage [%] Root Mean Square Error between the measured and the
135
        modeled data.
136

137
    """
138

139
    import numpy as np
2✔
140
    import pandas as pd
2✔
141
    df = pd.DataFrame({'model': model, 'meas': meas})
2✔
142
    df = df.dropna()
2✔
143
    minirr = meas.min()
2✔
144
    df = df[df.model > minirr]
2✔
145
    m = df.__len__()
2✔
146
    out = 100*np.sqrt(1/m*sum((df.model-df.meas)**2))/df.meas.mean()
2✔
147
    return out
2✔
148

149

150
# residuals absolute output (not %)
151
def MBD_abs(meas, model):
2✔
152
    """
153
    This function calculates the ABSOLUTE MEAN BIAS DEVIATION of measured vs.
154
    modeled data and returns it as a Percentage [%].
155

156
    MBD=100∙[((1⁄(m)∙∑〖(y_i-x_i)]÷[(1⁄(m)∙∑〖x_i]〗)〗)
157

158
    Parameters
159
    ----------
160
    meas : numeric list
161
        Measured data.
162
    model : numeric list
163
        Modeled data
164

165
    Returns
166
    -------
167
    out : numeric
168
        Absolute output residual of the Mean Bias Deviation between the
169
        measured and the modeled data.
170

171
    """
172

173
    import pandas as pd
2✔
174
    df = pd.DataFrame({'model': model, 'meas': meas})
2✔
175
    # rudimentary filtering of modeled irradiance
176
    df = df.dropna()
2✔
177
    minirr = meas.min()
2✔
178
    df = df[df.model > minirr]
2✔
179
    m = df.__len__()
2✔
180
    out = ((1/m)*sum(df.model-df.meas))
2✔
181
    return out
2✔
182

183

184
def RMSE_abs(meas, model):
2✔
185
    """
186
    This function calculates the ABSOLUTE ROOT MEAN SQUARE ERROR of measured
187
    vs. modeled data and returns it as a Percentage [%].
188

189
    RMSD=100∙〖[(1⁄(m)∙∑▒(y_i-x_i )^2 )]〗^(1⁄2)÷[(1⁄(m)∙∑▒〖x_i]〗)
190

191
    Parameters
192
    ----------
193
    meas : numeric list
194
        Measured data.
195
    model : numeric list
196
        Modeled data
197

198
    Returns
199
    -------
200
    out : numeric
201
        Absolute output residual of the Root Mean Square Error between the
202
        measured and the modeled data.
203

204
    """
205

206
    #
207
    import numpy as np
2✔
208
    import pandas as pd
2✔
209
    df = pd.DataFrame({'model': model, 'meas': meas})
2✔
210
    df = df.dropna()
2✔
211
    minirr = meas.min()
2✔
212
    df = df[df.model > minirr]
2✔
213
    m = df.__len__()
2✔
214
    out = np.sqrt(1/m*sum((df.model-df.meas)**2))
2✔
215
    return out
2✔
216

217

218
def _cleanDataFrameResults(mattype, rearMat, Wm2Front, Wm2Back,
2✔
219
                           fillcleanedSensors=False, agriPV=False):
220

221
    import numpy as np
2✔
222

223
    if agriPV:
2✔
224
        matchers = ['sky', 'pole', 'tube', 'bar', '3267', '1540']
×
225
    else:
226
        matchers = ['sky', 'pole', 'tube', 'bar', 'ground', '3267', '1540']
2✔
227

228
    maskfront = np.column_stack([mattype[col].str.contains('|'.join(matchers),
2✔
229
                                                           na=False) for col in
230
                                 mattype])
231
    Wm2Front[maskfront] = np.nan
2✔
232

233
    maskback = np.column_stack([rearMat[col].str.contains('|'.join(matchers),
2✔
234
                                                          na=False) for col in
235
                                rearMat])
236
    Wm2Back[maskback] = np.nan
2✔
237

238
    # Filling Nans...
239
    filledFront = Wm2Front.mean(axis=1)
2✔
240

241
    if fillcleanedSensors:
2✔
242
        filledBack = Wm2Back.copy().interpolate()
2✔
243
    else:
244
        filledBack = Wm2Back.copy()  # interpolate()
2✔
245

246
    return filledFront, filledBack
2✔
247

248

249
def calculateResults(module, csvfile=None, results=None,
2✔
250
                     temp_air=None, wind_speed=1, temp_cell=None,
251
                     CECMod2=None,
252
                     fillcleanedSensors=False, agriPV=False, **kwargs):
253
    '''
254
    Calculate Performance and Mismatch for timestamped data. This routine requires
255
    CECMod details to have been set with the module using ModuleObj.addCEC.
256

257

258
    Parameters
259
    ----------
260
    module : bifacial_radiance.module.ModuleObj
261
        module object with CEC Module data as dictionary
262
    csvfile : numeric list
263
        Compiled Results data
264
    results : numeric list
265
        compiled Results data
266
    temp_air : value or list
267
        Air temperature for calculating module temperature
268
    wind_speed : value or list
269
        Wind tempreatuer for calcluating module temperature
270
    temp_cell : value or list
271
        Cell temperature for calculating module performance. If none, module
272
        temperature is calculated using temp_air and wind_speed
273
    CECMod2 : dict, optional
274
        CEC Module data as dictionary, for a monofacial module to be used as
275
        comparison for Bifacial Gain in Energy using only the calculated front
276
        Irradiance. If none, same module as CECMod
277
        is used.
278
    agriPV : Bool
279
        Turns off cleaning for ground material
280

281
    Returns
282
    -------
283
    dfst : dataframe
284
    Dataframe with the complied and calculated results for the sim, including:
285
        POA_eff: mean of [(mean of clean Gfront) + clean Grear * bifaciality
286
                          factor]
287
        Gfront_mean: mean of clean Gfront
288
        Grear_mean: mean of clean Grear
289
        Mismatch: mismatch calculated from the MAD distribution of
290
                  POA_total
291
        Pout_raw: power output calculated from POA_total, considers
292
              wind speed and temp_amb if in trackerdict.
293
        Pout: power output considering electrical mismatch
294
        BGG: Bifacial Gain in Irradiance, [Grear_mean*100*bifacialityfactor/
295
                                           Gfront_mean]
296
        BGE: Bifacial Gain in Energy, when power is calculated with CECMod2 or
297
            same module but just the front irradiance as input, so that
298
            [Pout-Pout_Gfront/Pout_Gfront]
299

300
    '''
301

302
    from bifacial_radiance import mismatch
2✔
303

304
    import pandas as pd
2✔
305

306

307
    dfst = pd.DataFrame()
2✔
308

309
    if csvfile is not None:
2✔
310
        data = pd.read_csv(csvfile)
×
311
        Wm2Front = data['Wm2Front'].str.strip(
×
312
            '[]').str.split(',', expand=True).astype(float)
313
        Wm2Back = data['Wm2Back'].str.strip(
×
314
            '[]').str.split(',', expand=True).astype(float)
315
        mattype = data['mattype'].str.strip('[]').str.split(',', expand=True)
×
316
        rearMat = data['rearMat'].str.strip('[]').str.split(',', expand=True)
×
317

318
        if 'timestamp' in data:
×
319
            dfst['timestamp'] = data['timestamp']
×
320
        if 'modNum' in data:
×
321
            dfst['Module'] = data['modNum']
×
322
        if 'rowNum' in data:
×
323
            dfst['Row'] = data['rowNum']
×
324
        if 'sceneNum' in data:
×
325
            dfst['sceneNum'] = data['sceneNum']
×
326
    else:
327
        if results is not None:
2✔
328
            Wm2Front = pd.DataFrame.from_dict(dict(zip(
2✔
329
                results.index, results['Wm2Front']))).T
330
            Wm2Back = pd.DataFrame.from_dict(dict(zip(
2✔
331
                results.index, results['Wm2Back']))).T
332
            mattype = pd.DataFrame.from_dict(dict(zip(
2✔
333
                results.index, results['mattype']))).T
334
            rearMat = pd.DataFrame.from_dict(dict(zip(
2✔
335
                results.index, results['rearMat']))).T
336

337
            if 'timestamp' in results:
2✔
338
                dfst['timestamp'] = results['timestamp']
×
339
            if 'modNum' in results:
2✔
340
                dfst['module'] = results['modNum']
2✔
341
            if 'rowNum' in results:
2✔
342
                dfst['row'] = results['rowNum']
2✔
343
            if 'sceneNum' in results:
2✔
344
                dfst['sceneNum'] = results['sceneNum']
2✔
345

346
        else:
347
            print("Data or file not passed. Ending arrayResults")
×
348
            return
×
349

350
    filledFront, filledBack = _cleanDataFrameResults(
2✔
351
        mattype, rearMat, Wm2Front, Wm2Back,
352
        fillcleanedSensors=fillcleanedSensors, agriPV=agriPV)
353

354
    POA = filledBack.apply(lambda x: x*module.bifi + filledFront)
2✔
355

356
    # Statistics Calculations
357
    # dfst['MAD/G_Total'] = bifacial_radiance.mismatch.mad_fn(POA.T)
358
    # 'MAD/G_Total
359
    dfst['POA_eff'] = POA.mean(axis=1)
2✔
360
    dfst['Grear_mean'] = Wm2Back.mean(axis=1)
2✔
361
    dfst['Gfront_mean'] = Wm2Front.mean(axis=1)
2✔
362

363
    # dfst['MAD/G_Total**2'] = dfst['MAD/G_Total']**2
364
    # dfst['stdev'] = POA.std(axis=1)/ dfst['poat']
365

366
    dfst['Pout_raw'] = module.calculatePerformance(
2✔
367
        effective_irradiance=dfst['POA_eff'], 
368
        temp_air=temp_air, wind_speed=wind_speed, temp_cell=temp_cell)
369
    dfst['Pout_Gfront'] = module.calculatePerformance(
2✔
370
        effective_irradiance=dfst['Gfront_mean'], CECMod=CECMod2,
371
        temp_air=temp_air, wind_speed=wind_speed, temp_cell=temp_cell)
372
    dfst['BGG'] = dfst['Grear_mean']*100*module.bifi/dfst['Gfront_mean']
2✔
373
    dfst['BGE'] = ((dfst['Pout_raw'] - dfst['Pout_Gfront']) * 100 /
2✔
374
                   dfst['Pout_Gfront'])
375
    dfst['Mismatch'] = mismatch.mismatch_fit3(POA.T)
2✔
376
    dfst['Pout'] = dfst['Pout_raw']*(1-dfst['Mismatch']/100)
2✔
377
    dfst['Wind Speed'] = wind_speed
2✔
378
    if "dni" in kwargs:
2✔
379
        dfst['DNI'] = kwargs['dni']
2✔
380
    if "dhi" in kwargs:
2✔
381
        dfst['DHI'] = kwargs['dhi']
2✔
382
    if "ghi" in kwargs:
2✔
383
        dfst['GHI'] = kwargs['ghi']
2✔
384
    dfst['Wind Speed'] = wind_speed
2✔
385

386
    return dfst
2✔
387

388

389
def calculateResultsGencumsky1axis(csvfile=None, results=None,
2✔
390
                                   bifacialityfactor=1.0,
391
                                   fillcleanedSensors=True, agriPV=False):
392
    '''
393
    Compile calculate results for cumulative 1 axis tracking routine
394

395
    Parameters
396
    ----------
397
    csvfile : numeric list
398
        Compiled Results data
399
    results : numeric list
400
        compiled Results data
401
    agriPV : Bool
402
        Turns off cleaning for ground material
403

404
    Returns
405
    -------
406
    dfst : dataframe
407
        Dataframe with the complied and calculated results for the sim,
408
        including: POA_eff: Avg of [(mean of clean Gfront) + clean Grear *
409
                                    bifaciality factor]
410
        Gfront_mean: mean of clean Gfront
411
        Grear_mean: mean of clean Grear
412
        BGG: Bifacial Gain in Irradiance, [Grear_mean * 100 *
413
                                           bifacialityfactor/Gfront_mean
414

415
    '''
416

417
    import pandas as pd
2✔
418

419
    dfst = pd.DataFrame()
2✔
420

421
    if csvfile is not None:
2✔
422
        data = pd.read_csv(csvfile)
×
423
        Wm2Front = data['Wm2Front'
×
424
                        ].str.strip('[]').str.split(',',
425
                                                    expand=True).astype(float)
426
        Wm2Back = data['Wm2Back'
×
427
                       ].str.strip('[]').str.split(',',
428
                                                   expand=True).astype(float)
429
        mattype = data['mattype'
×
430
                       ].str.strip('[]').str.split(',',
431
                                                   expand=True)
432
        rearMat = data['rearMat'
×
433
                       ].str.strip('[]').str.split(',',
434
                                                   expand=True)
435

436
        if 'modNum' in data:
×
437
            dfst['module'] = data['modNum']
×
438
        if 'rowNum' in data:
×
439
            dfst['row'] = data['rowNum']
×
440
        if 'sceneNum' in data:
×
441
            dfst['sceneNum'] = data['sceneNum']
×
442
    else:
443
        if results is not None:
2✔
444
            Wm2Front = pd.DataFrame.from_dict(dict(zip(
2✔
445
                results.index, results['Wm2Front']))).T
446
            Wm2Back = pd.DataFrame.from_dict(dict(zip(
2✔
447
                results.index, results['Wm2Back']))).T
448
            mattype = pd.DataFrame.from_dict(dict(zip(
2✔
449
                results.index, results['mattype']))).T
450
            rearMat = pd.DataFrame.from_dict(dict(zip(
2✔
451
                results.index, results['rearMat']))).T
452

453
            if 'modNum' in results:
2✔
454
                dfst['module'] = results['modNum']
2✔
455
            if 'rowNum' in results:
2✔
456
                dfst['row'] = results['rowNum']
2✔
457
            if 'sceneNum' in results:
2✔
458
                dfst['sceneNum'] = results['sceneNum']
2✔
459
                
460

461
        else:
462
            print("Data or file not passed. Ending calculateResults")
×
463
            return
×
464

465
    # Data gets cleaned but need to maintain same number of sensors
466
    # due to adding for the various tracker angles.
467
    filledFront, filledBack = _cleanDataFrameResults(
2✔
468
        mattype, rearMat, Wm2Front, Wm2Back,
469
        fillcleanedSensors=fillcleanedSensors, agriPV=agriPV)
470
    cumFront = []
2✔
471
    cumBack = []
2✔
472
    cumRow = []
2✔
473
    cumMod = []
2✔
474
    Grear_mean = []
2✔
475
#    Gfront_mean = []
476
    POA_eff = []
2✔
477

478
    # NOTE change 26.07.22 'row' -> 'rowNum' and 'mod' -> 'ModNumber
479
    # NOTE change March 13 2024 ModNumber -> modNum
480
    for rownum in results['rowNum'].unique():
2✔
481
        for modnum in results['modNum'].unique():
2✔
482
            mask = (results['rowNum'] == rownum) & (
2✔
483
                results['modNum'] == modnum)
484
            cumBack.append(list(filledBack[mask].sum(axis=0)))
2✔
485
            cumFront.append(filledFront[mask].sum(axis=0))
2✔
486
            cumRow.append(rownum)
2✔
487
            cumMod.append(modnum)
2✔
488

489
            # Maybe this would be faster by first doing the DF with the above,
490
            # exploding the column and calculating.
491
            POA_eff.append(list(
2✔
492
                (filledBack[mask].apply(lambda x: x*bifacialityfactor +
493
                                        filledFront[mask])).sum(axis=0)))
494
            Grear_mean.append(filledBack[mask].sum(axis=0).mean())
2✔
495
            # Gfront_mean.append(filledFront[mask].sum(axis=0).mean())
496

497
    dfst = pd.DataFrame(zip(cumRow, cumMod, cumFront, cumBack, Grear_mean,
2✔
498
                            POA_eff), columns=('row', 'module', 'Gfront_mean',
499
                                               'Wm2Back', 'Grear_mean',
500
                                               'POA_eff'))
501

502
    dfst['BGG'] = dfst['Grear_mean']*100*bifacialityfactor/dfst['Gfront_mean']
2✔
503

504
    # Reordering columns
505
    cols = ['row', 'module', 'BGG', 'Gfront_mean', 'Grear_mean', 'POA_eff',
2✔
506
            'Wm2Back']
507
    dfst = dfst[cols]
2✔
508

509
    return dfst
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc