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

NREL / bifacial_radiance / 10778060626

09 Sep 2024 05:18PM UTC coverage: 72.218% (+0.06%) from 72.155%
10778060626

Pull #543

github

cdeline
add `results` property to RadianceObj.  rename demo.CompiledResults to demo.compiledResults
Pull Request #543: 512 cec performance

119 of 135 new or added lines in 4 files covered. (88.15%)

7 existing lines in 1 file now uncovered.

3699 of 5122 relevant lines covered (72.22%)

1.44 hits per line

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

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

5
@author: sayala
6
"""
7

8
import pvlib
2✔
9
import pandas as pd
2✔
10
import numpy as np
2✔
11

12

13
def MBD(meas, model):
2✔
14
    """
15
    This function calculates the MEAN BIAS DEVIATION of measured vs. modeled
16
    data and returns it as a Percentage [%].
17

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

20
    Parameters
21
    ----------
22
    meas : numeric list
23
        Measured data.
24
    model : numeric list
25
        Modeled data
26

27
    Returns
28
    -------
29
    out : numeric
30
        Percentage [%] Mean Bias Deviation between the measured and the modeled
31
        data.
32

33
    """
34

35
    df = pd.DataFrame({'model': model, 'meas': meas})
2✔
36
    # rudimentary filtering of modeled irradiance
37
    df = df.dropna()
2✔
38
    minirr = meas.min()
2✔
39
    df = df[df.model > minirr]
2✔
40
    m = df.__len__()
2✔
41
    out = 100*((1/m)*sum(df.model-df.meas))/df.meas.mean()
2✔
42
    return out
2✔
43

44

45
def RMSE(meas, model):
2✔
46
    """
47
    This function calculates the ROOT MEAN SQUARE ERROR of measured vs. modeled
48
    data and returns it as a Percentage [%].
49

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

52
    Parameters
53
    ----------
54
    meas : numeric list
55
        Measured data.
56
    model : numeric list
57
        Modeled data
58

59
    Returns
60
    -------
61
    out : numeric
62
        Percentage [%] Root Mean Square Error between the measured and the
63
        modeled data.
64

65
    """
66

67

68

69
    df = pd.DataFrame({'model': model, 'meas': meas})
2✔
70
    df = df.dropna()
2✔
71
    minirr = meas.min()
2✔
72
    df = df[df.model > minirr]
2✔
73
    m = df.__len__()
2✔
74
    out = 100*np.sqrt(1/m*sum((df.model-df.meas)**2))/df.meas.mean()
2✔
75
    return out
2✔
76

77

78
# residuals absolute output (not %)
79
def MBD_abs(meas, model):
2✔
80
    """
81
    This function calculates the ABSOLUTE MEAN BIAS DEVIATION of measured vs.
82
    modeled data and returns it as a Percentage [%].
83

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

86
    Parameters
87
    ----------
88
    meas : numeric list
89
        Measured data.
90
    model : numeric list
91
        Modeled data
92

93
    Returns
94
    -------
95
    out : numeric
96
        Absolute output residual of the Mean Bias Deviation between the
97
        measured and the modeled data.
98

99
    """
100

101
    df = pd.DataFrame({'model': model, 'meas': meas})
2✔
102
    # rudimentary filtering of modeled irradiance
103
    df = df.dropna()
2✔
104
    minirr = meas.min()
2✔
105
    df = df[df.model > minirr]
2✔
106
    m = df.__len__()
2✔
107
    out = ((1/m)*sum(df.model-df.meas))
2✔
108
    return out
2✔
109

110

111
def RMSE_abs(meas, model):
2✔
112
    """
113
    This function calculates the ABSOLUTE ROOT MEAN SQUARE ERROR of measured
114
    vs. modeled data and returns it as a Percentage [%].
115

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

118
    Parameters
119
    ----------
120
    meas : numeric list
121
        Measured data.
122
    model : numeric list
123
        Modeled data
124

125
    Returns
126
    -------
127
    out : numeric
128
        Absolute output residual of the Root Mean Square Error between the
129
        measured and the modeled data.
130

131
    """
132

133
    #
134

135
    df = pd.DataFrame({'model': model, 'meas': meas})
2✔
136
    df = df.dropna()
2✔
137
    minirr = meas.min()
2✔
138
    df = df[df.model > minirr]
2✔
139
    m = df.__len__()
2✔
140
    out = np.sqrt(1/m*sum((df.model-df.meas)**2))
2✔
141
    return out
2✔
142

143

144
def _cleanDataFrameResults(mattype, rearMat, Wm2Front, Wm2Back,
2✔
145
                           fillcleanedSensors=False, agriPV=False):
146

147

148
    if agriPV:
2✔
149
        matchers = ['sky', 'pole', 'tube', 'bar', '3267', '1540']
×
150
    else:
151
        matchers = ['sky', 'pole', 'tube', 'bar', 'ground', '3267', '1540']
2✔
152

153
    maskfront = np.column_stack([mattype[col].str.contains('|'.join(matchers),
2✔
154
                                                           na=False) for col in
155
                                 mattype])
156
    Wm2Front[maskfront] = np.nan
2✔
157

158
    maskback = np.column_stack([rearMat[col].str.contains('|'.join(matchers),
2✔
159
                                                          na=False) for col in
160
                                rearMat])
161
    Wm2Back[maskback] = np.nan
2✔
162

163
    # Filling Nans...
164
    filledFront = Wm2Front.mean(axis=1)
2✔
165

166
    if fillcleanedSensors:
2✔
167
        filledBack = Wm2Back.copy().interpolate()
2✔
168
    else:
169
        filledBack = Wm2Back.copy()  # interpolate()
2✔
170

171
    return filledFront, filledBack
2✔
172

173

174
def calculatePerformance(module, csvfile=None, results=None,
2✔
175
                     temp_air=None, wind_speed=1, temp_cell=None,
176
                     CECMod2=None,
177
                     fillcleanedSensors=False, agriPV=False, **kwargs):
178
    '''
179
    Calculate Performance and Mismatch for timestamped data. This routine requires
180
    CECMod details to have been set with the module using ModuleObj.addCEC.
181

182

183
    Parameters
184
    ----------
185
    module : bifacial_radiance.module.ModuleObj
186
        module object with CEC Module data as dictionary
187
    csvfile : numeric list
188
        Compiled Results data
189
    results : numeric list
190
        compiled Results data
191
    temp_air : value or list
192
        Air temperature for calculating module temperature
193
    wind_speed : value or list
194
        Wind tempreatuer for calcluating module temperature
195
    temp_cell : value or list
196
        Cell temperature for calculating module performance. If none, module
197
        temperature is calculated using temp_air and wind_speed
198
    CECMod2 : dict, optional
199
        CEC Module data as dictionary, for a monofacial module to be used as
200
        comparison for Bifacial Gain in Energy using only the calculated front
201
        Irradiance. If none, same module as CECMod
202
        is used.
203
    agriPV : Bool
204
        Turns off cleaning for ground material
205

206
    Returns
207
    -------
208
    dfst : dataframe
209
    Dataframe with the complied and calculated results for the sim, including:
210
        POA_eff: mean of [(mean of clean Gfront) + clean Grear * bifaciality
211
                          factor]
212
        Gfront_mean: mean of clean Gfront
213
        Grear_mean: mean of clean Grear
214
        Mismatch: mismatch calculated from the MAD distribution of
215
                  POA_total
216
        Pout_raw: power output calculated from POA_total, considers
217
              wind speed and temp_amb if in trackerdict.
218
        Pout: power output considering electrical mismatch
219
        BGG: Bifacial Gain in Irradiance, [Grear_mean*100*bifacialityfactor/
220
                                           Gfront_mean]
221
        BGE: Bifacial Gain in Energy, when power is calculated with CECMod2 or
222
            same module but just the front irradiance as input, so that
223
            [Pout-Pout_Gfront/Pout_Gfront]
224

225
    '''
226

227
    from bifacial_radiance import mismatch
2✔
228

229
    dfst = pd.DataFrame()
2✔
230

231
    if csvfile is not None:
2✔
232
        data = pd.read_csv(csvfile)
×
233
        Wm2Front = data['Wm2Front'].str.strip(
×
234
            '[]').str.split(',', expand=True).astype(float)
235
        Wm2Back = data['Wm2Back'].str.strip(
×
236
            '[]').str.split(',', expand=True).astype(float)
237
        mattype = data['mattype'].str.strip('[]').str.split(',', expand=True)
×
238
        rearMat = data['rearMat'].str.strip('[]').str.split(',', expand=True)
×
239

240
        if 'timestamp' in data:
×
241
            dfst['timestamp'] = data['timestamp']
×
242
        if 'modNum' in data:
×
243
            dfst['Module'] = data['modNum']
×
244
        if 'rowNum' in data:
×
245
            dfst['Row'] = data['rowNum']
×
246
        if 'sceneNum' in data:
×
247
            dfst['sceneNum'] = data['sceneNum']
×
248
    else:
249
        if results is not None:
2✔
250
            Wm2Front = pd.DataFrame.from_dict(dict(zip(
2✔
251
                results.index, results['Wm2Front']))).T
252
            Wm2Back = pd.DataFrame.from_dict(dict(zip(
2✔
253
                results.index, results['Wm2Back']))).T
254
            mattype = pd.DataFrame.from_dict(dict(zip(
2✔
255
                results.index, results['mattype']))).T
256
            rearMat = pd.DataFrame.from_dict(dict(zip(
2✔
257
                results.index, results['rearMat']))).T
258

259
            if 'timestamp' in results:
2✔
260
                dfst['timestamp'] = results['timestamp']
×
261
            if 'modNum' in results:
2✔
262
                dfst['module'] = results['modNum']
2✔
263
            if 'rowNum' in results:
2✔
264
                dfst['row'] = results['rowNum']
2✔
265
            if 'sceneNum' in results:
2✔
266
                dfst['sceneNum'] = results['sceneNum']
2✔
267

268
        else:
269
            print("Data or file not passed. Ending arrayResults")
×
270
            return
×
271

272
    filledFront, filledBack = _cleanDataFrameResults(
2✔
273
        mattype, rearMat, Wm2Front, Wm2Back,
274
        fillcleanedSensors=fillcleanedSensors, agriPV=agriPV)
275

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

278
    # Statistics Calculations
279
    # dfst['MAD/G_Total'] = bifacial_radiance.mismatch.mad_fn(POA.T)
280
    # 'MAD/G_Total
281
    dfst['POA_eff'] = POA.mean(axis=1)
2✔
282
    dfst['Grear_mean'] = Wm2Back.mean(axis=1)
2✔
283
    dfst['Gfront_mean'] = Wm2Front.mean(axis=1)
2✔
284

285
    # dfst['MAD/G_Total**2'] = dfst['MAD/G_Total']**2
286
    # dfst['stdev'] = POA.std(axis=1)/ dfst['poat']
287

288
    dfst['Pout_raw'] = module.calculatePerformance(
2✔
289
        effective_irradiance=dfst['POA_eff'], 
290
        temp_air=temp_air, wind_speed=wind_speed, temp_cell=temp_cell)
291
    dfst['Pout_Gfront'] = module.calculatePerformance(
2✔
292
        effective_irradiance=dfst['Gfront_mean'], CECMod=CECMod2,
293
        temp_air=temp_air, wind_speed=wind_speed, temp_cell=temp_cell)
294
    dfst['BGG'] = dfst['Grear_mean']*100*module.bifi/dfst['Gfront_mean']
2✔
295
    dfst['BGE'] = ((dfst['Pout_raw'] - dfst['Pout_Gfront']) * 100 /
2✔
296
                   dfst['Pout_Gfront'])
297
    dfst['Mismatch'] = mismatch.mismatch_fit2(POA.T) # value in percentage [%]
2✔
298
    dfst['Pout'] = dfst['Pout_raw']*(1-dfst['Mismatch']/100)
2✔
299
    dfst['Wind Speed'] = wind_speed
2✔
300
    if "dni" in kwargs:
2✔
301
        dfst['DNI'] = kwargs['dni']
2✔
302
    if "dhi" in kwargs:
2✔
303
        dfst['DHI'] = kwargs['dhi']
2✔
304
    if "ghi" in kwargs:
2✔
305
        dfst['GHI'] = kwargs['ghi']
2✔
306
    dfst['Wind Speed'] = wind_speed
2✔
307

308
    return dfst
2✔
309

310

311
def calculatePerformanceGencumsky(csvfile=None, results=None,
2✔
312
                                   bifacialityfactor=1.0,
313
                                   fillcleanedSensors=True, agriPV=False):
314
    '''
315
    Compile calculate results for cumulative 1 axis tracking routine
316

317
    Parameters
318
    ----------
319
    csvfile : numeric list
320
        Compiled Results data
321
    results : numeric list
322
        compiled Results data
323
    agriPV : Bool
324
        Turns off cleaning for ground material
325

326
    Returns
327
    -------
328
    dfst : dataframe
329
        Dataframe with the complied and calculated results for the sim,
330
        including: POA_eff: Avg of [(mean of clean Gfront) + clean Grear *
331
                                    bifaciality factor]
332
        Gfront_mean: mean of clean Gfront
333
        Grear_mean: mean of clean Grear
334
        BGG: Bifacial Gain in Irradiance, [Grear_mean * 100 *
335
                                           bifacialityfactor/Gfront_mean
336

337
    '''
338

339
    import pandas as pd
2✔
340

341
    dfst = pd.DataFrame()
2✔
342

343
    if csvfile is not None:
2✔
344
        data = pd.read_csv(csvfile)
×
345
        Wm2Front = data['Wm2Front'
×
346
                        ].str.strip('[]').str.split(',',
347
                                                    expand=True).astype(float)
348
        Wm2Back = data['Wm2Back'
×
349
                       ].str.strip('[]').str.split(',',
350
                                                   expand=True).astype(float)
351
        mattype = data['mattype'
×
352
                       ].str.strip('[]').str.split(',',
353
                                                   expand=True)
354
        rearMat = data['rearMat'
×
355
                       ].str.strip('[]').str.split(',',
356
                                                   expand=True)
357

358
        if 'modNum' in data:
×
359
            dfst['module'] = data['modNum']
×
360
        if 'rowNum' in data:
×
361
            dfst['row'] = data['rowNum']
×
362
        if 'sceneNum' in data:
×
363
            dfst['sceneNum'] = data['sceneNum']
×
364
    else:
365
        if results is not None:
2✔
366
            Wm2Front = pd.DataFrame.from_dict(dict(zip(
2✔
367
                results.index, results['Wm2Front']))).T
368
            Wm2Back = pd.DataFrame.from_dict(dict(zip(
2✔
369
                results.index, results['Wm2Back']))).T
370
            mattype = pd.DataFrame.from_dict(dict(zip(
2✔
371
                results.index, results['mattype']))).T
372
            rearMat = pd.DataFrame.from_dict(dict(zip(
2✔
373
                results.index, results['rearMat']))).T
374

375
            if 'modNum' in results:
2✔
376
                dfst['module'] = results['modNum']
2✔
377
            if 'rowNum' in results:
2✔
378
                dfst['row'] = results['rowNum']
2✔
379
            if 'sceneNum' in results:
2✔
380
                dfst['sceneNum'] = results['sceneNum']
2✔
381
                
382

383
        else:
NEW
384
            print("Data or file not passed. Ending calculatePerformanceGencumsky")
×
385
            return
×
386

387
    # Data gets cleaned but need to maintain same number of sensors
388
    # due to adding for the various tracker angles.
389
    filledFront, filledBack = _cleanDataFrameResults(
2✔
390
        mattype, rearMat, Wm2Front, Wm2Back,
391
        fillcleanedSensors=fillcleanedSensors, agriPV=agriPV)
392
    cumFront = []
2✔
393
    cumBack = []
2✔
394
    cumRow = []
2✔
395
    cumMod = []
2✔
396
    Grear_mean = []
2✔
397
#    Gfront_mean = []
398
    POA_eff = []
2✔
399

400
    # NOTE change 26.07.22 'row' -> 'rowNum' and 'mod' -> 'ModNumber
401
    # NOTE change March 13 2024 ModNumber -> modNum
402
    for rownum in results['rowNum'].unique():
2✔
403
        for modnum in results['modNum'].unique():
2✔
404
            mask = (results['rowNum'] == rownum) & (
2✔
405
                results['modNum'] == modnum)
406
            cumBack.append(list(filledBack[mask].sum(axis=0)))
2✔
407
            cumFront.append(filledFront[mask].sum(axis=0))
2✔
408
            cumRow.append(rownum)
2✔
409
            cumMod.append(modnum)
2✔
410

411
            # Maybe this would be faster by first doing the DF with the above,
412
            # exploding the column and calculating.
413
            POA_eff.append(list(
2✔
414
                (filledBack[mask].apply(lambda x: x*bifacialityfactor +
415
                                        filledFront[mask])).sum(axis=0)))
416
            Grear_mean.append(filledBack[mask].sum(axis=0).mean())
2✔
417
            # Gfront_mean.append(filledFront[mask].sum(axis=0).mean())
418

419
    dfst = pd.DataFrame(zip(cumRow, cumMod, cumFront, cumBack, Grear_mean,
2✔
420
                            POA_eff), columns=('row', 'module', 'Gfront_mean',
421
                                               'Wm2Back', 'Grear_mean',
422
                                               'POA_eff'))
423

424
    dfst['BGG'] = dfst['Grear_mean']*100*bifacialityfactor/dfst['Gfront_mean']
2✔
425

426
    # Reordering columns
427
    cols = ['row', 'module', 'BGG', 'Gfront_mean', 'Grear_mean', 'POA_eff',
2✔
428
            'Wm2Back']
429
    dfst = dfst[cols]
2✔
430

431
    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