From 84dd8b075a2e17d01e629d14a1e3757f65268055 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 17 Oct 2026 12:25:47 -0400 Subject: [PATCH 1/2] Use effective irradiance directly in temperature.noct_sam Match SAM's corrected NOCT cell temperature model (NREL/ssc#1233): the temperature rise is computed from the effective irradiance, and the transmittance-absorptance product is no longer scaled by the ratio of effective to total irradiance. Previously, reflection losses did not reduce the modeled temperature of an open-circuit module, and small ratios gave cell temperatures below ambient. The first parameter of noct_sam is now effective_irradiance, poa_global is removed, and the optional parameters are keyword-only. The previous signature is accepted with a deprecation warning until v0.18.0. PVSystem.get_cell_temperature uses effective_irradiance when given and poa_global otherwise. Closes #1462 Co-Authored-By: Claude Fable 5.1 --- docs/sphinx/source/whatsnew/v0.16.2.rst | 12 ++ pvlib/pvsystem.py | 15 +- pvlib/temperature.py | 116 ++++++++++++---- tests/test_pvsystem.py | 28 +++- tests/test_temperature.py | 175 +++++++++++++++++------- 5 files changed, 258 insertions(+), 88 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.16.2.rst b/docs/sphinx/source/whatsnew/v0.16.2.rst index fff7ba5d46..02c6744a36 100644 --- a/docs/sphinx/source/whatsnew/v0.16.2.rst +++ b/docs/sphinx/source/whatsnew/v0.16.2.rst @@ -10,10 +10,21 @@ Breaking Changes Deprecations ~~~~~~~~~~~~ +* The signature of :py:func:`pvlib.temperature.noct_sam` has changed: the + first parameter is now ``effective_irradiance``, ``poa_global`` is removed, + and the remaining optional parameters are keyword-only. Calls using the + previous signature still work but emit a deprecation warning, and support + for them will be removed in v0.18.0. (:issue:`1462`, :pull:`TBD`) Bug fixes ~~~~~~~~~ +* :py:func:`pvlib.temperature.noct_sam` now calculates cell temperature from + ``effective_irradiance`` directly, matching the corrected SAM implementation + (NREL/ssc#1233). Previously the ratio of effective to total irradiance was + folded into ``transmittance_absorptance``, so reflection losses did not + reduce the modeled temperature of an open-circuit module, and small ratios + gave cell temperatures below ambient. (:issue:`1462`, :pull:`TBD`) * :py:func:`pvlib.tracking.calc_surface_orientation` returns scalar when input is scalar (:issue:`2881`, :pull:`2882`) @@ -62,6 +73,7 @@ Contributors * Chen Yi (:ghuser:`chenyi6758-source`) * Rajiv Daxini (:ghuser:`rdaxini`) * jason-writer (:ghuser:`jason-writer`) +* :ghuser:`asystemoffields` diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 79d8a58d11..76dfe5e86e 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -483,7 +483,9 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, effective_irradiance : numeric or tuple of numeric, optional The irradiance that is converted to photocurrent in W/m^2. - Only used for some models. + Only used for some models. For ``'noct_sam'``, the cell + temperature is calculated from ``effective_irradiance`` when it + is provided and from ``poa_global`` otherwise. longwave_down: numeric or tuple of numeric, optional Downwelling long-wave radiation from the sky, measured on a @@ -1368,7 +1370,9 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, effective_irradiance : numeric, optional The irradiance that is converted to photocurrent in W/m^2. - Only used for some models. + Only used for some models. For ``'noct_sam'``, the cell + temperature is calculated from ``effective_irradiance`` when it + is provided and from ``poa_global`` otherwise. longwave_down: numeric, optional Downwelling long-wave radiation from the sky, measured on a @@ -1432,8 +1436,11 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, if self.mount.module_height is not None: optional['module_height'] = self.mount.module_height elif model == 'noct_sam': - func = functools.partial(temperature.noct_sam, - effective_irradiance=effective_irradiance) + func = temperature.noct_sam + # noct_sam takes the irradiance absorbed by the module; fall + # back to poa_global when effective_irradiance is not provided + if effective_irradiance is not None: + poa_global = effective_irradiance required = _build_tcell_args(['noct', 'module_efficiency']) optional = _build_kwargs(['transmittance_absorptance', 'array_height', 'mount_standoff'], diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 55222c0fb1..35c6dffe57 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -10,6 +10,8 @@ import scipy import scipy.constants import warnings +import functools +from pvlib._deprecation import pvlibDeprecationWarning TEMPERATURE_MODEL_PARAMETERS = { @@ -937,18 +939,76 @@ def _adj_for_mounting_standoff(x): [0., 18., 11., 6., 2., 0.]) -def noct_sam(poa_global, temp_air, wind_speed, noct, module_efficiency, - effective_irradiance=None, transmittance_absorptance=0.9, +def _noct_sam_legacy_signature(func): + # Deprecation shim for the signature of noct_sam before v0.16.2: + # noct_sam(poa_global, temp_air, wind_speed, noct, module_efficiency, + # effective_irradiance=None, transmittance_absorptance=0.9, + # array_height=1, mount_standoff=4) + # Remove in v0.18.0, together with the tests decorated with + # @fail_on_pvlib_version('0.18.0') in tests/test_temperature.py. + legacy_optional = ('transmittance_absorptance', 'array_height', + 'mount_standoff') + preamble = ( + "The signature of pvlib.temperature.noct_sam changed in pvlib 0.16.2:" + " the first parameter is now effective_irradiance, poa_global was" + " removed, and the optional parameters are keyword-only. ") + epilogue = " Support for the previous signature will be removed in 0.18.0." + + def _warn(detail): + warnings.warn(preamble + detail + epilogue, pvlibDeprecationWarning, + stacklevel=3) + + @functools.wraps(func) + def wrapper(*args, **kwargs): + poa_global = kwargs.pop('poa_global', None) + has_eff_kw = 'effective_irradiance' in kwargs + eff_kw = kwargs.pop('effective_irradiance', None) + if len(args) > 9: + raise TypeError(f"noct_sam() takes at most 9 positional arguments" + f" ({len(args)} given)") + if len(args) > 5: + # legacy positional effective_irradiance (and options) + _warn("Pass effective_irradiance as the first argument, and" + " transmittance_absorptance, array_height and" + " mount_standoff by keyword.") + kwargs.update(zip(legacy_optional, args[6:])) + args = (args[5],) + tuple(args[1:5]) + elif eff_kw is not None: + if args: + _warn("The positional poa_global argument is ignored; pass" + " effective_irradiance as the first argument.") + args = (eff_kw,) + tuple(args[1:]) + else: + if poa_global is not None: + _warn("The poa_global argument is ignored.") + kwargs['effective_irradiance'] = eff_kw + elif poa_global is not None: + _warn("poa_global was passed in place of effective_irradiance.") + kwargs['effective_irradiance'] = poa_global + elif has_eff_kw: + _warn("effective_irradiance=None is not accepted; the first" + " argument is used as the effective irradiance.") + return func(*args, **kwargs) + + return wrapper + + +@_noct_sam_legacy_signature +def noct_sam(effective_irradiance, temp_air, wind_speed, noct, + module_efficiency, *, transmittance_absorptance=0.9, array_height=1, mount_standoff=4): - r''' + r""" Cell temperature model from the System Advisor Model (SAM). - The model is described in [1]_, Section 10.6. + The model is described in [1]_, Section 10.6, and is implemented as in + SAM [2]_. Parameters ---------- - poa_global : numeric - Total incident irradiance. [W/m^2] + effective_irradiance : numeric + Irradiance absorbed by the module, i.e., plane-of-array irradiance + after reflection and spectral losses, as converted to photocurrent. + [W/m^2] temp_air : numeric Ambient dry bulb temperature. [C] @@ -968,10 +1028,6 @@ def noct_sam(poa_global, temp_air, wind_speed, noct, module_efficiency, :math:`\eta_{m} = \frac{V_{mp} I_{mp}}{A \times 1000 W/m^2}` where A is module area [m^2]. - effective_irradiance : numeric, optional - The irradiance that is converted to photocurrent. If not specified, - assumed equal to poa_global. [W/m^2] - transmittance_absorptance : numeric, default 0.9 Coefficient for combined transmittance and absorptance effects. [unitless] @@ -995,25 +1051,27 @@ def noct_sam(poa_global, temp_air, wind_speed, noct, module_efficiency, ValueError If array_height is an invalid value (must be 1 or 2). + Notes + ----- + .. versionchanged:: 0.16.2 + ``effective_irradiance`` replaced ``poa_global`` as the first + parameter, and the optional parameters became keyword-only. + Previously, the ratio of ``effective_irradiance`` to ``poa_global`` + was folded into ``transmittance_absorptance``, so reflection losses + did not reduce the modeled temperature of an open-circuit module, and + small ratios gave cell temperatures below ambient. SAM made the same + correction in [2]_. The previous signature is deprecated and will be + removed in 0.18.0. + References ---------- .. [1] Gilman, P., Dobos, A., DiOrio, N., Freeman, J., Janzou, S., Ryberg, D., 2018, "SAM Photovoltaic Model Technical Reference Update", National Renewable Energy Laboratory Report NREL/TP-6A20-67399. - ''' - # in [1] the denominator for irr_ratio isn't precisely clear. From - # reproducing output of the SAM function noct_celltemp_t, we determined - # that: - # - G_total (SAM) is broadband plane-of-array irradiance before - # reflections. Equivalent to pvlib variable poa_global - # - Geff_total (SAM) is POA irradiance after reflections and - # adjustment for spectrum. Equivalent to effective_irradiance - if effective_irradiance is None: - irr_ratio = 1. - else: - irr_ratio = effective_irradiance / poa_global - + .. [2] NREL/ssc pull request 1233, "Noct taualpha", merged 2024-11-04. + https://github.com/NREL/ssc/pull/1233 + """ if array_height == 1: wind_adj = 0.51 * wind_speed elif array_height == 2: @@ -1023,12 +1081,14 @@ def noct_sam(poa_global, temp_air, wind_speed, noct, module_efficiency, f'array_height must be 1 or 2, {array_height} was given') noct_adj = noct + _adj_for_mounting_standoff(mount_standoff) - tau_alpha = transmittance_absorptance * irr_ratio - # [1] Eq. 10.37 isn't clear on exactly what "G" is. SAM SSC code uses - # poa_global where G appears - cell_temp_init = poa_global / 800. * (noct_adj - 20.) - heat_loss = 1 - module_efficiency / tau_alpha + # [1] Eq. 10.37 isn't explicit about which irradiance "G" is. SAM + # (noct_celltemp_t in shared/lib_cec6par.cpp) used the total POA + # irradiance and scaled tau_alpha by the ratio of effective to total + # irradiance until NREL/ssc#1233, which switched to the effective + # irradiance and removed the scaling. pvlib follows SAM. + cell_temp_init = effective_irradiance / 800. * (noct_adj - 20.) + heat_loss = 1 - module_efficiency / transmittance_absorptance wind_loss = 9.5 / (5.7 + 3.8 * wind_adj) return temp_air + cell_temp_init * heat_loss * wind_loss diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 71a515ab03..a3b127e2cc 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -621,8 +621,7 @@ def test_PVSystem_noct_celltemp(mocker): out = system.get_cell_temperature(poa_global, temp_air, wind_speed, model='noct_sam') temperature.noct_sam.assert_called_once_with( - poa_global, temp_air, wind_speed, noct, module_efficiency, - effective_irradiance=None) + poa_global, temp_air, wind_speed, noct, module_efficiency) assert_allclose(out, expected) # different types out = system.get_cell_temperature(np.array(poa_global), np.array(temp_air), @@ -639,7 +638,8 @@ def test_PVSystem_noct_celltemp(mocker): temp_model_params.update({'transmittance_absorptance': 0.8, 'array_height': 2, 'mount_standoff': 2.0}) - expected = 60.477703576 + # effective_irradiance is used in place of poa_global + expected = 62.877665877 system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) out = system.get_cell_temperature(poa_global, temp_air, wind_speed, effective_irradiance=1100., @@ -647,6 +647,28 @@ def test_PVSystem_noct_celltemp(mocker): assert_allclose(out, expected) +def test_PVSystem_noct_celltemp_against_sam(): + # poa_global and effective_irradiance both given, as ModelChain does. + # Reference is SAM's cell temperature for hour 11 of + # pvlib/data/723170TYA.CSV; see test_temperature.test_noct_sam_against_sam + # for the SAM configuration. poa_global is SAM's POA irradiance before + # reflection (subarray1_poa_shaded_soiled); effective_irradiance is the + # POA irradiance after reflection times the air mass modifier. + zenith = 60.4221 + air_mass = 1. / (np.cos(np.radians(zenith)) + + 0.5057 * (96.080 - zenith)**-1.634) + air_mass *= np.exp(-0.0001184 * 273.) + a = np.flipud([0.918093, 0.086257, -0.024459, 0.002816, -0.000126]) + effective_irradiance = 265.4939 * np.polyval(a, air_mass) + temp_model_params = {'noct': 43.1, + 'module_efficiency': 12.82 * 41.4 / (1000. * 2.58)} + system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) + out = system.get_cell_temperature( + 272.9785, 11.7, 5.2, model='noct_sam', + effective_irradiance=effective_irradiance) + assert_allclose(out, 15.305202, atol=1e-5) + + def test_PVSystem_noct_celltemp_error(): poa_global, temp_air, wind_speed, module_efficiency = (1000., 25., 1., 0.2) temp_model_params = {'module_efficiency': module_efficiency} diff --git a/tests/test_temperature.py b/tests/test_temperature.py index f7cc242477..17cb4e683e 100644 --- a/tests/test_temperature.py +++ b/tests/test_temperature.py @@ -2,12 +2,15 @@ import numpy as np import pytest -from .conftest import TESTS_DATA_DIR, assert_series_equal +from .conftest import ( + TESTS_DATA_DIR, assert_series_equal, fail_on_pvlib_version) from numpy.testing import assert_allclose from pvlib import temperature, tools +from pvlib._deprecation import pvlibDeprecationWarning import re +import warnings @pytest.fixture @@ -285,76 +288,142 @@ def test_fuentes_timezone(tz): def test_noct_sam(): - poa_global, temp_air, wind_speed, noct, module_efficiency = ( + effective_irradiance, temp_air, wind_speed, noct, module_efficiency = ( 1000., 25., 1., 45., 0.2) expected = 55.230790492 - result = temperature.noct_sam(poa_global, temp_air, wind_speed, noct, - module_efficiency) + result = temperature.noct_sam(effective_irradiance, temp_air, wind_speed, + noct, module_efficiency) assert_allclose(result, expected) # test with different types - result = temperature.noct_sam(np.array(poa_global), np.array(temp_air), - np.array(wind_speed), np.array(noct), - np.array(module_efficiency)) + result = temperature.noct_sam(np.array(effective_irradiance), + np.array(temp_air), np.array(wind_speed), + np.array(noct), np.array(module_efficiency)) assert_allclose(result, expected) dr = pd.date_range(start='2020-01-01 12:00:00', end='2020-01-01 13:00:00', freq='1h') - result = temperature.noct_sam(pd.Series(index=dr, data=poa_global), - pd.Series(index=dr, data=temp_air), - pd.Series(index=dr, data=wind_speed), - pd.Series(index=dr, data=noct), - module_efficiency) + result = temperature.noct_sam( + pd.Series(index=dr, data=effective_irradiance), + pd.Series(index=dr, data=temp_air), + pd.Series(index=dr, data=wind_speed), + pd.Series(index=dr, data=noct), + module_efficiency) assert_series_equal(result, pd.Series(index=dr, data=expected)) def test_noct_sam_against_sam(): - # test is constructed to reproduce output from SAM v2020.11.29. - # SAM calculation is the default Detailed PV System model (CEC diode model, - # NOCT cell temperature model), with the only change being the soiling - # loss is set to 0. Weather input is TMY3 for Phoenix AZ. - # Values are taken from the Jan 1 12:00:00 timestamp. - poa_total, temp_air, wind_speed, noct, module_efficiency = ( - 860.673, 25, 3, 46.4, 0.20551) - poa_total_after_refl = 851.458 # from SAM output - # compute effective irradiance - # spectral loss coefficients fixed in lib_cec6par.cpp + # Reproduces SAM's steady-state cell temperature output + # (subarray1_celltempSS) from SAM via NREL-PySAM 7.1.1. Configuration: + # Pvsamv1 default "FlatPlatePVSingleOwner" with the NOCT cell temperature + # model (cec_temp_corr_mode=0), fixed tilt of 20 degrees, monofacial + # module, soiling and snow losses off. Weather is pvlib/data/723170TYA.CSV + # (Greensboro, NC, elevation 273 m). Module: NOCT 43.1 C, Imp 12.82 A, + # Vmp 41.4 V, area 2.58 m2, ground/rack mounting (no standoff + # adjustment), one-story array height. + # SAM's effective irradiance is the POA irradiance after reflection + # (output subarray1_poa_front) times the air mass modifier f1 with the + # DeSoto coefficients, using absolute air mass from the Kasten-Young + # formula with zenith clipped at 86 degrees and the elevation correction + # exp(-0.0001184 * elevation). + noct = 43.1 + module_efficiency = 12.82 * 41.4 / (1000. * 2.58) a = np.flipud([0.918093, 0.086257, -0.024459, 0.002816, -0.000126]) - # reproduce SAM air mass calculation - zen = 56.4284 - elev = 358 - air_mass = 1. / (tools.cosd(zen) + 0.5057 * (96.080 - zen)**-1.634) - air_mass *= np.exp(-0.0001184 * elev) - f1 = np.polyval(a, air_mass) - effective_irradiance = f1 * poa_total_after_refl - transmittance_absorptance = 0.9 - array_height = 1 - mount_standoff = 4.0 - result = temperature.noct_sam(poa_total, temp_air, wind_speed, noct, - module_efficiency, effective_irradiance, - transmittance_absorptance, array_height, - mount_standoff) - expected = 43.0655 - # rtol from limited SAM output precision - assert_allclose(result, expected, rtol=1e-5) + # hour of year: (poa_front, zenith, temp_air, wind_speed, SAM cell temp) + cases = { + 11: (265.4939, 60.4221, 11.7, 5.2, 15.305202), + 4117: (432.4450, 19.4464, 25.0, 5.2, 30.705839), + 6562: (346.9347, 45.8076, 16.7, 4.1, 22.067894), + } + for poa_front, zenith, temp_air, wind_speed, expected in cases.values(): + zenith = min(zenith, 86.) + air_mass = 1. / (tools.cosd(zenith) + + 0.5057 * (96.080 - zenith)**-1.634) + air_mass *= np.exp(-0.0001184 * 273.) + effective_irradiance = poa_front * np.polyval(a, air_mass) + result = temperature.noct_sam(effective_irradiance, temp_air, + wind_speed, noct, module_efficiency) + assert_allclose(result, expected, atol=1e-5) def test_noct_sam_options(): - poa_global, temp_air, wind_speed, noct, module_efficiency = ( - 1000., 25., 1., 45., 0.2) - effective_irradiance = 1100. - transmittance_absorptance = 0.8 - array_height = 2 - mount_standoff = 2.0 - result = temperature.noct_sam(poa_global, temp_air, wind_speed, noct, - module_efficiency, effective_irradiance, - transmittance_absorptance, array_height, - mount_standoff) - expected = 60.477703576 + effective_irradiance, temp_air, wind_speed, noct, module_efficiency = ( + 1100., 25., 1., 45., 0.2) + result = temperature.noct_sam(effective_irradiance, temp_air, wind_speed, + noct, module_efficiency, + transmittance_absorptance=0.8, + array_height=2, mount_standoff=2.0) + expected = 62.877665877 assert_allclose(result, expected) -def test_noct_sam_errors(): - with pytest.raises(ValueError): - temperature.noct_sam(1000., 25., 1., 34., 0.2, array_height=3) +def test_noct_sam_effective_irradiance(): + # GH 1462: a lower effective irradiance (e.g. reflection losses) must + # lower the cell temperature, also for an open-circuit module, and the + # cell can't be cooler than the air. + kwargs = dict(temp_air=20., wind_speed=1. / 0.51, noct=41.6) + for module_efficiency in (0., 0.2): + full = temperature.noct_sam(800., module_efficiency=module_efficiency, + **kwargs) + reduced = temperature.noct_sam(600., + module_efficiency=module_efficiency, + **kwargs) + tiny = temperature.noct_sam(160., module_efficiency=module_efficiency, + **kwargs) + assert tiny > kwargs['temp_air'] + assert tiny < reduced < full + # temperature rise is proportional to effective irradiance + assert_allclose(reduced - 20., 0.75 * (full - 20.)) + + +def test_noct_sam_new_signature_no_warning(): + with warnings.catch_warnings(): + warnings.simplefilter('error', pvlibDeprecationWarning) + temperature.noct_sam(600., 25., 1., 45., 0.2, + transmittance_absorptance=0.8, array_height=2, + mount_standoff=2.0) + temperature.noct_sam(effective_irradiance=600., temp_air=25., + wind_speed=1., noct=45., module_efficiency=0.2) + + +@fail_on_pvlib_version('0.18.0') +def test_noct_sam_legacy_signature(): + # The signature before v0.16.2 is accepted with a deprecation warning and + # gives the same result as the new signature; only effective_irradiance + # determines the cell temperature. Remove with the shim in temperature.py. + expected = temperature.noct_sam(600., 25., 1., 45., 0.2, + transmittance_absorptance=0.8, + array_height=2, mount_standoff=2.0) + options = dict(transmittance_absorptance=0.8, array_height=2, + mount_standoff=2.0) + legacy_calls = [ + # everything positional + ((800., 25., 1., 45., 0.2, 600., 0.8, 2, 2.0), {}), + # positional poa_global, keyword effective_irradiance + ((800., 25., 1., 45., 0.2), dict(effective_irradiance=600., + **options)), + # keyword poa_global and effective_irradiance + ((), dict(poa_global=800., temp_air=25., wind_speed=1., noct=45., + module_efficiency=0.2, effective_irradiance=600., + **options)), + # keyword poa_global in place of effective_irradiance + ((), dict(poa_global=600., temp_air=25., wind_speed=1., noct=45., + module_efficiency=0.2, **options)), + # explicit effective_irradiance=None + ((600., 25., 1., 45., 0.2), dict(effective_irradiance=None, + **options)), + ] + for args, kwargs in legacy_calls: + with pytest.warns(pvlibDeprecationWarning, + match='effective_irradiance'): + result = temperature.noct_sam(*args, **kwargs) + assert_allclose(result, expected) + # array inputs through the legacy signature + with pytest.warns(pvlibDeprecationWarning): + result = temperature.noct_sam( + np.array([800., 800.]), 25., 1., 45., 0.2, + effective_irradiance=np.array([600., 300.]), **options) + assert_allclose(result, [expected, 25. + 0.5 * (expected - 25.)]) + with pytest.raises(TypeError): + temperature.noct_sam(800., 25., 1., 45., 0.2, 600., 0.8, 2, 2.0, 1) def test_prilliman(): From d548a34af4dc110cc4db364a884e657391c1a16d Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 17 Oct 2026 22:08:18 -0400 Subject: [PATCH 2/2] Add the PR number to the whatsnew entries Co-Authored-By: Claude Fable 5.1 --- docs/sphinx/source/whatsnew/v0.16.2.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.16.2.rst b/docs/sphinx/source/whatsnew/v0.16.2.rst index 02c6744a36..d9568683f5 100644 --- a/docs/sphinx/source/whatsnew/v0.16.2.rst +++ b/docs/sphinx/source/whatsnew/v0.16.2.rst @@ -14,7 +14,7 @@ Deprecations first parameter is now ``effective_irradiance``, ``poa_global`` is removed, and the remaining optional parameters are keyword-only. Calls using the previous signature still work but emit a deprecation warning, and support - for them will be removed in v0.18.0. (:issue:`1462`, :pull:`TBD`) + for them will be removed in v0.18.0. (:issue:`1462`, :pull:`2905`) Bug fixes @@ -24,7 +24,7 @@ Bug fixes (NREL/ssc#1233). Previously the ratio of effective to total irradiance was folded into ``transmittance_absorptance``, so reflection losses did not reduce the modeled temperature of an open-circuit module, and small ratios - gave cell temperatures below ambient. (:issue:`1462`, :pull:`TBD`) + gave cell temperatures below ambient. (:issue:`1462`, :pull:`2905`) * :py:func:`pvlib.tracking.calc_surface_orientation` returns scalar when input is scalar (:issue:`2881`, :pull:`2882`)