diff --git a/docs/sphinx/source/package_overview.rst b/docs/sphinx/source/package_overview.rst index e187887804..b01defc554 100644 --- a/docs/sphinx/source/package_overview.rst +++ b/docs/sphinx/source/package_overview.rst @@ -104,9 +104,10 @@ to accomplish our system modeling goal: model='haydavies') temps = pvlib.pvsystem.sapm_celltemp(total_irrad['poa_global'], wind_speed, temp_air) - dc = pvlib.pvsystem.sapm(module, total_irrad['poa_direct'], - total_irrad['poa_diffuse'], temps['temp_cell'], - am_abs, aoi) + effective_irradiance = pvlib.pvsystem.sapm_effective_irradiance( + module, total_irrad['poa_direct'], total_irrad['poa_diffuse'], + am_abs, aoi) + dc = pvlib.pvsystem.sapm(module, effective_irradiance, temps['temp_cell']) ac = pvlib.pvsystem.snlinverter(inverter, dc['v_mp'], dc['p_mp']) annual_energy = ac.sum() energies[name] = annual_energy @@ -240,11 +241,10 @@ object to accomplish our modeling goal: aoi = localized_system.get_aoi(solar_position['apparent_zenith'], solar_position['azimuth']) airmass = localized_system.get_airmass(solar_position=solar_position) - dc = localized_system.sapm(total_irrad['poa_direct'], - total_irrad['poa_diffuse'], - temps['temp_cell'], - airmass['airmass_absolute'], - aoi) + effective_irradiance = localized_system.sapm_effective_irradiance( + total_irrad['poa_direct'], total_irrad['poa_diffuse'], + airmass['airmass_absolute'], aoi) + dc = localized_system.sapm(effective_irradiance, temps['temp_cell']) ac = localized_system.snlinverter(dc['v_mp'], dc['p_mp']) annual_energy = ac.sum() energies[name] = annual_energy diff --git a/docs/sphinx/source/whatsnew/v0.4.0.txt b/docs/sphinx/source/whatsnew/v0.4.0.txt index dd551f085c..46f3f0eaa9 100644 --- a/docs/sphinx/source/whatsnew/v0.4.0.txt +++ b/docs/sphinx/source/whatsnew/v0.4.0.txt @@ -3,9 +3,10 @@ v0.4.0 (July xx, 2016) ----------------------- -This is a major release from 0.3.3. -We recommend that all users upgrade to this version after reviewing -the API changes. +This is a major release from 0.3.3. We recommend that all users upgrade +to this version after reviewing the API Changes. Please see the Bug +Fixes for changes that will result in slightly different modeling +results. API Changes @@ -18,6 +19,14 @@ API Changes in addition to arrays and Series. Furthermore, these functions no longer promote scalar or array input to Series output. Also applies to atmosphere.relativeairmass. (:issue:`201`, :issue:`214`) +* Updated to pvsystem.sapm to be consistent with the PVLIB MATLAB API. + pvsystem.sapm now takes an effective irradiance argument instead of + POA irradiances, airmass, and AOI. Implements closely related + sapm_spectral_loss, sapm_aoi_loss, and sapm_effective_irradiance + functions, as well as PVSystem methods. The sapm_aoi_loss function + includes an optional argument to apply an upper limit to the output + (output can be ~1% larger than 1 for AOI of ~30 degrees). + (:issue:`198`, :issue:`205`, :issue:`218`) Enhancements diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 4c1836cc11..161da931cc 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -169,11 +169,12 @@ def basic_chain(times, latitude, longitude, weather['wind_speed'], weather['temp_air']) - dc = pvsystem.sapm(module_parameters, total_irrad['poa_direct'], - total_irrad['poa_diffuse'], - temps['temp_cell'], - airmass, - aoi) + effective_irradiance = pvsystem.sapm_effective_irradiance( + module_parameters, total_irrad['poa_direct'], + total_irrad['poa_diffuse'], airmass, aoi) + + dc = pvsystem.sapm(module_parameters, effective_irradiance, + temps['temp_cell']) ac = pvsystem.snlinverter(inverter_parameters, dc['v_mp'], dc['p_mp']) @@ -365,6 +366,9 @@ def run_model(self, times, irradiance=None, weather=None): model=self.transposition_model, airmass=self.airmass['airmass_relative']) + self.aoi = self.system.get_aoi(self.solar_position['apparent_zenith'], + self.solar_position['azimuth']) + if weather is None: weather = {'wind_speed': 0, 'temp_air': 20} self.weather = weather @@ -373,14 +377,14 @@ def run_model(self, times, irradiance=None, weather=None): self.weather['wind_speed'], self.weather['temp_air']) - self.aoi = self.system.get_aoi(self.solar_position['apparent_zenith'], - self.solar_position['azimuth']) + self.effective_irradiance = self.system.sapm_effective_irradiance( + self.total_irrad['poa_direct'], + self.total_irrad['poa_diffuse'], + self.airmass['airmass_absolute'], + self.aoi) - self.dc = self.system.sapm(self.total_irrad['poa_direct'], - self.total_irrad['poa_diffuse'], - self.temps['temp_cell'], - self.airmass['airmass_absolute'], - self.aoi) + self.dc = self.system.sapm(self.effective_irradiance, + self.temps['temp_cell']) self.dc = self.system.scale_voltage_current_power(self.dc) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 8b94eb08df..812c74583f 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -5,6 +5,7 @@ from __future__ import division +from collections import OrderedDict import io try: from urllib2 import urlopen @@ -288,8 +289,7 @@ def calcparams_desoto(self, poa_global, temp_cell, **kwargs): self.module_parameters['EgRef'], self.module_parameters['dEgdT'], **kwargs) - def sapm(self, poa_direct, poa_diffuse, - temp_cell, airmass_absolute, aoi, **kwargs): + def sapm(self, effective_irradiance, temp_cell, **kwargs): """ Use the :py:func:`sapm` function, the input parameters, and ``self.module_parameters`` to calculate @@ -319,10 +319,8 @@ def sapm(self, poa_direct, poa_diffuse, ------- See pvsystem.sapm for details """ - return sapm(self.module_parameters, poa_direct, poa_diffuse, - temp_cell, airmass_absolute, aoi) + return sapm(self.module_parameters, effective_irradiance, temp_cell) - # model now specified by self.racking_model def sapm_celltemp(self, irrad, wind, temp): """Uses :py:func:`sapm_celltemp` to calculate module and cell temperatures based on ``self.racking_model`` and @@ -338,6 +336,75 @@ def sapm_celltemp(self, irrad, wind, temp): """ return sapm_celltemp(irrad, wind, temp, self.racking_model) + def sapm_spectral_loss(self, airmass_absolute): + """ + Use the :py:func:`sapm_spectral_loss` function, the input + parameters, and ``self.module_parameters`` to calculate F1. + + Parameters + ---------- + airmass_absolute : numeric + Absolute airmass. + + Returns + ------- + F1 : numeric + The SAPM spectral loss coefficient. + """ + return sapm_spectral_loss(self.module_parameters, airmass_absolute) + + def sapm_aoi_loss(self, aoi): + """ + Use the :py:func:`sapm_aoi_loss` function, the input parameters, + and ``self.module_parameters`` to calculate F2. + + Parameters + ---------- + aoi : numeric + Angle of incidence in degrees. + + Returns + ------- + F2 : numeric + The SAPM angle of incidence loss coefficient. + """ + return sapm_aoi_loss(self.module_parameters, aoi) + + def sapm_effective_irradiance(self, poa_direct, poa_diffuse, + airmass_absolute, aoi, + reference_irradiance=1000): + """ + Use the :py:func:`sapm_effective_irradiance` function, the input + parameters, and ``self.module_parameters`` to calculate + effective irradiance. + + Parameters + ---------- + poa_direct : numeric + The direct irradiance incident upon the module. + + poa_diffuse : numeric + The diffuse irradiance incident on module. + + airmass_absolute : numeric + Absolute airmass. + + aoi : numeric + Angle of incidence in degrees. + + reference_irradiance : numeric + Reference irradiance by which to divide the input irradiance. + + Returns + ------- + effective_irradiance : numeric + The SAPM effective irradiance. + """ + return sapm_effective_irradiance(self.module_parameters, + poa_direct, poa_diffuse, + airmass_absolute, aoi, + reference_irradiance) + def singlediode(self, photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth): """Wrapper around the :py:func:`singlediode` function. @@ -1084,7 +1151,7 @@ def _parse_raw_sam_df(csvdata): return df -def sapm(module, poa_direct, poa_diffuse, temp_cell, airmass_absolute, aoi): +def sapm(module, effective_irradiance, temp_cell): ''' The Sandia PV Array Performance Model (SAPM) generates 5 points on a PV module's I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to @@ -1092,25 +1159,16 @@ def sapm(module, poa_direct, poa_diffuse, temp_cell, airmass_absolute, aoi): Parameters ---------- - module : Series or dict - A DataFrame defining the SAPM performance parameters. See the - notes section for more details. + module : dict-like + A dict, Series, or DataFrame defining the SAPM performance + parameters. See the notes section for more details. - poa_direct : Series - The direct irradiance incident upon the module (W/m^2). - - poa_diffuse : Series - The diffuse irradiance incident on module. + effective_irradiance : numeric + Effective irradiance (suns). - temp_cell : Series + temp_cell : numeric The cell temperature (degrees C). - airmass_absolute : Series - Absolute airmass. - - aoi : Series - Angle of incidence (degrees). - Returns ------- A DataFrame with the columns: @@ -1124,7 +1182,6 @@ def sapm(module, poa_direct, poa_diffuse, temp_cell, airmass_absolute, aoi): curve for modeling curve shape * i_xx : Current at module V = 0.5(Voc+Vmp), defines 5th point on I-V curve for modeling curve shape - * effective_irradiance : Effective irradiance Notes ----- @@ -1184,60 +1241,46 @@ def sapm(module, poa_direct, poa_diffuse, temp_cell, airmass_absolute, aoi): kb = 1.38066e-23 # Boltzmann's constant in units of J/K E0 = 1000 - am_coeff = [module['A4'], module['A3'], module['A2'], module['A1'], - module['A0']] - aoi_coeff = [module['B5'], module['B4'], module['B3'], module['B2'], - module['B1'], module['B0']] - - F1 = np.polyval(am_coeff, airmass_absolute) - F2 = np.polyval(aoi_coeff, aoi) - - # Ee is the "effective irradiance" - Ee = F1 * ((poa_direct*F2 + module['FD']*poa_diffuse) / E0) - Ee.fillna(0, inplace=True) - Ee = Ee.clip_lower(0) + Ee = effective_irradiance Bvmpo = module['Bvmpo'] + module['Mbvmp']*(1 - Ee) Bvoco = module['Bvoco'] + module['Mbvoc']*(1 - Ee) delta = module['N'] * kb * (temp_cell + 273.15) / q - dfout = pd.DataFrame(index=Ee.index) + out = OrderedDict() - dfout['i_sc'] = ( - module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - T0)) - ) + out['i_sc'] = ( + module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - T0))) - dfout['i_mp'] = ( + out['i_mp'] = ( module['Impo'] * (module['C0']*Ee + module['C1']*(Ee**2)) * - (1 + module['Aimp']*(temp_cell - T0)) - ) + (1 + module['Aimp']*(temp_cell - T0))) - dfout['v_oc'] = ( + out['v_oc'] = np.maximum(0, ( module['Voco'] + module['Cells_in_Series']*delta*np.log(Ee) + - Bvoco*(temp_cell - T0) - ).clip_lower(0) + Bvoco*(temp_cell - T0))) - dfout['v_mp'] = ( + out['v_mp'] = np.maximum(0, ( module['Vmpo'] + module['C2']*module['Cells_in_Series']*delta*np.log(Ee) + module['C3']*module['Cells_in_Series']*((delta*np.log(Ee)) ** 2) + - Bvmpo*(temp_cell - T0) - ).clip_lower(0) + Bvmpo*(temp_cell - T0))) - dfout['p_mp'] = dfout['i_mp'] * dfout['v_mp'] + out['p_mp'] = out['i_mp'] * out['v_mp'] - dfout['i_x'] = (module['IXO'] * - (module['C4']*Ee + module['C5']*(Ee**2)) * - (1 + module['Aisc']*(temp_cell - T0))) + out['i_x'] = ( + module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) * + (1 + module['Aisc']*(temp_cell - T0))) # the Ixx calculation in King 2004 has a typo (mixes up Aisc and Aimp) - dfout['i_xx'] = (module['IXXO'] * - (module['C6']*Ee + module['C7']*(Ee**2)) * - (1 + module['Aisc']*(temp_cell - T0))) + out['i_xx'] = ( + module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) * + (1 + module['Aisc']*(temp_cell - T0))) - dfout['effective_irradiance'] = Ee + if isinstance(out['i_sc'], pd.Series): + out = pd.DataFrame(out) - return dfout + return out def sapm_celltemp(poa_global, wind_speed, temp_air, @@ -1333,6 +1376,146 @@ def sapm_celltemp(poa_global, wind_speed, temp_air, return pd.DataFrame({'temp_cell': temp_cell, 'temp_module': temp_module}) +def sapm_spectral_loss(module, airmass_absolute): + """ + Calculates the SAPM spectral loss coefficient, F1. + + Parameters + ---------- + module : dict-like + A dict, Series, or DataFrame defining the SAPM performance + parameters. See the :py:func:`sapm` notes section for more + details. + + airmass_absolute : numeric + Absolute airmass + + Returns + ------- + F1 : numeric + The SAPM spectral loss coefficient. + + Notes + ----- + nan airmass values will result in 0 output. + """ + + am_coeff = [module['A4'], module['A3'], module['A2'], module['A1'], + module['A0']] + + spectral_loss = np.maximum(0, np.polyval(am_coeff, airmass_absolute)) + + spectral_loss = np.where(np.isnan(spectral_loss), 0, spectral_loss) + + if isinstance(airmass_absolute, pd.Series): + spectral_loss = pd.Series(spectral_loss, airmass_absolute.index) + + return spectral_loss + + +def sapm_aoi_loss(module, aoi, upper=None): + """ + Calculates the SAPM angle of incidence loss coefficient, F2. + + Parameters + ---------- + module : dict-like + A dict, Series, or DataFrame defining the SAPM performance + parameters. See the :py:func:`sapm` notes section for more + details. + + aoi : numeric + Angle of incidence in degrees. Negative input angles will return + nan values. + + upper : None or float + Upper limit on the results. + + Returns + ------- + F2 : numeric + The SAPM angle of incidence loss coefficient. + + Notes + ----- + The SAPM traditionally does not define an upper limit on the AOI + loss function and values slightly exceeding 1 may exist for moderate + angles of incidence (15-40 degrees). However, users may consider + imposing an upper limit of 1. + + References + ---------- + [1] King, D. et al, 2004, "Sandia Photovoltaic Array Performance + Model", SAND Report 3535, Sandia National Laboratories, Albuquerque, + NM. + + [2] B.H. King et al, "Procedure to Determine Coefficients for the + Sandia Array Performance Model (SAPM)," SAND2016-5284, Sandia + National Laboratories (2016). + + [3] B.H. King et al, "Recent Advancements in Outdoor Measurement + Techniques for Angle of Incidence Effects," 42nd IEEE PVSC (2015). + DOI: 10.1109/PVSC.2015.7355849 + """ + + aoi_coeff = [module['B5'], module['B4'], module['B3'], module['B2'], + module['B1'], module['B0']] + + aoi_loss = np.polyval(aoi_coeff, aoi) + aoi_loss = np.clip(aoi_loss, 0, upper) + aoi_loss = np.where(aoi < 0, np.nan, aoi_loss) + + if isinstance(aoi, pd.Series): + aoi_loss = pd.Series(aoi_loss, aoi.index) + + return aoi_loss + + +def sapm_effective_irradiance(module, poa_direct, poa_diffuse, + airmass_absolute, aoi, + reference_irradiance=1000): + """ + Calculates the SAPM effective irradiance using the SAPM spectral + loss and SAPM angle of incidence loss functions. + + Parameters + ---------- + module : dict-like + A dict, Series, or DataFrame defining the SAPM performance + parameters. See the :py:func:`sapm` notes section for more + details. + + poa_direct : numeric + The direct irradiance incident upon the module. + + poa_diffuse : numeric + The diffuse irradiance incident on module. + + airmass_absolute : numeric + Absolute airmass. + + aoi : numeric + Angle of incidence in degrees. + + reference_irradiance : numeric + Reference irradiance by which to divide the input irradiance. + + Returns + ------- + effective_irradiance : numeric + The SAPM effective irradiance. + """ + + F1 = sapm_spectral_loss(module, airmass_absolute) + F2 = sapm_aoi_loss(module, aoi) + + E0 = reference_irradiance + + Ee = F1 * (poa_direct*F2 + module['FD']*poa_diffuse) / E0 + + return Ee + + def singlediode(photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth): r''' diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index f80786c64b..3c8aa08a71 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -1,6 +1,7 @@ import inspect import os import datetime +from collections import OrderedDict import numpy as np from numpy import nan @@ -139,58 +140,161 @@ def sam_data(): return data -def test_sapm(sam_data): +@pytest.fixture(scope="session") +def sapm_module_params(sam_data): modules = sam_data['sandiamod'] - module_parameters = modules['Canadian_Solar_CS5P_220M___2009_'] - times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H') - irrad_data = pd.DataFrame({'dni':[0,1000], 'ghi':[0,600], 'dhi':[0,100]}, - index=times) - am = pd.Series([0, 2.25], index=times) - aoi = pd.Series([180, 30], index=times) + module = 'Canadian_Solar_CS5P_220M___2009_' + module_parameters = modules[module] + return module_parameters + + +def test_sapm(sapm_module_params): - sapm = pvsystem.sapm(module_parameters, irrad_data['dni'], - irrad_data['dhi'], 25, am, aoi) + times = pd.DatetimeIndex(start='2015-01-01', periods=5, freq='12H') + effective_irradiance = pd.Series([-1, 0.5, 1.1, np.nan, 1], index=times) + temp_cell = pd.Series([10, 25, 50, 25, np.nan], index=times) + + out = pvsystem.sapm(sapm_module_params, effective_irradiance, temp_cell) expected = pd.DataFrame(np.array( - [[ 0. , 0. , 0. , 0. , - 0. , 0. , 0. , 0. ], - [ 5.74526799, 5.12194115, 59.67914031, 48.41924255, - 248.00051089, 5.61787615, 3.52581308, 1.12848138]]), - columns=['i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp', 'i_x', 'i_xx', - 'effective_irradiance'], + [[ -5.0608322 , -4.65037767, nan, nan, + nan, -4.91119927, -4.15367716], + [ 2.545575 , 2.28773882, 56.86182059, 47.21121608, + 108.00693168, 2.48357383, 1.71782772], + [ 5.65584763, 5.01709903, 54.1943277 , 42.51861718, + 213.32011294, 5.52987899, 3.48660728], + [ nan, nan, nan, nan, + nan, nan, nan], + [ nan, nan, nan, nan, + nan, nan, nan]]), + columns=['i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp', 'i_x', 'i_xx'], index=times) - assert_frame_equal(sapm, expected) + assert_frame_equal(out, expected, check_less_precise=4) + + out = pvsystem.sapm(sapm_module_params, 1, 25) + + expected = OrderedDict() + expected['i_sc'] = 5.09115 + expected['i_mp'] = 4.5462909092579995 + expected['v_oc'] = 59.260800000000003 + expected['v_mp'] = 48.315600000000003 + expected['p_mp'] = 219.65677305534581 + expected['i_x'] = 4.9759899999999995 + expected['i_xx'] = 3.1880204359100004 + + for k, v in expected.items(): + assert_allclose(out[k], v, atol=1e-4) # just make sure it works with a dict input - sapm = pvsystem.sapm(module_parameters.to_dict(), irrad_data['dni'], - irrad_data['dhi'], 25, am, aoi) + pvsystem.sapm(sapm_module_params.to_dict(), effective_irradiance, + temp_cell) -def test_PVSystem_sapm(sam_data): - modules = sam_data['sandiamod'] - module = 'Canadian_Solar_CS5P_220M___2009_' - module_parameters = modules[module] - system = pvsystem.PVSystem(module=module, - module_parameters=module_parameters) +def test_PVSystem_sapm(sapm_module_params): + system = pvsystem.PVSystem(module_parameters=sapm_module_params) + + times = pd.DatetimeIndex(start='2015-01-01', periods=5, freq='12H') + effective_irradiance = pd.Series([-1, 0.5, 1.1, np.nan, 1], index=times) + temp_cell = pd.Series([10, 25, 50, 25, np.nan], index=times) + + out = system.sapm(effective_irradiance, temp_cell) + + +@pytest.mark.parametrize('airmass,expected', [ + (1.5, 1.00028714375), + (np.array([[10, np.nan]]), np.array([[0.999535, 0]])), + (pd.Series([5]), pd.Series([1.0387675])) +]) +def test_sapm_spectral_loss(sapm_module_params, airmass, expected): + + out = pvsystem.sapm_spectral_loss(sapm_module_params, airmass) + + if isinstance(airmass, pd.Series): + assert_series_equal(out, expected, check_less_precise=4) + else: + assert_allclose(out, expected, atol=1e-4) + + +def test_PVSystem_sapm_spectral_loss(sapm_module_params): + system = pvsystem.PVSystem(module_parameters=sapm_module_params) + times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H') - irrad_data = pd.DataFrame({'dni':[0,1000], 'ghi':[0,600], 'dhi':[0,100]}, - index=times) - am = pd.Series([0, 2.25], index=times) - aoi = pd.Series([180, 30], index=times) + airmass = pd.Series([1, 10], index=times) - sapm = system.sapm(irrad_data['dni'], irrad_data['dhi'], 25, am, aoi) + out = system.sapm_spectral_loss(airmass) - expected = pd.DataFrame(np.array( - [[ 0. , 0. , 0. , 0. , - 0. , 0. , 0. , 0. ], - [ 5.74526799, 5.12194115, 59.67914031, 48.41924255, - 248.00051089, 5.61787615, 3.52581308, 1.12848138]]), - columns=['i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp', 'i_x', 'i_xx', - 'effective_irradiance'], - index=times) - assert_frame_equal(sapm, expected) +@pytest.mark.parametrize('aoi,expected', [ + (45, 0.9975036250000002), + (np.array([[-30, 30, 100, np.nan]]), + np.array([[np.nan, 1.007572, 0, np.nan]])), + (pd.Series([80]), pd.Series([0.597472])) +]) +def test_sapm_aoi_loss(sapm_module_params, aoi, expected): + + out = pvsystem.sapm_aoi_loss(sapm_module_params, aoi) + + if isinstance(aoi, pd.Series): + assert_series_equal(out, expected, check_less_precise=4) + else: + assert_allclose(out, expected, atol=1e-4) + + +def test_sapm_aoi_loss_limits(): + module_parameters = {'B0': 5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0} + assert pvsystem.sapm_aoi_loss(module_parameters, 1) == 5 + + module_parameters = {'B0': 5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0} + assert pvsystem.sapm_aoi_loss(module_parameters, 1, upper=1) == 1 + + module_parameters = {'B0': -5, 'B1': 0, 'B2': 0, 'B3': 0, 'B4': 0, 'B5': 0} + assert pvsystem.sapm_aoi_loss(module_parameters, 1) == 0 + + +def test_PVSystem_sapm_aoi_loss(sapm_module_params): + system = pvsystem.PVSystem(module_parameters=sapm_module_params) + + times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H') + aoi = pd.Series([45, 10], index=times) + + out = system.sapm_aoi_loss(aoi) + + +@pytest.mark.parametrize('test_input,expected', [ + ([1000, 100, 5, 45, 1000], 1.1400510967821877), + ([np.array([np.nan, 1000, 1000]), + np.array([100, np.nan, 100]), + np.array([1.1, 1.1, 1.1]), + np.array([10, 10, 10]), + 1000], + np.array([np.nan, np.nan, 1.081157])), + ([pd.Series([1000]), pd.Series([100]), pd.Series([1.1]), + pd.Series([10]), 1370], + pd.Series([0.789166])) +]) +def test_sapm_effective_irradiance(sapm_module_params, test_input, expected): + + out = pvsystem.sapm_effective_irradiance(sapm_module_params, *test_input) + + if isinstance(test_input, pd.Series): + assert_series_equal(out, expected, check_less_precise=4) + else: + assert_allclose(out, expected, atol=1e-4) + + +def test_PVSystem_sapm_effective_irradiance(sapm_module_params): + system = pvsystem.PVSystem(module_parameters=sapm_module_params) + + poa_direct = np.array([np.nan, 1000, 1000]) + poa_diffuse = np.array([100, np.nan, 100]) + airmass_absolute = np.array([1.1, 1.1, 1.1]) + aoi = np.array([10, 10, 10]) + reference_irradiance = 1000 + + out = system.sapm_effective_irradiance( + poa_direct, poa_diffuse, airmass_absolute, + aoi, reference_irradiance=reference_irradiance) def test_calcparams_desoto(sam_data):