From 2698d3226eed63c6f731c748c2eb01e4ea0fe0bd Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 25 May 2022 16:29:32 +0200 Subject: [PATCH 01/14] First commit to gather feedback on class design. --- pvlib/temperature.py | 248 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 6a4ed1fd6e..c719553814 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -977,3 +977,251 @@ def prilliman(temp_cell, wind_speed, unit_mass=11.1, coefficients=None): smoothed[0] = temp_cell.values[0] smoothed = pd.Series(smoothed, index=temp_cell.index) return smoothed + + +class GenericLinearModel(): + ''' + A generic linear temperature model class that can both use and convert + parameters of several well-known equivalent or nearly equivalent models. + + Examples + -------- + + glm = GenericLinearModel(module_efficiency=0.19, absorptance=0.88) + + glm.set_faiman(16, 8) + + glm.get_pvsyst() + + parmdict = glm.get_pvsyst() + + glm.set_pvsyst(29, 0).get_faiman() + + Tests + ----- + wind = np.array([1.4, 1/.51, 5.4]) + weather = (765, 32, wind) + + glm = GenericLinearModel(module_efficiency=0.18) + + glm.set_faiman(25, 6.84) + + print(glm) + print(glm(*weather)) + print(faiman(*weather, **glm.get_faiman())) + print(pvsyst_cell(*weather, **glm.get_pvsyst())) + print(sapm_module(*weather, **glm.get_sapm())) + print(noct_sam(*weather, **glm.get_noct_sam())) + + glm.set_pvsyst(29, 0) + + print(glm) + print(glm(*weather)) + print(faiman(*weather, **glm.get_faiman())) + print(pvsyst_cell(*weather, **glm.get_pvsyst())) + print(sapm_module(*weather, **glm.get_sapm())) + print(noct_sam(*weather, **glm.get_noct_sam())) + + glm.set_sapm(-3.56, -0.075) + + print(glm) + print(glm(*weather)) + print(faiman(*weather, **glm.get_faiman())) + print(pvsyst_cell(*weather, **glm.get_pvsyst())) + print(sapm_module(*weather, **glm.get_sapm())) + print(noct_sam(*weather, **glm.get_noct_sam())) + + glm.set_noct_sam(45) + + print(glm) + print(glm(*weather)) + print(faiman(*weather, **glm.get_faiman())) + print(pvsyst_cell(*weather, **glm.get_pvsyst())) + print(sapm_module(*weather, **glm.get_sapm())) + print(noct_sam(*weather, **glm.get_noct_sam())) + ''' + + def __init__(self, module_efficiency=0.15, absorptance=0.9): + ''' + init docstring + + The two parameters are pv module properties that are relevant for + temperature modeling even if certain models do not explicitly + mention or use them. + + Since these are empirical models, their parameters are determined + from a limited set of measurements (usually high irradiance). + The values of efficiency and absorptance should be interpreted + as representative of the efficiency and absorptance actually + occuring during those measurements. + + When used for simulation, efficiency and absorptance may be + different, and each model handles this differently. + ''' + self.u_const = np.nan + self.du_wind = np.nan + self.eta = module_efficiency + self.alpha = absorptance + return None + + + def __repr__(self): + ''' + Return an informative string for a particular model instance. + ''' + return self.__class__.__name__ + ': ' +vars(self).__repr__() + + + def __call__(self, poa_global, temp_air, wind_speed, + module_efficiency=None): + ''' + Calculate module temperature using the generic linear model and + previously initialized parameters. + ''' + if module_efficiency is None: + module_efficiency = self.eta + + heat_input = poa_global * (self.alpha - module_efficiency) + total_loss_factor = self.u_const + self.du_wind * wind_speed + temp_difference = heat_input / total_loss_factor + + return temp_air + temp_difference + + + def set_faiman(self, u0, u1, **kwargs): + ''' + Set the generic model parameters to match the Faiman model. + ''' + net_absorptance = self.alpha - self.eta + self.u_const = u0 * net_absorptance + self.du_wind = u1 * net_absorptance + + return self + + + def get_faiman(self): + ''' + Get the Faiman model parameters matching the generic ones. + ''' + net_absorptance = self.alpha - self.eta + u0 = self.u_const / net_absorptance + u1 = self.du_wind / net_absorptance + + return dict(u0=u0, u1=u1) + + + def set_pvsyst(self, u_c, u_v, module_efficiency=None, + alpha_absorption=None): + ''' + Set the generic model parameters to match the PVsyst model. + + The optional parameters are primarily for convenient compatibility + with the other function signatures. + ''' + if module_efficiency is not None: + self.eta = module_efficiency + + if alpha_absorption is not None: + self.alpha = alpha_absorption + + net_absorptance_glm = self.alpha - self.eta + net_absorptance_pvsyst = self.alpha * (1.0 - self.eta) + absorptance_ratio = net_absorptance_glm / net_absorptance_pvsyst + + self.u_const = u_c * absorptance_ratio + self.du_wind = u_v * absorptance_ratio + + return self + + + def get_pvsyst(self): + ''' + Get the PVsyst model parameters matching the generic ones. + ''' + net_absorptance_glm = self.alpha - self.eta + net_absorptance_pvsyst = self.alpha * (1.0 - self.eta) + absorptance_ratio = net_absorptance_glm / net_absorptance_pvsyst + + u_c = self.u_const / absorptance_ratio + u_v = self.du_wind / absorptance_ratio + + return dict(u_c=u_c, + u_v=u_v, + module_efficiency=self.eta, + alpha_absorption=self.alpha) + + + def set_noct_sam(self, noct, module_efficiency=None, + transmittance_absorptance=None): + ''' + Set the generic model parameters to match the NOCT SAM model. + + The optional parameters are primarily for convenient compatibility + with the other function signatures. + ''' + if module_efficiency is not None: + self.eta = module_efficiency + + if transmittance_absorptance is not None: + self.alpha = transmittance_absorptance + + # NOCT is determined with wind speed near module height + # the adjustment reduces the wind coefficient for use with 10m wind + wind_adj=0.51 + u_noct = 800.0 * self.alpha / (noct - 20.0) + self.u_const = u_noct * 0.6 + self.du_wind = u_noct * 0.4 * wind_adj + return self + + + def get_noct_sam(self): + ''' + Get the NOCT SAM model parameters to approximate the generic model. + ''' + # NOCT is determined with wind speed near module height + # the adjustment reduces the wind coefficient for use with 10m wind + wind_adj=0.51 + u_noct = self.u_const + self.du_wind / wind_adj + noct = 20.0 + (800.0 * self.alpha) / u_noct + return dict(noct=noct, + module_efficiency=self.eta, + transmittance_absorptance=self.alpha) + + + def set_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): + ''' + Set the generic model parameters to approximate the SAPM module model. + + The generic model will equal the SAPM model at the two + given 10m wind speed values. + ''' + u_low = 1.0 / np.exp(a + b * wind_fit_low) + u_high = 1.0 / np.exp(a + b * wind_fit_high) + + du_wind = (u_high - u_low) / (wind_fit_high - wind_fit_low) + u_const = u_low - du_wind * wind_fit_low + + net_absorptance = self.alpha - self.eta + self.u_const = u_const * net_absorptance + self.du_wind = du_wind * net_absorptance + return self + + + def get_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): + ''' + Get the SAPM module model parameters to approximate the generic model. + + The SAPM model will equal the generic model at the two + given 10m wind speed values. + ''' + net_absorptance = self.alpha - self.eta + u_const = self.u_const / net_absorptance + du_wind = self.du_wind / net_absorptance + + u_low = u_const + du_wind * wind_fit_low + u_high = u_const + du_wind * wind_fit_high + + b = - ((np.log(u_high) - np.log(u_low)) / + (wind_fit_high - wind_fit_low)) + a = - (np.log(u_low) + b * wind_fit_low) + return dict(a=a, b=b) From 4ca021631d65088226a1a689856035b1f8c25776 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 22 Jun 2022 14:33:22 +0200 Subject: [PATCH 02/14] Changed set_*/get_* pattern to use_*/to_* --- pvlib/temperature.py | 64 ++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index c719553814..683a421441 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -989,13 +989,13 @@ class GenericLinearModel(): glm = GenericLinearModel(module_efficiency=0.19, absorptance=0.88) - glm.set_faiman(16, 8) + glm.use_faiman(16, 8) - glm.get_pvsyst() + glm.to_pvsyst() - parmdict = glm.get_pvsyst() + parmdict = glm.to_pvsyst() - glm.set_pvsyst(29, 0).get_faiman() + glm.use_pvsyst(29, 0).to_faiman() Tests ----- @@ -1004,41 +1004,41 @@ class GenericLinearModel(): glm = GenericLinearModel(module_efficiency=0.18) - glm.set_faiman(25, 6.84) + glm.use_faiman(25, 6.84) print(glm) print(glm(*weather)) - print(faiman(*weather, **glm.get_faiman())) - print(pvsyst_cell(*weather, **glm.get_pvsyst())) - print(sapm_module(*weather, **glm.get_sapm())) - print(noct_sam(*weather, **glm.get_noct_sam())) + print(faiman(*weather, **glm.to_faiman())) + print(pvsyst_cell(*weather, **glm.to_pvsyst())) + print(sapm_module(*weather, **glm.to_sapm())) + print(noct_sam(*weather, **glm.to_noct_sam())) - glm.set_pvsyst(29, 0) + glm.use_pvsyst(29, 0) print(glm) print(glm(*weather)) - print(faiman(*weather, **glm.get_faiman())) - print(pvsyst_cell(*weather, **glm.get_pvsyst())) - print(sapm_module(*weather, **glm.get_sapm())) - print(noct_sam(*weather, **glm.get_noct_sam())) + print(faiman(*weather, **glm.to_faiman())) + print(pvsyst_cell(*weather, **glm.to_pvsyst())) + print(sapm_module(*weather, **glm.to_sapm())) + print(noct_sam(*weather, **glm.to_noct_sam())) - glm.set_sapm(-3.56, -0.075) + glm.use_sapm(-3.56, -0.075) print(glm) print(glm(*weather)) - print(faiman(*weather, **glm.get_faiman())) - print(pvsyst_cell(*weather, **glm.get_pvsyst())) - print(sapm_module(*weather, **glm.get_sapm())) - print(noct_sam(*weather, **glm.get_noct_sam())) + print(faiman(*weather, **glm.to_faiman())) + print(pvsyst_cell(*weather, **glm.to_pvsyst())) + print(sapm_module(*weather, **glm.to_sapm())) + print(noct_sam(*weather, **glm.to_noct_sam())) - glm.set_noct_sam(45) + glm.use_noct_sam(45) print(glm) print(glm(*weather)) - print(faiman(*weather, **glm.get_faiman())) - print(pvsyst_cell(*weather, **glm.get_pvsyst())) - print(sapm_module(*weather, **glm.get_sapm())) - print(noct_sam(*weather, **glm.get_noct_sam())) + print(faiman(*weather, **glm.to_faiman())) + print(pvsyst_cell(*weather, **glm.to_pvsyst())) + print(sapm_module(*weather, **glm.to_sapm())) + print(noct_sam(*weather, **glm.to_noct_sam())) ''' def __init__(self, module_efficiency=0.15, absorptance=0.9): @@ -1088,7 +1088,7 @@ def __call__(self, poa_global, temp_air, wind_speed, return temp_air + temp_difference - def set_faiman(self, u0, u1, **kwargs): + def use_faiman(self, u0, u1, **kwargs): ''' Set the generic model parameters to match the Faiman model. ''' @@ -1099,7 +1099,7 @@ def set_faiman(self, u0, u1, **kwargs): return self - def get_faiman(self): + def to_faiman(self): ''' Get the Faiman model parameters matching the generic ones. ''' @@ -1110,7 +1110,7 @@ def get_faiman(self): return dict(u0=u0, u1=u1) - def set_pvsyst(self, u_c, u_v, module_efficiency=None, + def use_pvsyst(self, u_c, u_v, module_efficiency=None, alpha_absorption=None): ''' Set the generic model parameters to match the PVsyst model. @@ -1134,7 +1134,7 @@ def set_pvsyst(self, u_c, u_v, module_efficiency=None, return self - def get_pvsyst(self): + def to_pvsyst(self): ''' Get the PVsyst model parameters matching the generic ones. ''' @@ -1151,7 +1151,7 @@ def get_pvsyst(self): alpha_absorption=self.alpha) - def set_noct_sam(self, noct, module_efficiency=None, + def use_noct_sam(self, noct, module_efficiency=None, transmittance_absorptance=None): ''' Set the generic model parameters to match the NOCT SAM model. @@ -1174,7 +1174,7 @@ def set_noct_sam(self, noct, module_efficiency=None, return self - def get_noct_sam(self): + def to_noct_sam(self): ''' Get the NOCT SAM model parameters to approximate the generic model. ''' @@ -1188,7 +1188,7 @@ def get_noct_sam(self): transmittance_absorptance=self.alpha) - def set_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): + def use_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): ''' Set the generic model parameters to approximate the SAPM module model. @@ -1207,7 +1207,7 @@ def set_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): return self - def get_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): + def to_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): ''' Get the SAPM module model parameters to approximate the generic model. From 7f7acbacbfaf3d73f4a776a08c8e40c85b2570c7 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 22 Jun 2022 15:25:21 +0200 Subject: [PATCH 03/14] Add generic_linear() as a separate module temperature function and adjustments to doc strings. --- pvlib/temperature.py | 52 +++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 683a421441..f08bf89220 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -979,6 +979,19 @@ def prilliman(temp_cell, wind_speed, unit_mass=11.1, coefficients=None): return smoothed +def generic_linear(poa_global, temp_air, wind_speed, u_const, du_wind, + module_efficiency=0.15, absorptance=0.9): + r""" + Calculate cell temperature using a generic linear heat loss factor model + as formulated in [n]. + """ + heat_input = poa_global * (absorptance - module_efficiency) + total_loss_factor = u_const + du_wind * wind_speed + temp_difference = heat_input / total_loss_factor + + return temp_air + temp_difference + + class GenericLinearModel(): ''' A generic linear temperature model class that can both use and convert @@ -1008,6 +1021,7 @@ class GenericLinearModel(): print(glm) print(glm(*weather)) + print(generic_linear(*weather, **glm.get_generic())) print(faiman(*weather, **glm.to_faiman())) print(pvsyst_cell(*weather, **glm.to_pvsyst())) print(sapm_module(*weather, **glm.to_sapm())) @@ -1017,6 +1031,7 @@ class GenericLinearModel(): print(glm) print(glm(*weather)) + print(generic_linear(*weather, **glm.get_generic())) print(faiman(*weather, **glm.to_faiman())) print(pvsyst_cell(*weather, **glm.to_pvsyst())) print(sapm_module(*weather, **glm.to_sapm())) @@ -1026,6 +1041,7 @@ class GenericLinearModel(): print(glm) print(glm(*weather)) + print(generic_linear(*weather, **glm.get_generic())) print(faiman(*weather, **glm.to_faiman())) print(pvsyst_cell(*weather, **glm.to_pvsyst())) print(sapm_module(*weather, **glm.to_sapm())) @@ -1035,12 +1051,12 @@ class GenericLinearModel(): print(glm) print(glm(*weather)) + print(generic_linear(*weather, **glm.get_generic())) print(faiman(*weather, **glm.to_faiman())) print(pvsyst_cell(*weather, **glm.to_pvsyst())) print(sapm_module(*weather, **glm.to_sapm())) print(noct_sam(*weather, **glm.to_noct_sam())) ''' - def __init__(self, module_efficiency=0.15, absorptance=0.9): ''' init docstring @@ -1064,14 +1080,12 @@ def __init__(self, module_efficiency=0.15, absorptance=0.9): self.alpha = absorptance return None - def __repr__(self): ''' Return an informative string for a particular model instance. ''' return self.__class__.__name__ + ': ' +vars(self).__repr__() - def __call__(self, poa_global, temp_air, wind_speed, module_efficiency=None): ''' @@ -1087,10 +1101,19 @@ def __call__(self, poa_global, temp_air, wind_speed, return temp_air + temp_difference + def get_generic(self): + ''' + Get the generic model parameters to use with the separate + generic module temperature calculation function. + ''' + return dict(u_const=self.u_const, + du_wind=self.du_wind, + module_efficiency=self.eta, + absorptance=self.alpha) def use_faiman(self, u0, u1, **kwargs): ''' - Set the generic model parameters to match the Faiman model. + Use the Faiman model parameters to set the generic equivalents. ''' net_absorptance = self.alpha - self.eta self.u_const = u0 * net_absorptance @@ -1098,10 +1121,9 @@ def use_faiman(self, u0, u1, **kwargs): return self - def to_faiman(self): ''' - Get the Faiman model parameters matching the generic ones. + Convert the generic model parameters to Faiman equivalents. ''' net_absorptance = self.alpha - self.eta u0 = self.u_const / net_absorptance @@ -1109,11 +1131,10 @@ def to_faiman(self): return dict(u0=u0, u1=u1) - def use_pvsyst(self, u_c, u_v, module_efficiency=None, alpha_absorption=None): ''' - Set the generic model parameters to match the PVsyst model. + Use the PVsyst model parameters to set the generic equivalents. The optional parameters are primarily for convenient compatibility with the other function signatures. @@ -1133,10 +1154,9 @@ def use_pvsyst(self, u_c, u_v, module_efficiency=None, return self - def to_pvsyst(self): ''' - Get the PVsyst model parameters matching the generic ones. + Convert the generic model parameters to PVsyst model equivalents. ''' net_absorptance_glm = self.alpha - self.eta net_absorptance_pvsyst = self.alpha * (1.0 - self.eta) @@ -1150,11 +1170,10 @@ def to_pvsyst(self): module_efficiency=self.eta, alpha_absorption=self.alpha) - def use_noct_sam(self, noct, module_efficiency=None, transmittance_absorptance=None): ''' - Set the generic model parameters to match the NOCT SAM model. + Use the NOCT SAM model parameters to set the generic equivalents. The optional parameters are primarily for convenient compatibility with the other function signatures. @@ -1173,10 +1192,9 @@ def use_noct_sam(self, noct, module_efficiency=None, self.du_wind = u_noct * 0.4 * wind_adj return self - def to_noct_sam(self): ''' - Get the NOCT SAM model parameters to approximate the generic model. + Convert the generic model parameters to NOCT SAM model equivalents. ''' # NOCT is determined with wind speed near module height # the adjustment reduces the wind coefficient for use with 10m wind @@ -1187,10 +1205,9 @@ def to_noct_sam(self): module_efficiency=self.eta, transmittance_absorptance=self.alpha) - def use_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): ''' - Set the generic model parameters to approximate the SAPM module model. + Use the SAPM module model parameters to set the generic equivalents. The generic model will equal the SAPM model at the two given 10m wind speed values. @@ -1206,10 +1223,9 @@ def use_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): self.du_wind = du_wind * net_absorptance return self - def to_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): ''' - Get the SAPM module model parameters to approximate the generic model. + Convert the generic model parameters to SAPM model equivalents. The SAPM model will equal the generic model at the two given 10m wind speed values. From 0b83f26667d12f614f9f033f10f24f4a3847389a Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 31 Aug 2022 23:08:55 +0200 Subject: [PATCH 04/14] Add tests. --- pvlib/temperature.py | 4 +-- pvlib/tests/test_temperature.py | 64 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 4246a5ba69..7093da6af3 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -1016,7 +1016,7 @@ class GenericLinearModel(): wind = np.array([1.4, 1/.51, 5.4]) weather = (765, 32, wind) - glm = GenericLinearModel(module_efficiency=0.18) + glm = GenericLinearModel(module_efficiency=0.18, absorptance=0.90) glm.use_faiman(25, 6.84) @@ -1058,7 +1058,7 @@ class GenericLinearModel(): print(sapm_module(*weather, **glm.to_sapm())) print(noct_sam(*weather, **glm.to_noct_sam())) ''' - def __init__(self, module_efficiency=0.15, absorptance=0.9): + def __init__(self, module_efficiency, absorptance): ''' init docstring diff --git a/pvlib/tests/test_temperature.py b/pvlib/tests/test_temperature.py index 5630f441e5..94009c3f8d 100644 --- a/pvlib/tests/test_temperature.py +++ b/pvlib/tests/test_temperature.py @@ -363,3 +363,67 @@ def test_prilliman_nans(): # original implementation would return some values < 1 here expected = pd.Series(1., index=times) assert_series_equal(actual, expected) + + +def test_glm_conversions(): + + glm = temperature.GenericLinearModel(module_efficiency=0.1, + absorptance=0.9) + + inp = {'u0': 25.0, 'u1': 6.84} + glm.use_faiman(**inp) + out = glm.to_faiman() + for k, v in inp.items(): + assert np.isclose(out[k], v) + + inp = {'u_c': 25, 'u_v': 4} + glm.use_pvsyst(**inp) + out = glm.to_pvsyst() + for k, v in inp.items(): + assert np.isclose(out[k], v) + + inp = {'u_c': 25, 'u_v': 4, + 'module_efficiency': 0.15, + 'alpha_absorption': 0.95} + glm.use_pvsyst(**inp) + out = glm.to_pvsyst() + for k, v in inp.items(): + assert np.isclose(out[k], v) + + inp = {'noct': 47} + glm.use_noct_sam(**inp) + out = glm.to_noct_sam() + for k, v in inp.items(): + assert np.isclose(out[k], v) + + inp = {'noct': 47, + 'module_efficiency': 0.15, + 'transmittance_absorptance': 0.95} + glm.use_noct_sam(**inp) + out = glm.to_noct_sam() + for k, v in inp.items(): + assert np.isclose(out[k], v) + + inp = {'a': -3.5, 'b': -0.1} + glm.use_sapm(**inp) + out = glm.to_sapm() + for k, v in inp.items(): + assert np.isclose(out[k], v) + + +def test_glm_simulations(): + + glm = temperature.GenericLinearModel(module_efficiency=0.1, + absorptance=0.9) + wind = np.array([1.4, 1/.51, 5.4]) + weather = (765, 32, wind) + + inp = {'u0': 20.0, 'u1': 5.0} + glm.use_faiman(**inp) + out = glm(*weather) + expected = temperature.faiman(*weather, **inp) + assert np.allclose(out, expected) + + inp = glm.get_generic() + temperature.generic_linear(*weather, **inp) + assert np.allclose(out, expected) From 62f73676cf819fe2a82626519d0f4ca065d779f0 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Wed, 31 Aug 2022 23:59:19 +0200 Subject: [PATCH 05/14] Minor test edits. --- pvlib/tests/test_temperature.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pvlib/tests/test_temperature.py b/pvlib/tests/test_temperature.py index 94009c3f8d..37c83ff0aa 100644 --- a/pvlib/tests/test_temperature.py +++ b/pvlib/tests/test_temperature.py @@ -366,7 +366,7 @@ def test_prilliman_nans(): def test_glm_conversions(): - + # it is easiest and sufficient to test conversion from & to the same model glm = temperature.GenericLinearModel(module_efficiency=0.1, absorptance=0.9) @@ -382,6 +382,7 @@ def test_glm_conversions(): for k, v in inp.items(): assert np.isclose(out[k], v) + # test with optional parameters inp = {'u_c': 25, 'u_v': 4, 'module_efficiency': 0.15, 'alpha_absorption': 0.95} @@ -396,6 +397,7 @@ def test_glm_conversions(): for k, v in inp.items(): assert np.isclose(out[k], v) + # test with optional parameters inp = {'noct': 47, 'module_efficiency': 0.15, 'transmittance_absorptance': 0.95} @@ -416,7 +418,7 @@ def test_glm_simulations(): glm = temperature.GenericLinearModel(module_efficiency=0.1, absorptance=0.9) wind = np.array([1.4, 1/.51, 5.4]) - weather = (765, 32, wind) + weather = (800, 20, wind) inp = {'u0': 20.0, 'u1': 5.0} glm.use_faiman(**inp) From 2b2136a3da6a9a98b95c9387c1847cce4b6ab9c6 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 1 Sep 2022 11:14:45 +0200 Subject: [PATCH 06/14] One more test to achieve 100% coverage (hopefully). --- pvlib/tests/test_temperature.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pvlib/tests/test_temperature.py b/pvlib/tests/test_temperature.py index 37c83ff0aa..b05c699d86 100644 --- a/pvlib/tests/test_temperature.py +++ b/pvlib/tests/test_temperature.py @@ -426,6 +426,25 @@ def test_glm_simulations(): expected = temperature.faiman(*weather, **inp) assert np.allclose(out, expected) + out = glm(*weather) + assert np.allclose(out, expected) + + out = glm(*weather, module_efficiency=0.1) + assert np.allclose(out, expected) + inp = glm.get_generic() - temperature.generic_linear(*weather, **inp) + out = temperature.generic_linear(*weather, **inp) assert np.allclose(out, expected) + + +def test_glm_repr(): + + glm = temperature.GenericLinearModel(module_efficiency=0.1, + absorptance=0.9) + inp = {'u0': 20.0, 'u1': 5.0} + glm.use_faiman(**inp) + expected = ("GenericLinearModel: {'u_const': 16.0, " + "'du_wind': 4.0, " + "'eta': 0.1, " + "'alpha': 0.9}") + assert glm.__repr__() == expected From 0c3621128bfac05fdf9fb595b4b53ac267768330 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 1 Sep 2022 13:42:40 +0200 Subject: [PATCH 07/14] Some formatting, docstring development. --- pvlib/temperature.py | 159 +++++++++++++++++--------------- pvlib/tests/test_temperature.py | 10 +- 2 files changed, 89 insertions(+), 80 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 7093da6af3..e329d22765 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -996,7 +996,41 @@ def generic_linear(poa_global, temp_air, wind_speed, u_const, du_wind, class GenericLinearModel(): ''' A generic linear temperature model class that can both use and convert - parameters of several well-known equivalent or nearly equivalent models. + parameters of several well-known equivalent or nearly equivalent models: + faiman, pvsyst, noct-sam and sapm. + + Normally this class is used on an ad hoc basis to make a needed conversion + to a target model, and the converted parameters are then used for + subsequent simulations running the target model. The parameters are + always returned as dictionaries that are compatible with the respective + model functions in pvlib.temperature. + + An instance of the class represents a specific module type, therefore, + the module properties efficiency and absorptance must be provided. + Although some temperature models do not use these properties, they + nevertheless exist and affect operating temperature. + + Since these are empirical models, their parameters are determined + from a limited set of measurements (usually high irradiance). + The values of efficiency and absorptance should be interpreted + as representative of the efficiency and absorptance actually + occuring during those measurements. + + When used for time series simulation, efficiency and absorptance may + vary, and each emperical model handles this differently. + + Parameters + ---------- + module_efficiency : float + The electrical efficiency of the module. [pu] + absorptance : float + The light absorptance of the module. [pu] + + Notes + ----- + After creating a GenericLinearModel object using the module properties, + one of the use_* methods must be called to provide thermal model + parameters. Examples -------- @@ -1011,70 +1045,9 @@ class GenericLinearModel(): glm.use_pvsyst(29, 0).to_faiman() - Tests - ----- - wind = np.array([1.4, 1/.51, 5.4]) - weather = (765, 32, wind) - - glm = GenericLinearModel(module_efficiency=0.18, absorptance=0.90) - - glm.use_faiman(25, 6.84) - - print(glm) - print(glm(*weather)) - print(generic_linear(*weather, **glm.get_generic())) - print(faiman(*weather, **glm.to_faiman())) - print(pvsyst_cell(*weather, **glm.to_pvsyst())) - print(sapm_module(*weather, **glm.to_sapm())) - print(noct_sam(*weather, **glm.to_noct_sam())) - - glm.use_pvsyst(29, 0) - - print(glm) - print(glm(*weather)) - print(generic_linear(*weather, **glm.get_generic())) - print(faiman(*weather, **glm.to_faiman())) - print(pvsyst_cell(*weather, **glm.to_pvsyst())) - print(sapm_module(*weather, **glm.to_sapm())) - print(noct_sam(*weather, **glm.to_noct_sam())) - - glm.use_sapm(-3.56, -0.075) - - print(glm) - print(glm(*weather)) - print(generic_linear(*weather, **glm.get_generic())) - print(faiman(*weather, **glm.to_faiman())) - print(pvsyst_cell(*weather, **glm.to_pvsyst())) - print(sapm_module(*weather, **glm.to_sapm())) - print(noct_sam(*weather, **glm.to_noct_sam())) - - glm.use_noct_sam(45) - - print(glm) - print(glm(*weather)) - print(generic_linear(*weather, **glm.get_generic())) - print(faiman(*weather, **glm.to_faiman())) - print(pvsyst_cell(*weather, **glm.to_pvsyst())) - print(sapm_module(*weather, **glm.to_sapm())) - print(noct_sam(*weather, **glm.to_noct_sam())) ''' def __init__(self, module_efficiency, absorptance): - ''' - init docstring - - The two parameters are pv module properties that are relevant for - temperature modeling even if certain models do not explicitly - mention or use them. - - Since these are empirical models, their parameters are determined - from a limited set of measurements (usually high irradiance). - The values of efficiency and absorptance should be interpreted - as representative of the efficiency and absorptance actually - occuring during those measurements. - When used for simulation, efficiency and absorptance may be - different, and each model handles this differently. - ''' self.u_const = np.nan self.du_wind = np.nan self.eta = module_efficiency @@ -1082,16 +1055,34 @@ def __init__(self, module_efficiency, absorptance): return None def __repr__(self): - ''' - Return an informative string for a particular model instance. - ''' - return self.__class__.__name__ + ': ' +vars(self).__repr__() + + return self.__class__.__name__ + ': ' + vars(self).__repr__() def __call__(self, poa_global, temp_air, wind_speed, - module_efficiency=None): + module_efficiency=None): ''' Calculate module temperature using the generic linear model and previously initialized parameters. + + Parameters + ---------- + poa_global : numeric + Total incident irradiance [W/m^2]. + + temp_air : numeric + Ambient dry bulb temperature [C]. + + wind_speed : numeric + Wind speed in m/s measured at the same height for which the wind + loss factor was determined. [m/s] + + module_efficiency : numeric, optional + Module electrical efficiency. The default value is the one + that was specified initially. [pu] + + Returns + ------- + numeric, values in degrees Celsius ''' if module_efficiency is None: module_efficiency = self.eta @@ -1104,8 +1095,12 @@ def __call__(self, poa_global, temp_air, wind_speed, def get_generic(self): ''' - Get the generic model parameters to use with the separate - generic module temperature calculation function. + Get the generic linea model parameters to use with the separate + generic linear module temperature calculation function. + + Returns: + -------- + dict containg generic linear model parameters ''' return dict(u_const=self.u_const, du_wind=self.du_wind, @@ -1115,6 +1110,12 @@ def get_generic(self): def use_faiman(self, u0, u1, **kwargs): ''' Use the Faiman model parameters to set the generic equivalents. + + Parameters + ---------- + u0, u1 : float + + See :py:func:`pvlib.temperature.faiman` for details. ''' net_absorptance = self.alpha - self.eta self.u_const = u0 * net_absorptance @@ -1125,6 +1126,12 @@ def use_faiman(self, u0, u1, **kwargs): def to_faiman(self): ''' Convert the generic model parameters to Faiman equivalents. + + Returns + ---------- + dict containing keys 'u0', 'u1' + + See :py:func:`pvlib.temperature.faiman` for details. ''' net_absorptance = self.alpha - self.eta u0 = self.u_const / net_absorptance @@ -1133,7 +1140,7 @@ def to_faiman(self): return dict(u0=u0, u1=u1) def use_pvsyst(self, u_c, u_v, module_efficiency=None, - alpha_absorption=None): + alpha_absorption=None): ''' Use the PVsyst model parameters to set the generic equivalents. @@ -1150,8 +1157,8 @@ def use_pvsyst(self, u_c, u_v, module_efficiency=None, net_absorptance_pvsyst = self.alpha * (1.0 - self.eta) absorptance_ratio = net_absorptance_glm / net_absorptance_pvsyst - self.u_const = u_c * absorptance_ratio - self.du_wind = u_v * absorptance_ratio + self.u_const = u_c * absorptance_ratio + self.du_wind = u_v * absorptance_ratio return self @@ -1172,7 +1179,7 @@ def to_pvsyst(self): alpha_absorption=self.alpha) def use_noct_sam(self, noct, module_efficiency=None, - transmittance_absorptance=None): + transmittance_absorptance=None): ''' Use the NOCT SAM model parameters to set the generic equivalents. @@ -1187,7 +1194,7 @@ def use_noct_sam(self, noct, module_efficiency=None, # NOCT is determined with wind speed near module height # the adjustment reduces the wind coefficient for use with 10m wind - wind_adj=0.51 + wind_adj = 0.51 u_noct = 800.0 * self.alpha / (noct - 20.0) self.u_const = u_noct * 0.6 self.du_wind = u_noct * 0.4 * wind_adj @@ -1213,7 +1220,7 @@ def use_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): The generic model will equal the SAPM model at the two given 10m wind speed values. ''' - u_low = 1.0 / np.exp(a + b * wind_fit_low) + u_low = 1.0 / np.exp(a + b * wind_fit_low) u_high = 1.0 / np.exp(a + b * wind_fit_high) du_wind = (u_high - u_low) / (wind_fit_high - wind_fit_low) @@ -1235,7 +1242,7 @@ def to_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): u_const = self.u_const / net_absorptance du_wind = self.du_wind / net_absorptance - u_low = u_const + du_wind * wind_fit_low + u_low = u_const + du_wind * wind_fit_low u_high = u_const + du_wind * wind_fit_high b = - ((np.log(u_high) - np.log(u_low)) / diff --git a/pvlib/tests/test_temperature.py b/pvlib/tests/test_temperature.py index b05c699d86..f244a857a0 100644 --- a/pvlib/tests/test_temperature.py +++ b/pvlib/tests/test_temperature.py @@ -443,8 +443,10 @@ def test_glm_repr(): absorptance=0.9) inp = {'u0': 20.0, 'u1': 5.0} glm.use_faiman(**inp) - expected = ("GenericLinearModel: {'u_const': 16.0, " - "'du_wind': 4.0, " - "'eta': 0.1, " - "'alpha': 0.9}") + expected = ("GenericLinearModel: {" + "'u_const': 16.0, " + "'du_wind': 4.0, " + "'eta': 0.1, " + "'alpha': 0.9}") + assert glm.__repr__() == expected From fbdec3416c30f83242ed0e742ef61b3079979063 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 1 Sep 2022 13:58:48 +0200 Subject: [PATCH 08/14] Try to update sphynx. --- docs/sphinx/source/reference/pv_modeling.rst | 5 +++++ pvlib/temperature.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/reference/pv_modeling.rst b/docs/sphinx/source/reference/pv_modeling.rst index d1ae5a6559..d4d784f289 100644 --- a/docs/sphinx/source/reference/pv_modeling.rst +++ b/docs/sphinx/source/reference/pv_modeling.rst @@ -44,6 +44,7 @@ PV temperature models temperature.ross temperature.noct_sam temperature.prilliman + temperature.generic_linear pvsystem.PVSystem.get_cell_temperature Temperature Model Parameters @@ -54,6 +55,10 @@ Temperature Model Parameters .. currentmodule:: pvlib +.. autosummary:: + :toctree: generated/ + temperature.GenericLinearModel + Single diode models ------------------- diff --git a/pvlib/temperature.py b/pvlib/temperature.py index e329d22765..20397684fc 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -1206,7 +1206,7 @@ def to_noct_sam(self): ''' # NOCT is determined with wind speed near module height # the adjustment reduces the wind coefficient for use with 10m wind - wind_adj=0.51 + wind_adj = 0.51 u_noct = self.u_const + self.du_wind / wind_adj noct = 20.0 + (800.0 * self.alpha) / u_noct return dict(noct=noct, @@ -1246,6 +1246,6 @@ def to_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): u_high = u_const + du_wind * wind_fit_high b = - ((np.log(u_high) - np.log(u_low)) / - (wind_fit_high - wind_fit_low)) + (wind_fit_high - wind_fit_low)) a = - (np.log(u_low) + b * wind_fit_low) return dict(a=a, b=b) From a3188f4301f0a28cae1fe174027b247a65c38b66 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 1 Sep 2022 14:11:21 +0200 Subject: [PATCH 09/14] Try sphynx again. --- docs/sphinx/source/reference/pv_modeling.rst | 7 ++----- pvlib/temperature.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/sphinx/source/reference/pv_modeling.rst b/docs/sphinx/source/reference/pv_modeling.rst index d4d784f289..31c380c1bb 100644 --- a/docs/sphinx/source/reference/pv_modeling.rst +++ b/docs/sphinx/source/reference/pv_modeling.rst @@ -44,8 +44,9 @@ PV temperature models temperature.ross temperature.noct_sam temperature.prilliman - temperature.generic_linear pvsystem.PVSystem.get_cell_temperature + temperature.generic_linear + temperature.GenericLinearModel Temperature Model Parameters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -55,10 +56,6 @@ Temperature Model Parameters .. currentmodule:: pvlib -.. autosummary:: - :toctree: generated/ - temperature.GenericLinearModel - Single diode models ------------------- diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 20397684fc..7a1bca4bed 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -1246,6 +1246,6 @@ def to_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): u_high = u_const + du_wind * wind_fit_high b = - ((np.log(u_high) - np.log(u_low)) / - (wind_fit_high - wind_fit_low)) + (wind_fit_high - wind_fit_low)) a = - (np.log(u_low) + b * wind_fit_low) return dict(a=a, b=b) From 91af074b50e992f875ad1fefe7efaeb555188ab4 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 1 Sep 2022 16:25:10 +0200 Subject: [PATCH 10/14] Docstrings nearing completion. --- pvlib/temperature.py | 153 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 139 insertions(+), 14 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 7a1bca4bed..c61e043eda 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -981,11 +981,53 @@ def prilliman(temp_cell, wind_speed, unit_mass=11.1, coefficients=None): def generic_linear(poa_global, temp_air, wind_speed, u_const, du_wind, - module_efficiency=0.15, absorptance=0.9): - r""" + module_efficiency, absorptance): + """ Calculate cell temperature using a generic linear heat loss factor model - as formulated in [n]. + as developed in [1]_. + + The parameters for this model can be obtained using + :py:class:`GenericLinearModel` + + Parameters + ---------- + poa_global : numeric + Total incident irradiance [W/m^2]. + + temp_air : numeric + Ambient dry bulb temperature [C]. + + wind_speed : numeric + Wind speed at a height of 10 meters [m/s]. + + u_const : numeric + Combined heat transfer coefficient at zero wind speed [(W/m^2)/C] + + du_wind : numeric + Influence of wind speed on combined heat transfer coefficient + [(W/m^2)/C/(m/s)] + + module_efficiency : float + The electrical efficiency of the module. [pu] + + absorptance : float + The light absorptance of the module. [pu] + + Returns + ------- + numeric, values in degrees C. + + References + ---------- + .. [1] A. Driesse et al, "PV Module Operating Temperature + Model Equivalence and Parameter Translation". 2022 IEEE + Photovoltaic Specialists Conference (PVSC), 2022. + + See also + -------- + pvlib.temperature.GenericLinearModel """ + # Contributed by Anton Driesse (@adriesse), PV Performance Labs, Sept. 2022 heat_input = poa_global * (absorptance - module_efficiency) total_loss_factor = u_const + du_wind * wind_speed temp_difference = heat_input / total_loss_factor @@ -1023,6 +1065,7 @@ class GenericLinearModel(): ---------- module_efficiency : float The electrical efficiency of the module. [pu] + absorptance : float The light absorptance of the module. [pu] @@ -1043,9 +1086,20 @@ class GenericLinearModel(): parmdict = glm.to_pvsyst() - glm.use_pvsyst(29, 0).to_faiman() + pvsyst_cell(800, 20, 1, **parmdict) + + References + ---------- + .. [1] A. Driesse et al, "PV Module Operating Temperature + Model Equivalence and Parameter Translation". 2022 IEEE + Photovoltaic Specialists Conference (PVSC), 2022. + See also + -------- + pvlib.temperature.generic_linear ''' + # Contributed by Anton Driesse (@adriesse), PV Performance Labs, Sept. 2022 + def __init__(self, module_efficiency, absorptance): self.u_const = np.nan @@ -1083,6 +1137,11 @@ def __call__(self, poa_global, temp_air, wind_speed, Returns ------- numeric, values in degrees Celsius + + See also + -------- + get_generic + pvlib.temperature.generic_linear ''' if module_efficiency is None: module_efficiency = self.eta @@ -1114,8 +1173,7 @@ def use_faiman(self, u0, u1, **kwargs): Parameters ---------- u0, u1 : float - - See :py:func:`pvlib.temperature.faiman` for details. + See :py:func:`pvlib.temperature.faiman` for details. ''' net_absorptance = self.alpha - self.eta self.u_const = u0 * net_absorptance @@ -1129,9 +1187,9 @@ def to_faiman(self): Returns ---------- - dict containing keys 'u0', 'u1' - - See :py:func:`pvlib.temperature.faiman` for details. + model_parameters : dict + See :py:func:`pvlib.temperature.faiman` for + model parameter details. ''' net_absorptance = self.alpha - self.eta u0 = self.u_const / net_absorptance @@ -1144,8 +1202,18 @@ def use_pvsyst(self, u_c, u_v, module_efficiency=None, ''' Use the PVsyst model parameters to set the generic equivalents. + Parameters + ---------- + u_c, u_v : float + See :py:func:`pvlib.temperature.pvsyst_cell` for details. + + module_efficiency, alpha_absorption : float, optional + See :py:func:`pvlib.temperature.pvsyst_cell` for details. + + Notes + ----- The optional parameters are primarily for convenient compatibility - with the other function signatures. + with existing function signatures. ''' if module_efficiency is not None: self.eta = module_efficiency @@ -1165,6 +1233,12 @@ def use_pvsyst(self, u_c, u_v, module_efficiency=None, def to_pvsyst(self): ''' Convert the generic model parameters to PVsyst model equivalents. + + Returns + ---------- + model_parameters : dict + See :py:func:`pvlib.temperature.pvsyst_cell` for + model parameter details. ''' net_absorptance_glm = self.alpha - self.eta net_absorptance_pvsyst = self.alpha * (1.0 - self.eta) @@ -1183,8 +1257,18 @@ def use_noct_sam(self, noct, module_efficiency=None, ''' Use the NOCT SAM model parameters to set the generic equivalents. + Parameters + ---------- + noct : float + See :py:func:`pvlib.temperature.noct_sam` for details. + + module_efficiency, transmittance_absorptance : float, optional + See :py:func:`pvlib.temperature.noct_sam` for details. + + Notes + ----- The optional parameters are primarily for convenient compatibility - with the other function signatures. + with existing function signatures. ''' if module_efficiency is not None: self.eta = module_efficiency @@ -1203,6 +1287,12 @@ def use_noct_sam(self, noct, module_efficiency=None, def to_noct_sam(self): ''' Convert the generic model parameters to NOCT SAM model equivalents. + + Returns + ---------- + model_parameters : dict + See :py:func:`pvlib.temperature.noct_sam` for + model parameter details. ''' # NOCT is determined with wind speed near module height # the adjustment reduces the wind coefficient for use with 10m wind @@ -1217,8 +1307,21 @@ def use_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): ''' Use the SAPM module model parameters to set the generic equivalents. - The generic model will equal the SAPM model at the two - given 10m wind speed values. + In the SAPM the heat transfer coefficient increases exponentially + with windspeed, whereas in the other models the increase is linear. + This function equates the generic linear model to SAPM at two + specified winds speeds, thereby defining a linear approximation + for the exponential behavior. + + Parameters + ---------- + a, b : float + See :py:func:`pvlib.temperature.noct_sam` for details. + + wind_fit_low, wind_fit_high : float, optional + Two wind speed values at which the generic linear model + must be equal to the SAPM model. The default + values represent wind speed at 10m height. [m/s] ''' u_low = 1.0 / np.exp(a + b * wind_fit_low) u_high = 1.0 / np.exp(a + b * wind_fit_high) @@ -1235,8 +1338,30 @@ def to_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): ''' Convert the generic model parameters to SAPM model equivalents. + In the SAPM the heat transfer coefficient increases exponentially + with windspeed, whereas in the other models the increase is linear. + This function equates SAPM to the generic linear model at two + specified winds speeds, thereby defining an exponential approximation + for the linear behavior. + + Parameters + ---------- + wind_fit_low, wind_fit_high : float, optional + Two wind speed values at which the generic linear model + must be equal to the SAPM model. The default + values represent wind speed at 10m height. [m/s] + + Returns + ---------- + model_parameters : dict + See :py:func:`pvlib.temperature.sapm_module` for + model parameter details. + + Notes + ----- The SAPM model will equal the generic model at the two - given 10m wind speed values. + given 10m wind speed values, and approximately equal at + other wind speeds. ''' net_absorptance = self.alpha - self.eta u_const = self.u_const / net_absorptance From 12009c5f52dfc706536053da04326e91a7ba767f Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 1 Sep 2022 17:14:50 +0200 Subject: [PATCH 11/14] Docstrings complete. --- pvlib/temperature.py | 44 ++++++++++++++++----------------- pvlib/tests/test_temperature.py | 2 +- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index c61e043eda..922afb1590 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -1008,10 +1008,10 @@ def generic_linear(poa_global, temp_air, wind_speed, u_const, du_wind, [(W/m^2)/C/(m/s)] module_efficiency : float - The electrical efficiency of the module. [pu] + The electrical efficiency of the module. [-] absorptance : float - The light absorptance of the module. [pu] + The light absorptance of the module. [-] Returns ------- @@ -1020,7 +1020,7 @@ def generic_linear(poa_global, temp_air, wind_speed, u_const, du_wind, References ---------- .. [1] A. Driesse et al, "PV Module Operating Temperature - Model Equivalence and Parameter Translation". 2022 IEEE + Model Equivalence and Parameter Translation". 2022 IEEE Photovoltaic Specialists Conference (PVSC), 2022. See also @@ -1028,6 +1028,7 @@ def generic_linear(poa_global, temp_air, wind_speed, u_const, du_wind, pvlib.temperature.GenericLinearModel """ # Contributed by Anton Driesse (@adriesse), PV Performance Labs, Sept. 2022 + heat_input = poa_global * (absorptance - module_efficiency) total_loss_factor = u_const + du_wind * wind_speed temp_difference = heat_input / total_loss_factor @@ -1048,7 +1049,7 @@ class GenericLinearModel(): model functions in pvlib.temperature. An instance of the class represents a specific module type, therefore, - the module properties efficiency and absorptance must be provided. + the module properties *efficiency* and *absorptance* must be provided. Although some temperature models do not use these properties, they nevertheless exist and affect operating temperature. @@ -1059,15 +1060,15 @@ class GenericLinearModel(): occuring during those measurements. When used for time series simulation, efficiency and absorptance may - vary, and each emperical model handles this differently. + vary, and each empirical model handles this differently. Parameters ---------- module_efficiency : float - The electrical efficiency of the module. [pu] + The electrical efficiency of the module. [-] absorptance : float - The light absorptance of the module. [pu] + The light absorptance of the module. [-] Notes ----- @@ -1075,9 +1076,14 @@ class GenericLinearModel(): one of the use_* methods must be called to provide thermal model parameters. + References + ---------- + .. [1] A. Driesse et al, "PV Module Operating Temperature + Model Equivalence and Parameter Translation". 2022 IEEE + Photovoltaic Specialists Conference (PVSC), 2022. + Examples -------- - glm = GenericLinearModel(module_efficiency=0.19, absorptance=0.88) glm.use_faiman(16, 8) @@ -1088,12 +1094,6 @@ class GenericLinearModel(): pvsyst_cell(800, 20, 1, **parmdict) - References - ---------- - .. [1] A. Driesse et al, "PV Module Operating Temperature - Model Equivalence and Parameter Translation". 2022 IEEE - Photovoltaic Specialists Conference (PVSC), 2022. - See also -------- pvlib.temperature.generic_linear @@ -1152,14 +1152,18 @@ def __call__(self, poa_global, temp_air, wind_speed, return temp_air + temp_difference - def get_generic(self): + def use_generic_linear(self): ''' - Get the generic linea model parameters to use with the separate + Get the generic linear model parameters to use with the separate generic linear module temperature calculation function. Returns: -------- - dict containg generic linear model parameters + model_parameters : dict + + See also + -------- + pvlib.temperature.generic_linear ''' return dict(u_const=self.u_const, du_wind=self.du_wind, @@ -1356,12 +1360,6 @@ def to_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): model_parameters : dict See :py:func:`pvlib.temperature.sapm_module` for model parameter details. - - Notes - ----- - The SAPM model will equal the generic model at the two - given 10m wind speed values, and approximately equal at - other wind speeds. ''' net_absorptance = self.alpha - self.eta u_const = self.u_const / net_absorptance diff --git a/pvlib/tests/test_temperature.py b/pvlib/tests/test_temperature.py index f244a857a0..1aed916b87 100644 --- a/pvlib/tests/test_temperature.py +++ b/pvlib/tests/test_temperature.py @@ -432,7 +432,7 @@ def test_glm_simulations(): out = glm(*weather, module_efficiency=0.1) assert np.allclose(out, expected) - inp = glm.get_generic() + inp = glm.use_generic_linear() out = temperature.generic_linear(*weather, **inp) assert np.allclose(out, expected) From 5b2bc65effcc512b246180b6f790d1d584b4bfe1 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Thu, 1 Sep 2022 17:38:23 +0200 Subject: [PATCH 12/14] One more small name change. --- pvlib/temperature.py | 2 +- pvlib/tests/test_temperature.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 922afb1590..65d552ee5e 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -1152,7 +1152,7 @@ def __call__(self, poa_global, temp_air, wind_speed, return temp_air + temp_difference - def use_generic_linear(self): + def get_generic_linear(self): ''' Get the generic linear model parameters to use with the separate generic linear module temperature calculation function. diff --git a/pvlib/tests/test_temperature.py b/pvlib/tests/test_temperature.py index 1aed916b87..5e36714d12 100644 --- a/pvlib/tests/test_temperature.py +++ b/pvlib/tests/test_temperature.py @@ -432,7 +432,7 @@ def test_glm_simulations(): out = glm(*weather, module_efficiency=0.1) assert np.allclose(out, expected) - inp = glm.use_generic_linear() + inp = glm.get_generic_linear() out = temperature.generic_linear(*weather, **inp) assert np.allclose(out, expected) From 21036ca1214ea28af1ef076839559b71c26b3c2f Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Sat, 3 Sep 2022 12:01:50 +0200 Subject: [PATCH 13/14] Fix problems identified in reviews. --- pvlib/temperature.py | 140 +++++++++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 59 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 65d552ee5e..c55155606e 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -983,11 +983,11 @@ def prilliman(temp_cell, wind_speed, unit_mass=11.1, coefficients=None): def generic_linear(poa_global, temp_air, wind_speed, u_const, du_wind, module_efficiency, absorptance): """ - Calculate cell temperature using a generic linear heat loss factor model - as developed in [1]_. + Calculate cell temperature using a generic linear heat loss factor model. - The parameters for this model can be obtained using - :py:class:`GenericLinearModel` + The parameters for this model can be obtained from other model + parameters using :py:class:`GenericLinearModel`. A description of this + model and its relationship to other temperature models is found in [1]_. Parameters ---------- @@ -1000,10 +1000,10 @@ def generic_linear(poa_global, temp_air, wind_speed, u_const, du_wind, wind_speed : numeric Wind speed at a height of 10 meters [m/s]. - u_const : numeric + u_const : float Combined heat transfer coefficient at zero wind speed [(W/m^2)/C] - du_wind : numeric + du_wind : float Influence of wind speed on combined heat transfer coefficient [(W/m^2)/C/(m/s)] @@ -1038,29 +1038,23 @@ def generic_linear(poa_global, temp_air, wind_speed, u_const, du_wind, class GenericLinearModel(): ''' - A generic linear temperature model class that can both use and convert - parameters of several well-known equivalent or nearly equivalent models: - faiman, pvsyst, noct-sam and sapm. - - Normally this class is used on an ad hoc basis to make a needed conversion - to a target model, and the converted parameters are then used for - subsequent simulations running the target model. The parameters are - always returned as dictionaries that are compatible with the respective - model functions in pvlib.temperature. - - An instance of the class represents a specific module type, therefore, - the module properties *efficiency* and *absorptance* must be provided. + A class that can both use and convert parameters of linear module + temperature models: faiman, pvsyst, noct_sam, sapm_module + and generic_linear. + + Parameters are converted between models by first converting + to the generic linear heat transfer model [1]_ by the ``use_`` + methods. The equivalent parameters for the target temperature + model are then obtained by the ``to_`` method. + Parameters are returned as a dictionary that is compatible with the + target model function to use in simulations. + + An instance of the class represents a specific module type and + the parameters ``module_efficiency`` and ``absorptance`` are required. Although some temperature models do not use these properties, they - nevertheless exist and affect operating temperature. - - Since these are empirical models, their parameters are determined - from a limited set of measurements (usually high irradiance). - The values of efficiency and absorptance should be interpreted - as representative of the efficiency and absorptance actually - occuring during those measurements. - - When used for time series simulation, efficiency and absorptance may - vary, and each empirical model handles this differently. + nevertheless exist and affect operating temperature. Values + should be representative of the conditions at which the input + model parameters were determined (usually high irradiance). Parameters ---------- @@ -1073,8 +1067,9 @@ class GenericLinearModel(): Notes ----- After creating a GenericLinearModel object using the module properties, - one of the use_* methods must be called to provide thermal model - parameters. + one of the ``use_`` methods must be called to provide thermal model + parameters. If this is not done, the ``to_`` methods will return ``nan`` + values. References ---------- @@ -1084,15 +1079,19 @@ class GenericLinearModel(): Examples -------- - glm = GenericLinearModel(module_efficiency=0.19, absorptance=0.88) - - glm.use_faiman(16, 8) + >>> glm = GenericLinearModel(module_efficiency=0.19, absorptance=0.88) - glm.to_pvsyst() + >>> glm.use_faiman(16, 8) + GenericLinearModel: {'u_const': 11.04, 'du_wind': 5.52, + 'eta': 0.19, 'alpha': 0.88} - parmdict = glm.to_pvsyst() + >>> glm.to_pvsyst() + {'u_c': 11.404800000000002, 'u_v': 5.702400000000001, + 'module_efficiency': 0.19, 'alpha_absorption': 0.88} - pvsyst_cell(800, 20, 1, **parmdict) + >>> parmdict = glm.to_pvsyst() + >>> pvsyst_cell(800, 20, 1, **parmdict) + 53.33333333333333 See also -------- @@ -1106,6 +1105,7 @@ def __init__(self, module_efficiency, absorptance): self.du_wind = np.nan self.eta = module_efficiency self.alpha = absorptance + return None def __repr__(self): @@ -1115,7 +1115,7 @@ def __repr__(self): def __call__(self, poa_global, temp_air, wind_speed, module_efficiency=None): ''' - Calculate module temperature using the generic linear model and + Calculate module temperature using the generic_linear model and previously initialized parameters. Parameters @@ -1132,7 +1132,7 @@ def __call__(self, poa_global, temp_air, wind_speed, module_efficiency : numeric, optional Module electrical efficiency. The default value is the one - that was specified initially. [pu] + that was specified initially. [-] Returns ------- @@ -1146,19 +1146,17 @@ def __call__(self, poa_global, temp_air, wind_speed, if module_efficiency is None: module_efficiency = self.eta - heat_input = poa_global * (self.alpha - module_efficiency) - total_loss_factor = self.u_const + self.du_wind * wind_speed - temp_difference = heat_input / total_loss_factor - - return temp_air + temp_difference + return generic_linear(poa_global, temp_air, wind_speed, + self.u_const, self.du_wind, + module_efficiency, self.alpha) def get_generic_linear(self): ''' Get the generic linear model parameters to use with the separate generic linear module temperature calculation function. - Returns: - -------- + Returns + ------- model_parameters : dict See also @@ -1170,9 +1168,9 @@ def get_generic_linear(self): module_efficiency=self.eta, absorptance=self.alpha) - def use_faiman(self, u0, u1, **kwargs): + def use_faiman(self, u0, u1): ''' - Use the Faiman model parameters to set the generic equivalents. + Use the Faiman model parameters to set the generic_model equivalents. Parameters ---------- @@ -1204,7 +1202,7 @@ def to_faiman(self): def use_pvsyst(self, u_c, u_v, module_efficiency=None, alpha_absorption=None): ''' - Use the PVsyst model parameters to set the generic equivalents. + Use the PVsyst model parameters to set the generic_model equivalents. Parameters ---------- @@ -1259,7 +1257,7 @@ def to_pvsyst(self): def use_noct_sam(self, noct, module_efficiency=None, transmittance_absorptance=None): ''' - Use the NOCT SAM model parameters to set the generic equivalents. + Use the NOCT SAM model parameters to set the generic_model equivalents. Parameters ---------- @@ -1286,6 +1284,7 @@ def use_noct_sam(self, noct, module_efficiency=None, u_noct = 800.0 * self.alpha / (noct - 20.0) self.u_const = u_noct * 0.6 self.du_wind = u_noct * 0.4 * wind_adj + return self def to_noct_sam(self): @@ -1303,13 +1302,14 @@ def to_noct_sam(self): wind_adj = 0.51 u_noct = self.u_const + self.du_wind / wind_adj noct = 20.0 + (800.0 * self.alpha) / u_noct + return dict(noct=noct, module_efficiency=self.eta, transmittance_absorptance=self.alpha) def use_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): ''' - Use the SAPM module model parameters to set the generic equivalents. + Use the SAPM model parameters to set the generic_model equivalents. In the SAPM the heat transfer coefficient increases exponentially with windspeed, whereas in the other models the increase is linear. @@ -1320,12 +1320,22 @@ def use_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): Parameters ---------- a, b : float - See :py:func:`pvlib.temperature.noct_sam` for details. + See :py:func:`pvlib.temperature.sapm_module` for details. - wind_fit_low, wind_fit_high : float, optional - Two wind speed values at which the generic linear model - must be equal to the SAPM model. The default - values represent wind speed at 10m height. [m/s] + wind_fit_low : float, optional + First wind speed value at which the generic linear model + must be equal to the SAPM model. [m/s] + + wind_fit_high : float, optional + Second wind speed value at which the generic linear model + must be equal to the SAPM model. [m/s] + + Notes + ----- + The two default wind speed values are based on measurements + at 10 m height. Both the SAPM model and the conversion + functions can work with wind speed data at different heights as + long as the same height is used consistently throughout. ''' u_low = 1.0 / np.exp(a + b * wind_fit_low) u_high = 1.0 / np.exp(a + b * wind_fit_high) @@ -1336,6 +1346,7 @@ def use_sapm(self, a, b, wind_fit_low=1.4, wind_fit_high=5.4): net_absorptance = self.alpha - self.eta self.u_const = u_const * net_absorptance self.du_wind = du_wind * net_absorptance + return self def to_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): @@ -1350,16 +1361,26 @@ def to_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): Parameters ---------- - wind_fit_low, wind_fit_high : float, optional - Two wind speed values at which the generic linear model - must be equal to the SAPM model. The default - values represent wind speed at 10m height. [m/s] + wind_fit_low : float, optional + First wind speed value at which the generic linear model + must be equal to the SAPM model. [m/s] + + wind_fit_high : float, optional + Second wind speed value at which the generic linear model + must be equal to the SAPM model. [m/s] Returns ---------- model_parameters : dict See :py:func:`pvlib.temperature.sapm_module` for model parameter details. + + Notes + ----- + The two default wind speed values are based on measurements + at 10 m height. Both the SAPM model and the conversion + functions can work with wind speed data at different heights as + long as the same height is used consistently throughout. ''' net_absorptance = self.alpha - self.eta u_const = self.u_const / net_absorptance @@ -1371,4 +1392,5 @@ def to_sapm(self, wind_fit_low=1.4, wind_fit_high=5.4): b = - ((np.log(u_high) - np.log(u_low)) / (wind_fit_high - wind_fit_low)) a = - (np.log(u_low) + b * wind_fit_low) + return dict(a=a, b=b) From 62d1ca738b8e429c054b13b3cd9446bb658ad501 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Mon, 12 Sep 2022 11:10:46 +0200 Subject: [PATCH 14/14] Update whatsnew. --- docs/sphinx/source/whatsnew/v0.9.3.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.9.3.rst b/docs/sphinx/source/whatsnew/v0.9.3.rst index 2f3c643d05..2afd26b1dd 100644 --- a/docs/sphinx/source/whatsnew/v0.9.3.rst +++ b/docs/sphinx/source/whatsnew/v0.9.3.rst @@ -9,6 +9,10 @@ Deprecations Enhancements ~~~~~~~~~~~~ +* New class and function translate module temperature model parameters + :py:func:`~pvlib.temperature.GenericLinearModel` + :py:func:`~pvlib.temperature.generic_linear` + (:issue:`1442`, :pull:`1463`) * Low resolution altitude lookup map :py:func:`~pvlib.location.lookup_altitude` (:issue:`1516`, :pull:`1518`) @@ -37,3 +41,8 @@ Contributors ~~~~~~~~~~~~ * João Guilherme (:ghuser:`joaoguilhermeS`) * Nicolas Martinez (:ghuser:`nicomt`) +* Anton Driesse (:ghuser:`adriesse`) +* Cliff Hansen (:ghuser:`cwhanse`) +* Kevin Anderson (:ghuser:`kanderso-nrel`) +* Mark Mikofski (:ghuser:`mikofski`) +* Will Holmgren (:ghuser:`wholmgren`)