Skip to content

Commit

Permalink
Merge branch 'main' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
cdeline committed Nov 9, 2023
2 parents 42ad4a5 + 4a6e004 commit d5aab17
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 38 deletions.
6 changes: 5 additions & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
version: 2

build:
os: ubuntu-20.04
tools:
python: "3.8"

python:
version: "3.8"
install:
- method: pip
path: .
Expand Down
5 changes: 4 additions & 1 deletion bifacial_radiance/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,10 @@ def _subhourlydatatoGencumskyformat(gencumskydata, label='right'):


#Resample to hourly. Gencumsky wants right-labeled data.
gencumskydata = gencumskydata.resample('60T', closed='right', label='right').mean()
try:
gencumskydata = gencumskydata.resample('60T', closed='right', label='right').mean()
except TypeError: # Pandas 2.0 error
gencumskydata = gencumskydata.resample('60T', closed='right', label='right').mean(numeric_only=True)

if label == 'left': #switch from left to right labeled by adding an hour
gencumskydata.index = gencumskydata.index + pd.to_timedelta('1H')
Expand Down
80 changes: 53 additions & 27 deletions bifacial_radiance/mismatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,52 +172,83 @@ def mismatch_fit3(data):
Parameters
----------
data : np.ndarray
data : np.ndarray, pd.Series, pd.DataFrame
Gtotal irradiance measurements. Each column is the irradiance for a module
at a specific time. (be carefule -- might have to .T the dataframe as input)
at a specific time.
Returns
-------
fit3 : 1Darray
fit3 : Float or pd.Series
Returns mismatch values for each module
Equation: 1/(n^2*Gavg)*Sum Sum (abs(G_i - G_j))
## Note: starting with Pandas 1.0.0 this function will not work on Series objects.
'''
import numpy as np
import pandas as pd

if type(data) == np.ndarray:
data = pd.DataFrame(data)

datac = data[~np.isnan(data)]
mad = mad_fn(datac) /100 # (percentage)
mad2 = mad**2

fit3 = 0.054*mad + 0.068*mad2

if fit3.__len__() == 1:
fit3 = float(fit3)

return fit3


def mad_fn(data):
def mad_fn(data, axis='index'):
'''
Mean average deviation calculation for mismatch purposes.
Parameters
----------
data : np.ndarray
Gtotal irradiance measurements.
data : np.ndarray or pd.Series or pd.DataFrame
Gtotal irradiance measurements. If data is a pandas.DataFrame, one
MAD/Average is returned for each index, based on values across columns.
axis : {0 or 'index', 1 or 'columns'}, default 'index'
Calculate mean average deviation across rows (default) or columns for 2D data
* 0, or 'index' : MAD calculated across rows.
* 1, or 'columns' : MAD calculated across columns.
Returns
-------
scalar : return MAD / Average for a 1D array
scalar or pd.Series: return MAD / Average [%]. Scalar for a 1D array, Series for 2D.
Equation: 1/(n^2*Gavg)*Sum Sum (abs(G_i - G_j))
## Note: starting with Pandas 1.0.0 this function will not work on Series objects.
Equation: 1/(n^2*Gavg)*Sum Sum (abs(G_i - G_j)) * 100[%]
'''
import numpy as np
import pandas as pd
# Pandas returns a notimplemented error if this is a series.
if type(data) == pd.Series:
def _mad_1D(data): #1D calculation of MAD
return (np.abs(np.subtract.outer(data,data)).sum()/float(data.__len__())**2 / np.mean(data))*100
if type(axis) == str:
try:
axis = {"index": 0, "rows": 0, 'columns':1}[axis]
except KeyError:
raise Exception('Incorrect index string in mad_fn. options: index, rows, columns.')

ndim = data.ndim
if ndim == 2 and axis==0:
data = data.T
# Pandas returns a notimplemented error if this is a DataFrame.
if (type(data) == pd.Series):
data = data.to_numpy()

return (np.abs(np.subtract.outer(data,data)).sum()/float(data.__len__())**2 / np.mean(data))*100
if type(data) == pd.DataFrame:
temp = data.apply(pd.Series.to_numpy, axis=1)
return(temp.apply(_mad_1D))
elif ndim ==2: #2D array
return [_mad_1D(i) for i in data]
else:
return _mad_1D(data)



Expand Down Expand Up @@ -346,24 +377,19 @@ def analysisIrradianceandPowerMismatch(testfolder, writefiletitle, portraitorlan
F.index='FrontIrradiance_cell_'+F.index.astype(str)
B.index='BackIrradiance_cell_'+B.index.astype(str)
Poat.index='POAT_Irradiance_cell_'+Poat.index.astype(str)

## Transpose
F = F.T
B = B.T
Poat = Poat.T

# Statistics Calculatoins
dfst=pd.DataFrame()
dfst['MAD/G_Total'] = mad_fn(Poat.T)
dfst['Front_MAD/G_Total'] = mad_fn(F.T)
dfst['MAD/G_Total'] = mad_fn(Poat)
dfst['Front_MAD/G_Total'] = mad_fn(F)
dfst['MAD/G_Total**2'] = dfst['MAD/G_Total']**2
dfst['Front_MAD/G_Total**2'] = dfst['Front_MAD/G_Total']**2
dfst['poat'] = Poat.mean(axis=1)
dfst['gfront'] = F.mean(axis=1)
dfst['grear'] = B.mean(axis=1)
dfst['poat'] = Poat.mean()
dfst['gfront'] = F.mean()
dfst['grear'] = B.mean()
dfst['bifi_ratio'] = dfst['grear']/dfst['gfront']
dfst['stdev'] = Poat.std(axis=1)/ dfst['poat']
dfst.index=Poat.index.astype(str)
dfst['stdev'] = Poat.std()/ dfst['poat']
dfst.index=Poat.columns.astype(str)

# Power Calculations/Saving
Pout=pd.DataFrame()
Expand All @@ -373,10 +399,10 @@ def analysisIrradianceandPowerMismatch(testfolder, writefiletitle, portraitorlan
Pout['Front_Pdet']=Pdet_front_all
Pout['Mismatch_rel'] = 100-(Pout['Pdet']*100/Pout['Pavg'])
Pout['Front_Mismatch_rel'] = 100-(Pout['Front_Pdet']*100/Pout['Front_Pavg'])
Pout.index=Poat.index.astype(str)
Pout.index=Poat.columns.astype(str)

## Save CSV
df_all = pd.concat([Pout,dfst,Poat,F,B],axis=1)
## Save CSV as one long row
df_all = pd.concat([Pout, dfst, Poat.T, F.T, B.T], axis=1)
df_all.to_csv(writefiletitle)
print("Saved Results to ", writefiletitle)

Expand Down
6 changes: 4 additions & 2 deletions docs/sphinx/source/whatsnew/pending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ API Changes
* Results generated with the above can be saved with the :py:class:`~bifacial_radiance.RadianceObj.exportTrackerDict`, which saves an Hourly, Monthly and Yearly .csvs in the results folder.
*Multiple modules and rows can now be selected in a single analysis scan. ``modWanted`` and ``rowWanted`` inputs in :py:class:`~bifacial_radiance.RadianceObj.analysis1axis` can now be a list, to select multiple rows and modules for scans. (:issue:`405`)(:pull:`408`)
*To support multiple modules and row scans for 1axis simulations, outputs like Wm2Front are now stored in ``trackerdict``.``Results`` (:issue:`405`)(:pull:`408`)
* ``mismatch.mad_fn`` has new functionality and input parameter `axis`. If a 2D matrix or dataframe is passed in as data, MAD is calculated along the row (default) or along the columns by passing 'axis=1'
Enhancements
~~~~~~~~~~~~
Expand All @@ -21,6 +21,7 @@ Enhancements

Bug fixes
~~~~~~~~~
* Fixed Pandas 2.0 errors by re-factoring ``mismatch.mad_fn`` (:issue:`449`)
* Fixed typo on Opacity calculation factor (:issue:`426`)

Documentation
Expand All @@ -31,4 +32,5 @@ Documentation
Contributors
~~~~~~~~~~~~
* Silvana Ayala (:ghuser:`shirubana`)
* Chris Deline (:ghuser:`cdeline`)
* Chris Deline (:ghuser:`cdeline`)
* Kevin Anderson (:ghuser:`kandersolar`)
32 changes: 25 additions & 7 deletions tests/test_mismatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,29 @@ def test_setupforPVMismatch():


def test_MAD():
ans_T = pd.Series([ 70.892019, 69.953052, 69.014085, 68.075117,
67.136150, 66.197183, 65.258216, 64.319249,
63.380282, 62.441315, 61.502347, 60.563380])
ans = pd.Series([72.222222, 22.698413, 13.465160,
9.571620, 7.424714, 6.064461])

assert bifacial_radiance.mismatch.mad_fn(TEST_ARRAY) == \
pytest.approx(2433.333,abs = 0.001)
pytest.approx(ans.to_numpy(), abs = 0.001)

assert bifacial_radiance.mismatch.mad_fn(TEST_ARRAY, axis='columns') == \
pytest.approx(ans_T.to_numpy(), abs = 0.001)

temp = bifacial_radiance.mismatch.mad_fn(pd.DataFrame(TEST_ARRAY))
ans = pd.Series([15706.061,4936.190,2928.249,2081.526,1614.642,1318.8295])
pd.testing.assert_series_equal(temp,ans,check_less_precise=True)
temp2 = bifacial_radiance.mismatch.mad_fn(pd.DataFrame(TEST_ARRAY), axis=1)
pd.testing.assert_series_equal(temp, ans)
pd.testing.assert_series_equal(temp2, ans_T)
# test pd.Series objects are correctly handled
assert bifacial_radiance.mismatch.mad_fn(ans) == \
pytest.approx(96.491,abs = 0.001)
# assert temp == \
# pytest.approx(2433.333,abs = 0.001)
assert bifacial_radiance.mismatch.mad_fn(ans_T) == \
pytest.approx(5.674, abs = 0.001)
assert bifacial_radiance.mismatch.mad_fn(ans_T.to_numpy()) == \
pytest.approx(5.674, abs = 0.001)



def test_analysisIrradianceandPowerMismatch():
#analysisIrradianceandPowerMismatch(testfolder, writefiletitle,
Expand All @@ -77,4 +88,11 @@ def test_analysisIrradianceandPowerMismatch():
df_all = pd.read_csv(writefiletitle)
assert df_all.Mismatch_rel[0] == pytest.approx(0.376, abs = 0.001)
assert df_all["MAD/G_Total"][0] == pytest.approx(1.987, abs = 0.001)


def test_mismatch_fit3():
ans = pd.Series([0.074469, 0.015761, 0.008504, 0.005792, 0.004384, 0.003525])
pd.testing.assert_series_equal( bifacial_radiance.mismatch.mismatch_fit3(TEST_ARRAY), ans, atol=1e-6)
pd.testing.assert_series_equal( bifacial_radiance.mismatch.mismatch_fit3(pd.DataFrame(TEST_ARRAY)), ans, atol=1e-6)
assert bifacial_radiance.mismatch.mismatch_fit3(TEST_ARRAY[:,0]) == pytest.approx(ans[0], abs = 0.001)

0 comments on commit d5aab17

Please sign in to comment.