From 05f54087e7bca678f6735bfdc8f4a202d4bbece7 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 7 Oct 2025 08:03:14 -0500 Subject: [PATCH 01/41] add marion's adjustment --- pvlib/pvsystem.py | 54 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 2b703f3a52..14c96ff6bf 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2878,14 +2878,36 @@ def scale_voltage_current_power(data, voltage=1, current=1): @renamed_kwarg_warning( "0.13.0", "g_poa_effective", "effective_irradiance") -def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25.): +def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., + k=0.0, capped_adjustment=False): r""" - Implements NREL's PVWatts DC power model. The PVWatts DC model [1]_ is: + Implements NREL's PVWatts (Version 5) DC power model. The PVWatts Version + 5 DC model [1]_ is: .. math:: P_{dc} = \frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \gamma_{pdc} (T_{cell} - T_{ref})) + This model has also been referred to as the power temperature coefficient + model. + + This function accepts an optional irradiance adjustment factor, `k`, based + on based on [2]_. This applies a piece-wise adjustment to power based on + irradiance, where `k` is the reduction in actual power at 200 W/m^2 + relative to ideal power calculated linearly from standard test conditions, + normalized to nameplate power at standard test conditions. + + .. math:: + + k=\frac{0.2P_{dc0}-P_{200}}{P_{dc0}} + + For example, a 500 W module that produces 95 W at 200 W/m^2 (a 5% relative + reduction in efficiency) would have a value of `k` = 0.01. + + This adjustment increases relative efficiency for irradiance above 1000 + W/m^2, which may not be desired. An optional input, `capped_adjustment`, + modifies the adjustment from [2]_ to only apply below 1000 W/m^2. + Note that ``pdc0`` is also used as a symbol in :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC power of the modules at reference conditions. ``pdc0`` in @@ -2909,6 +2931,11 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25.): temp_ref: numeric, default 25.0 Cell reference temperature. PVWatts defines it to be 25 C and is included here for flexibility. [C] + k: numeric, default 0.005 + Irradiance correction factor, defined in [2]_. [unitless] + modified_marion: Boolean, default False + Optional modification to [2]_, where no adjustment is applied above + 1000 W/m^2. Returns ------- @@ -2920,11 +2947,34 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25.): .. [1] A. P. Dobos, "PVWatts Version 5 Manual" http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf (2014). + .. [2] B. Marion, "Comparison of Predictive Models for + Photovoltaic Module Performance," + https://doi.org/10.1109/PVSC.2008.4922586, + https://docs.nrel.gov/docs/fy08osti/42511.pdf + (2008). """ # noqa: E501 pdc = (effective_irradiance * 0.001 * pdc0 * (1 + gamma_pdc * (temp_cell - temp_ref))) + # apply Marion's correction if k is anything but zero + if k != 0: + err_1 = (k * (1 - (1 - effective_irradiance / 200)**4) / + (effective_irradiance / 1000)) + err_2 = (k * (1000 - effective_irradiance) / (1000 - 200)) + + pdc_marion = np.where(effective_irradiance <= 200, + pdc * (1 - err_1), + pdc * (1 - err_2)) + + # "cap" Marion's correction at 1000 W/m^2 + if capped_adjustment is True: + pdc_marion = np.where(effective_irradiance >= 1000, + pdc, + pdc_marion) + + pdc = pdc_marion + return pdc From 6d7ae12f6e0e3d352017dc2f3ca271519460f4d6 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:52:45 -0500 Subject: [PATCH 02/41] linting --- pvlib/pvsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 14c96ff6bf..c3b241339c 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2907,7 +2907,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., This adjustment increases relative efficiency for irradiance above 1000 W/m^2, which may not be desired. An optional input, `capped_adjustment`, modifies the adjustment from [2]_ to only apply below 1000 W/m^2. - + Note that ``pdc0`` is also used as a symbol in :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC power of the modules at reference conditions. ``pdc0`` in @@ -2948,7 +2948,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf (2014). .. [2] B. Marion, "Comparison of Predictive Models for - Photovoltaic Module Performance," + Photovoltaic Module Performance," https://doi.org/10.1109/PVSC.2008.4922586, https://docs.nrel.gov/docs/fy08osti/42511.pdf (2008). From 40328c836840880ecd4e8c4239044c907459cbdc Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:56:39 -0500 Subject: [PATCH 03/41] Update pvlib/pvsystem.py Co-authored-by: Cliff Hansen --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index c3b241339c..14018f4fe3 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2933,7 +2933,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., is included here for flexibility. [C] k: numeric, default 0.005 Irradiance correction factor, defined in [2]_. [unitless] - modified_marion: Boolean, default False + cap_adjustment: Boolean, default False Optional modification to [2]_, where no adjustment is applied above 1000 W/m^2. From a9dfb281277210f3f23733c455c314893c8a4eb3 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:56:49 -0500 Subject: [PATCH 04/41] Update pvlib/pvsystem.py Co-authored-by: Cliff Hansen --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 14018f4fe3..5ce37bcdad 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2931,7 +2931,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., temp_ref: numeric, default 25.0 Cell reference temperature. PVWatts defines it to be 25 C and is included here for flexibility. [C] - k: numeric, default 0.005 + k: numeric, optional Irradiance correction factor, defined in [2]_. [unitless] cap_adjustment: Boolean, default False Optional modification to [2]_, where no adjustment is applied above From 551e722632ae28519ccbc56df27affb14c6f707f Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:57:18 -0500 Subject: [PATCH 05/41] Update pvlib/pvsystem.py Co-authored-by: Cliff Hansen --- pvlib/pvsystem.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 5ce37bcdad..5379776e3a 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2934,8 +2934,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., k: numeric, optional Irradiance correction factor, defined in [2]_. [unitless] cap_adjustment: Boolean, default False - Optional modification to [2]_, where no adjustment is applied above - 1000 W/m^2. + If True, apply the optional adjustment at and below 1000 W/m^2. Returns ------- From 73e80d3fccf1d7f36a7cdec0ce5dd818b7a41854 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:57:25 -0500 Subject: [PATCH 06/41] Update pvlib/pvsystem.py Co-authored-by: Cliff Hansen --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 5379776e3a..1e54ea2c33 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2957,7 +2957,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., (1 + gamma_pdc * (temp_cell - temp_ref))) # apply Marion's correction if k is anything but zero - if k != 0: + if k is not None: err_1 = (k * (1 - (1 - effective_irradiance / 200)**4) / (effective_irradiance / 1000)) err_2 = (k * (1000 - effective_irradiance) / (1000 - 200)) From f25b42634b4d1dd3cc6efcc35ec9e14593ff46c2 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:57:53 -0500 Subject: [PATCH 07/41] Update pvlib/pvsystem.py Co-authored-by: Cliff Hansen --- pvlib/pvsystem.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 1e54ea2c33..9f75b7cb22 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2894,8 +2894,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., This function accepts an optional irradiance adjustment factor, `k`, based on based on [2]_. This applies a piece-wise adjustment to power based on irradiance, where `k` is the reduction in actual power at 200 W/m^2 - relative to ideal power calculated linearly from standard test conditions, - normalized to nameplate power at standard test conditions. + relative to power calculated at 200 W/m^2 as 0.2*`pdc0`. .. math:: From 380bb5895cee96bc34d4318093f95dd6010a9c13 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:01:27 -0500 Subject: [PATCH 08/41] more suggested changes --- pvlib/pvsystem.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 9f75b7cb22..064b75fdaf 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2879,7 +2879,7 @@ def scale_voltage_current_power(data, voltage=1, current=1): @renamed_kwarg_warning( "0.13.0", "g_poa_effective", "effective_irradiance") def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., - k=0.0, capped_adjustment=False): + k=None, cap_adjustment=False): r""" Implements NREL's PVWatts (Version 5) DC power model. The PVWatts Version 5 DC model [1]_ is: @@ -2892,17 +2892,16 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., model. This function accepts an optional irradiance adjustment factor, `k`, based - on based on [2]_. This applies a piece-wise adjustment to power based on - irradiance, where `k` is the reduction in actual power at 200 W/m^2 - relative to power calculated at 200 W/m^2 as 0.2*`pdc0`. + on [2]_. This applies a piece-wise adjustment to power based on irradiance, + where `k` is the reduction in actual power at 200 W/m^2 relative to power + calculated at 200 W/m^2 as 0.2*`pdc0`. For example, a 500 W module that + produces 95 W at 200 W/m^2 (a 5% relative reduction in efficiency) would + have a value of `k` = 0.01. .. math:: k=\frac{0.2P_{dc0}-P_{200}}{P_{dc0}} - For example, a 500 W module that produces 95 W at 200 W/m^2 (a 5% relative - reduction in efficiency) would have a value of `k` = 0.01. - This adjustment increases relative efficiency for irradiance above 1000 W/m^2, which may not be desired. An optional input, `capped_adjustment`, modifies the adjustment from [2]_ to only apply below 1000 W/m^2. @@ -2966,7 +2965,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., pdc * (1 - err_2)) # "cap" Marion's correction at 1000 W/m^2 - if capped_adjustment is True: + if cap_adjustment is True: pdc_marion = np.where(effective_irradiance >= 1000, pdc, pdc_marion) From 5d31dfd0a2708d7d85a4c53b958c100c9142340f Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:41:41 -0500 Subject: [PATCH 09/41] Update pvlib/pvsystem.py doi sphinx Co-authored-by: RDaxini <143435106+RDaxini@users.noreply.github.com> --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 064b75fdaf..8d5755dd14 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2946,7 +2946,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., (2014). .. [2] B. Marion, "Comparison of Predictive Models for Photovoltaic Module Performance," - https://doi.org/10.1109/PVSC.2008.4922586, + :doi:`10.1109/PVSC.2008.4922586`, https://docs.nrel.gov/docs/fy08osti/42511.pdf (2008). """ # noqa: E501 From ad146b508b122d41ed7d252e5166d3a28e342999 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:42:13 -0500 Subject: [PATCH 10/41] Update pvlib/pvsystem.py Co-authored-by: RDaxini <143435106+RDaxini@users.noreply.github.com> --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 8d5755dd14..be003e8d54 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2893,7 +2893,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., This function accepts an optional irradiance adjustment factor, `k`, based on [2]_. This applies a piece-wise adjustment to power based on irradiance, - where `k` is the reduction in actual power at 200 W/m^2 relative to power + where `k` is the reduction in actual power at 200 Wm⁻² relative to power calculated at 200 W/m^2 as 0.2*`pdc0`. For example, a 500 W module that produces 95 W at 200 W/m^2 (a 5% relative reduction in efficiency) would have a value of `k` = 0.01. From 69ce5e743507cc00cb7d090f42a9ec91f4c72449 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:42:26 -0500 Subject: [PATCH 11/41] Update pvlib/pvsystem.py Co-authored-by: RDaxini <143435106+RDaxini@users.noreply.github.com> --- pvlib/pvsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index be003e8d54..211ac2d5b2 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2903,8 +2903,8 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., k=\frac{0.2P_{dc0}-P_{200}}{P_{dc0}} This adjustment increases relative efficiency for irradiance above 1000 - W/m^2, which may not be desired. An optional input, `capped_adjustment`, - modifies the adjustment from [2]_ to only apply below 1000 W/m^2. + Wm⁻², which may not be desired. An optional input, `capped_adjustment`, + modifies the adjustment from [2]_ to only apply below 1000 Wm⁻². Note that ``pdc0`` is also used as a symbol in :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC From dc58507188ef4df9c1b4a6f4e28dd88bfea7538b Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:54:17 -0500 Subject: [PATCH 12/41] cleaning up docs --- pvlib/pvsystem.py | 63 +++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 211ac2d5b2..bb9a8b903e 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2881,36 +2881,7 @@ def scale_voltage_current_power(data, voltage=1, current=1): def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., k=None, cap_adjustment=False): r""" - Implements NREL's PVWatts (Version 5) DC power model. The PVWatts Version - 5 DC model [1]_ is: - - .. math:: - - P_{dc} = \frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \gamma_{pdc} (T_{cell} - T_{ref})) - - This model has also been referred to as the power temperature coefficient - model. - - This function accepts an optional irradiance adjustment factor, `k`, based - on [2]_. This applies a piece-wise adjustment to power based on irradiance, - where `k` is the reduction in actual power at 200 Wm⁻² relative to power - calculated at 200 W/m^2 as 0.2*`pdc0`. For example, a 500 W module that - produces 95 W at 200 W/m^2 (a 5% relative reduction in efficiency) would - have a value of `k` = 0.01. - - .. math:: - - k=\frac{0.2P_{dc0}-P_{200}}{P_{dc0}} - - This adjustment increases relative efficiency for irradiance above 1000 - Wm⁻², which may not be desired. An optional input, `capped_adjustment`, - modifies the adjustment from [2]_ to only apply below 1000 Wm⁻². - - Note that ``pdc0`` is also used as a symbol in - :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC - power of the modules at reference conditions. ``pdc0`` in - :py:func:`pvlib.inverter.pvwatts` refers to the DC power input limit of - the inverter. + Implement NREL's PVWatts (Version 5) DC power model. Parameters ---------- @@ -2939,6 +2910,38 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., pdc: numeric DC power. [W] + Notes + ----- + The PVWatts Version 5 DC model [1]_ is: + + .. math:: + + P_{dc} = \frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \gamma_{pdc} (T_{cell} - T_{ref})) + + This model has also been referred to as the power temperature coefficient + model. + + This function accepts an optional irradiance adjustment factor, `k`, based + on [2]_. This applies a piece-wise adjustment to power based on irradiance, + where `k` is the reduction in actual power at 200 Wm⁻² relative to power + calculated at 200 Wm-2 as 0.2*`pdc0`. For example, a 500 W module that + produces 95 W at 200 Wm-2 (a 5% relative reduction in efficiency) would + have a value of `k` = 0.01. + + .. math:: + + k=\frac{0.2P_{dc0}-P_{200}}{P_{dc0}} + + This adjustment increases relative efficiency for irradiance above 1000 + Wm⁻², which may not be desired. An optional input, `capped_adjustment`, + modifies the adjustment from [2]_ to only apply below 1000 Wm⁻². + + Note that ``pdc0`` is also used as a symbol in + :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC + power of the modules at reference conditions. ``pdc0`` in + :py:func:`pvlib.inverter.pvwatts` refers to the DC power input limit of + the inverter. + References ---------- .. [1] A. P. Dobos, "PVWatts Version 5 Manual" From 0d8b0b5f2b1e54b44f8b8cead089779a8a37bdbe Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:00:01 -0600 Subject: [PATCH 13/41] Update pvlib/pvsystem.py Co-authored-by: Will Holmgren --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index bb9a8b903e..610519eea4 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2968,7 +2968,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., pdc * (1 - err_2)) # "cap" Marion's correction at 1000 W/m^2 - if cap_adjustment is True: + if cap_adjustment: pdc_marion = np.where(effective_irradiance >= 1000, pdc, pdc_marion) From 90b7c54e7b5c7ce6b3671971a15fde0ffd5e6c9a Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:58:24 -0500 Subject: [PATCH 14/41] prevent negative power --- pvlib/pvsystem.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index bb9a8b903e..57b48d644d 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2901,7 +2901,8 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., Cell reference temperature. PVWatts defines it to be 25 C and is included here for flexibility. [C] k: numeric, optional - Irradiance correction factor, defined in [2]_. [unitless] + Irradiance correction factor, defined in [2]_. Typically positive. + [unitless] cap_adjustment: Boolean, default False If True, apply the optional adjustment at and below 1000 W/m^2. @@ -2973,6 +2974,10 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., pdc, pdc_marion) + # large k values can result in negative power at low irradiance, so + # set negative power to zero + pdc_marion[pdc_marion < 0] = 0 + pdc = pdc_marion return pdc From 4488e3ce497523f47c9796fe9a55b44ba81d5635 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:04:15 -0500 Subject: [PATCH 15/41] fix typo in error eqn --- pvlib/pvsystem.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index a85861dfa0..2fb02cfd48 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2960,9 +2960,8 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., # apply Marion's correction if k is anything but zero if k is not None: - err_1 = (k * (1 - (1 - effective_irradiance / 200)**4) / - (effective_irradiance / 1000)) - err_2 = (k * (1000 - effective_irradiance) / (1000 - 200)) + err_1 = k * (1 - (1 - effective_irradiance / 200)**4) + err_2 = k * (1000 - effective_irradiance) / (1000 - 200) pdc_marion = np.where(effective_irradiance <= 200, pdc * (1 - err_1), From 250af5a1df3287e3be97fee67581466b1827a0e7 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Wed, 8 Oct 2025 21:15:26 -0500 Subject: [PATCH 16/41] fix the right typo this time... --- pvlib/pvsystem.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 2fb02cfd48..f8ba6a37e1 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2960,8 +2960,10 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., # apply Marion's correction if k is anything but zero if k is not None: - err_1 = k * (1 - (1 - effective_irradiance / 200)**4) - err_2 = k * (1000 - effective_irradiance) / (1000 - 200) + err_1 = (k * (1 - (1 - effective_irradiance / 200)**4) / + (effective_irradiance / 1000)) + err_2 = (k * (1000 - effective_irradiance) / (1000 - 200) / + (effective_irradiance / 1000)) pdc_marion = np.where(effective_irradiance <= 200, pdc * (1 - err_1), From bd16ac2648dfe504bc3aec6233a8e82bef8edfe7 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:07:23 -0500 Subject: [PATCH 17/41] reorder err eqns --- pvlib/pvsystem.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index f8ba6a37e1..04ecbf182c 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2960,14 +2960,12 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., # apply Marion's correction if k is anything but zero if k is not None: - err_1 = (k * (1 - (1 - effective_irradiance / 200)**4) / - (effective_irradiance / 1000)) - err_2 = (k * (1000 - effective_irradiance) / (1000 - 200) / - (effective_irradiance / 1000)) + err_1 = k * (1 - (1 - effective_irradiance / 200)**4) + err_2 = k * (1000 - effective_irradiance) / (1000 - 200) pdc_marion = np.where(effective_irradiance <= 200, - pdc * (1 - err_1), - pdc * (1 - err_2)) + pdc - (pdc0 * err_1), + pdc - (pdc0 * err_2)) # "cap" Marion's correction at 1000 W/m^2 if cap_adjustment: From f3041c21c6817c4ee3c111e9216c27d6d744b642 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:08:17 -0500 Subject: [PATCH 18/41] add more detail on `cap_adjustment` --- pvlib/pvsystem.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 04ecbf182c..f07d698379 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2933,9 +2933,12 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., k=\frac{0.2P_{dc0}-P_{200}}{P_{dc0}} - This adjustment increases relative efficiency for irradiance above 1000 - Wm⁻², which may not be desired. An optional input, `capped_adjustment`, - modifies the adjustment from [2]_ to only apply below 1000 Wm⁻². + For positive `k` values, and `k` is typically positive, this adjustment + increases relative efficiency when irradiance is above 1000 Wm⁻². This may + not be desired, as modules with nonlinear irradiance response often have + peak efficiency near 1000 Wm⁻², and it is either flat or declining at + higher irradiance. An optional parameter, `cap_adjustment`, can address + this by modifying the adjustment from [2]_ to only apply below 1000 Wm⁻². Note that ``pdc0`` is also used as a symbol in :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC From 9f756bf24a233391e90f1e1cf09caf100cfd64b7 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Thu, 16 Oct 2025 09:22:46 -0500 Subject: [PATCH 19/41] return same object type that was input --- pvlib/pvsystem.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index f07d698379..83ee09f820 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2963,18 +2963,27 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., # apply Marion's correction if k is anything but zero if k is not None: + pdc_marion = pdc err_1 = k * (1 - (1 - effective_irradiance / 200)**4) err_2 = k * (1000 - effective_irradiance) / (1000 - 200) - pdc_marion = np.where(effective_irradiance <= 200, - pdc - (pdc0 * err_1), - pdc - (pdc0 * err_2)) + if hasattr(effective_irradiance, '__len__'): + pdc_marion[effective_irradiance <= 200] = pdc - (pdc0 * err_1) + pdc_marion[effective_irradiance > 200] = pdc - (pdc0 * err_2) + else: + if effective_irradiance <= 200: + pdc_marion = pdc - (pdc0 * err_1) + elif effective_irradiance > 200: + pdc_marion = pdc - (pdc0 * err_2) + # "cap" Marion's correction at 1000 W/m^2 if cap_adjustment: - pdc_marion = np.where(effective_irradiance >= 1000, - pdc, - pdc_marion) + if hasattr(effective_irradiance, '__len__'): + pdc_marion[effective_irradiance >= 1000] = pdc + else: + if effective_irradiance >= 1000: + pdc_marion = pdc # large k values can result in negative power at low irradiance, so # set negative power to zero From 17f7213b631ba411b43db5c3a32456c3dbc7ead7 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:33:35 -0500 Subject: [PATCH 20/41] fix issue with scalars --- pvlib/pvsystem.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 83ee09f820..8f7518ff1a 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2976,7 +2976,6 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., elif effective_irradiance > 200: pdc_marion = pdc - (pdc0 * err_2) - # "cap" Marion's correction at 1000 W/m^2 if cap_adjustment: if hasattr(effective_irradiance, '__len__'): @@ -2987,7 +2986,11 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., # large k values can result in negative power at low irradiance, so # set negative power to zero - pdc_marion[pdc_marion < 0] = 0 + if hasattr(effective_irradiance, '__len__'): + pdc_marion[pdc_marion < 0] = 0 + else: + if pdc_marion < 0: + pdc_marion = 0 pdc = pdc_marion From a018525b49b3e818f158dd9ea29ba926d372ee62 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:59:43 -0500 Subject: [PATCH 21/41] another fix --- pvlib/pvsystem.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 8f7518ff1a..c024680f6d 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2968,8 +2968,12 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., err_2 = k * (1000 - effective_irradiance) / (1000 - 200) if hasattr(effective_irradiance, '__len__'): - pdc_marion[effective_irradiance <= 200] = pdc - (pdc0 * err_1) - pdc_marion[effective_irradiance > 200] = pdc - (pdc0 * err_2) + pdc_marion[effective_irradiance <= 200] = ( + pdc[effective_irradiance <= 200] - + (pdc0 * err_1[effective_irradiance <= 200])) + pdc_marion[effective_irradiance > 200] = ( + pdc[effective_irradiance > 200] - + (pdc0 * err_2[effective_irradiance > 200])) else: if effective_irradiance <= 200: pdc_marion = pdc - (pdc0 * err_1) @@ -2979,7 +2983,8 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., # "cap" Marion's correction at 1000 W/m^2 if cap_adjustment: if hasattr(effective_irradiance, '__len__'): - pdc_marion[effective_irradiance >= 1000] = pdc + pdc_marion[effective_irradiance >= 1000] = ( + pdc[effective_irradiance >= 1000]) else: if effective_irradiance >= 1000: pdc_marion = pdc From 816c82a9cfff5cb39d08ab76783cbd20f53d3256 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:59:48 -0500 Subject: [PATCH 22/41] add tests --- tests/test_pvsystem.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index b7d8ba6173..2fb09b9797 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -2181,6 +2181,46 @@ def test_pvwatts_dc_series(): assert_series_equal(expected, out) +def test_pvwatts_dc_scalars_with_k(): + expected = 8.9125 + out = pvsystem.pvwatts_dc(100, 30, 100, -0.003, 25, 0.01) + assert_allclose(out, expected) + + +def test_pvwatts_dc_arrays_with_k(): + irrad_trans = np.array([np.nan, 100, 100]) + temp_cell = np.array([30, np.nan, 30]) + irrad_trans, temp_cell = np.meshgrid(irrad_trans, temp_cell) + expected = np.array([[nan, 8.9125, 8.9125], + [nan, nan, nan], + [nan, 8.9125, 8.9125]]) + out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, 25, 0.01) + assert_allclose(out, expected, equal_nan=True) + + +def test_pvwatts_dc_series_with_k(): + irrad_trans = pd.Series([np.nan, 100, 100]) + temp_cell = pd.Series([30, np.nan, 30]) + expected = pd.Series(np.array([ nan, nan, 8.9125])) + out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, 25, 0.01) + assert_series_equal(expected, out) + + +def test_pvwatts_dc_with_k_and_cap_adjustment(): + irrad_trans = [100, 1200] + temp_cell = 25 + ks = [0.01, 0.02] + cap_adjustments = [False, True] + out = [] + expected = [9.0625, 120.25, 8.125, 120.5, 9.0625, 120.0, 8.125, 120.0] + for cap_adjustment in cap_adjustments: + for k in ks: + for irrad in irrad_trans: + out.append(pvsystem.pvwatts_dc(irrad, temp_cell, 100, -0.003, + 25, k, cap_adjustment)) + assert_allclose(out, expected) + + def test_pvwatts_losses_default(): expected = 14.075660688264469 out = pvsystem.pvwatts_losses() From a19b721239b1fafaee8f1e380fde1a40a1d5faa9 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:53:21 -0500 Subject: [PATCH 23/41] increase test coverage --- tests/test_pvsystem.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 2fb09b9797..d07a9be1e4 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -2199,9 +2199,9 @@ def test_pvwatts_dc_arrays_with_k(): def test_pvwatts_dc_series_with_k(): - irrad_trans = pd.Series([np.nan, 100, 100]) - temp_cell = pd.Series([30, np.nan, 30]) - expected = pd.Series(np.array([ nan, nan, 8.9125])) + irrad_trans = pd.Series([np.nan, 100, 100, 1200]) + temp_cell = pd.Series([30, np.nan, 30, 30]) + expected = pd.Series(np.array([ nan, nan, 8.9125, 118.45])) out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, 25, 0.01) assert_series_equal(expected, out) @@ -2209,10 +2209,10 @@ def test_pvwatts_dc_series_with_k(): def test_pvwatts_dc_with_k_and_cap_adjustment(): irrad_trans = [100, 1200] temp_cell = 25 - ks = [0.01, 0.02] + ks = [0.01, 0.15] cap_adjustments = [False, True] out = [] - expected = [9.0625, 120.25, 8.125, 120.5, 9.0625, 120.0, 8.125, 120.0] + expected = [9.0625, 120.25, 0, 123.75, 9.0625, 120.0, 0, 120.0] for cap_adjustment in cap_adjustments: for k in ks: for irrad in irrad_trans: From 85016fab291af65b6e1ba9db35f10615c35eba96 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:58:09 -0500 Subject: [PATCH 24/41] more test coverage --- tests/test_pvsystem.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index d07a9be1e4..61873a9111 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -2188,12 +2188,12 @@ def test_pvwatts_dc_scalars_with_k(): def test_pvwatts_dc_arrays_with_k(): - irrad_trans = np.array([np.nan, 100, 100]) + irrad_trans = np.array([np.nan, 100, 1200]) temp_cell = np.array([30, np.nan, 30]) irrad_trans, temp_cell = np.meshgrid(irrad_trans, temp_cell) - expected = np.array([[nan, 8.9125, 8.9125], + expected = np.array([[nan, 8.9125, 118.45], [nan, nan, nan], - [nan, 8.9125, 8.9125]]) + [nan, 8.9125, 118.45]]) out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, 25, 0.01) assert_allclose(out, expected, equal_nan=True) @@ -2221,6 +2221,18 @@ def test_pvwatts_dc_with_k_and_cap_adjustment(): assert_allclose(out, expected) +def test_pvwatts_dc_arrays_with_k_and_cap_adjustment(): + irrad_trans = np.array([np.nan, 100, 1200]) + temp_cell = np.array([30, np.nan, 30]) + irrad_trans, temp_cell = np.meshgrid(irrad_trans, temp_cell) + expected = np.array([[nan, 8.9125, 118.2], + [nan, nan, nan], + [nan, 8.9125, 118.2]]) + out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, 25, 0.01, + True) + assert_allclose(out, expected, equal_nan=True) + + def test_pvwatts_losses_default(): expected = 14.075660688264469 out = pvsystem.pvwatts_losses() From bed78f998c097c51580cc4ebe4c00f5faa757054 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:58:24 -0500 Subject: [PATCH 25/41] fix shallow copy issue --- pvlib/pvsystem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index c024680f6d..6c47e9a799 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2881,7 +2881,7 @@ def scale_voltage_current_power(data, voltage=1, current=1): def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., k=None, cap_adjustment=False): r""" - Implement NREL's PVWatts (Version 5) DC power model. + Implement NREL's PVWatts (Version 5) DC power model. Parameters ---------- @@ -2963,7 +2963,8 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., # apply Marion's correction if k is anything but zero if k is not None: - pdc_marion = pdc + pdc_marion = (effective_irradiance * 0.001 * pdc0 * + (1 + gamma_pdc * (temp_cell - temp_ref))) err_1 = k * (1 - (1 - effective_irradiance / 200)**4) err_2 = k * (1000 - effective_irradiance) / (1000 - 200) From c8b5cdee0deb87ddf0fc6e8b76727d86db228633 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:01:20 -0500 Subject: [PATCH 26/41] Update pvlib/pvsystem.py Co-authored-by: RDaxini <143435106+RDaxini@users.noreply.github.com> --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 6c47e9a799..b84208eb96 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2926,7 +2926,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., on [2]_. This applies a piece-wise adjustment to power based on irradiance, where `k` is the reduction in actual power at 200 Wm⁻² relative to power calculated at 200 Wm-2 as 0.2*`pdc0`. For example, a 500 W module that - produces 95 W at 200 Wm-2 (a 5% relative reduction in efficiency) would + produces 95 W at 200 Wm⁻² (a 5% relative reduction in efficiency) would have a value of `k` = 0.01. .. math:: From 1f85ed25312464a1cab9a7932955d3ee330572f7 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:01:44 -0500 Subject: [PATCH 27/41] Update pvlib/pvsystem.py Co-authored-by: RDaxini <143435106+RDaxini@users.noreply.github.com> --- pvlib/pvsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index b84208eb96..cb2c756e26 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2898,8 +2898,8 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., The temperature coefficient of power. Typically -0.002 to -0.005 per degree C. [1/C] temp_ref: numeric, default 25.0 - Cell reference temperature. PVWatts defines it to be 25 C and - is included here for flexibility. [C] + Cell reference temperature. PVWatts defines it to be 25 °C and + is included here for flexibility. [°C] k: numeric, optional Irradiance correction factor, defined in [2]_. Typically positive. [unitless] From e81b450d335b3c64df35212b62505ffb9c39d264 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:01:56 -0500 Subject: [PATCH 28/41] Update pvlib/pvsystem.py Co-authored-by: RDaxini <143435106+RDaxini@users.noreply.github.com> --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index cb2c756e26..0742bc4881 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2904,7 +2904,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., Irradiance correction factor, defined in [2]_. Typically positive. [unitless] cap_adjustment: Boolean, default False - If True, apply the optional adjustment at and below 1000 W/m^2. + If True, apply the optional adjustment at and below 1000 Wm⁻² Returns ------- From 3ea68efaa1bf49245ea65b17df005208fdd6e920 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:36:51 -0500 Subject: [PATCH 29/41] update whatsnew --- docs/sphinx/source/whatsnew/v0.13.2.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.13.2.rst b/docs/sphinx/source/whatsnew/v0.13.2.rst index 3177f7a022..1a680f24c5 100644 --- a/docs/sphinx/source/whatsnew/v0.13.2.rst +++ b/docs/sphinx/source/whatsnew/v0.13.2.rst @@ -27,8 +27,8 @@ Enhancements :py:func:`~pvlib.singlediode.bishop88_mpp`, :py:func:`~pvlib.singlediode.bishop88_v_from_i`, and :py:func:`~pvlib.singlediode.bishop88_i_from_v`. (:issue:`2497`, :pull:`2498`) - - +* Add Marion 2008 non-linear irradiance adjustment factor to + :py:func:`pvlib.pvsystem.pvwatts_dc`. (:issue:`2566`, :pull:`2569`) Documentation ~~~~~~~~~~~~~ @@ -52,4 +52,4 @@ Maintenance Contributors ~~~~~~~~~~~~ - +* Will Hobbs (:ghuser:`williamhobbs`) From 158ff4195662f24106af2b7f1a9d04a5965b497b Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:00:36 -0500 Subject: [PATCH 30/41] reorganize to clean it up --- pvlib/pvsystem.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 0742bc4881..f8bad096d2 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2963,38 +2963,46 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., # apply Marion's correction if k is anything but zero if k is not None: - pdc_marion = (effective_irradiance * 0.001 * pdc0 * - (1 + gamma_pdc * (temp_cell - temp_ref))) + # calculate error adjustments err_1 = k * (1 - (1 - effective_irradiance / 200)**4) err_2 = k * (1000 - effective_irradiance) / (1000 - 200) + # if input is Series or array if hasattr(effective_irradiance, '__len__'): + # precalculate pdc before applying error adjustments + pdc_marion = (effective_irradiance * 0.001 * pdc0 * + (1 + gamma_pdc * (temp_cell - temp_ref))) + + # apply error adjustments pdc_marion[effective_irradiance <= 200] = ( pdc[effective_irradiance <= 200] - (pdc0 * err_1[effective_irradiance <= 200])) pdc_marion[effective_irradiance > 200] = ( pdc[effective_irradiance > 200] - (pdc0 * err_2[effective_irradiance > 200])) + + # "cap" Marion's correction at 1000 W/m^2 + if cap_adjustment: + pdc_marion[effective_irradiance >= 1000] = ( + pdc[effective_irradiance >= 1000]) + + # set negative power to zero + pdc_marion[pdc_marion < 0] = 0 + + # else (input is scalar) else: + # apply error adjustments if effective_irradiance <= 200: pdc_marion = pdc - (pdc0 * err_1) elif effective_irradiance > 200: pdc_marion = pdc - (pdc0 * err_2) - # "cap" Marion's correction at 1000 W/m^2 - if cap_adjustment: - if hasattr(effective_irradiance, '__len__'): - pdc_marion[effective_irradiance >= 1000] = ( - pdc[effective_irradiance >= 1000]) - else: + # "cap" Marion's correction at 1000 W/m^2 if needed + if cap_adjustment: if effective_irradiance >= 1000: pdc_marion = pdc - # large k values can result in negative power at low irradiance, so - # set negative power to zero - if hasattr(effective_irradiance, '__len__'): - pdc_marion[pdc_marion < 0] = 0 - else: + # set negative power to zero if pdc_marion < 0: pdc_marion = 0 From 1e3d41362a9820ad32e92bf9132accb8597fd4a4 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:30:49 -0500 Subject: [PATCH 31/41] single comp path on numpy array --- pvlib/pvsystem.py | 57 ++++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index f8bad096d2..c8b0a7735a 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2963,50 +2963,27 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., # apply Marion's correction if k is anything but zero if k is not None: + + # preserve input types + index = pdc.index if isinstance(pdc, pd.Series) else None + is_scalar = np.isscalar(pdc) + # calculate error adjustments err_1 = k * (1 - (1 - effective_irradiance / 200)**4) err_2 = k * (1000 - effective_irradiance) / (1000 - 200) + err = np.where(effective_irradiance <= 200, err_1, err_2) + if cap_adjustment: + err = np.where(effective_irradiance >= 1000, 0, err) - # if input is Series or array - if hasattr(effective_irradiance, '__len__'): - # precalculate pdc before applying error adjustments - pdc_marion = (effective_irradiance * 0.001 * pdc0 * - (1 + gamma_pdc * (temp_cell - temp_ref))) - - # apply error adjustments - pdc_marion[effective_irradiance <= 200] = ( - pdc[effective_irradiance <= 200] - - (pdc0 * err_1[effective_irradiance <= 200])) - pdc_marion[effective_irradiance > 200] = ( - pdc[effective_irradiance > 200] - - (pdc0 * err_2[effective_irradiance > 200])) - - # "cap" Marion's correction at 1000 W/m^2 - if cap_adjustment: - pdc_marion[effective_irradiance >= 1000] = ( - pdc[effective_irradiance >= 1000]) - - # set negative power to zero - pdc_marion[pdc_marion < 0] = 0 - - # else (input is scalar) - else: - # apply error adjustments - if effective_irradiance <= 200: - pdc_marion = pdc - (pdc0 * err_1) - elif effective_irradiance > 200: - pdc_marion = pdc - (pdc0 * err_2) - - # "cap" Marion's correction at 1000 W/m^2 if needed - if cap_adjustment: - if effective_irradiance >= 1000: - pdc_marion = pdc - - # set negative power to zero - if pdc_marion < 0: - pdc_marion = 0 - - pdc = pdc_marion + pdc = pdc - pdc0 * err + + # set negative power to zero + pdc = np.where(pdc < 0, 0, pdc) + + if index is not None: + pdc = pd.Series(pdc, index=index) + elif is_scalar: + pdc = float(pdc) return pdc From bdf331d2cd311fae665be0e45445b24d30260697 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:44:14 -0500 Subject: [PATCH 32/41] added some comments --- pvlib/pvsystem.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index c8b0a7735a..7ee982c732 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2961,7 +2961,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., pdc = (effective_irradiance * 0.001 * pdc0 * (1 + gamma_pdc * (temp_cell - temp_ref))) - # apply Marion's correction if k is anything but zero + # apply Marion's correction if k is provided if k is not None: # preserve input types @@ -2972,14 +2972,18 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., err_1 = k * (1 - (1 - effective_irradiance / 200)**4) err_2 = k * (1000 - effective_irradiance) / (1000 - 200) err = np.where(effective_irradiance <= 200, err_1, err_2) + + # cap adjustment, if needed if cap_adjustment: err = np.where(effective_irradiance >= 1000, 0, err) + # make error adjustment pdc = pdc - pdc0 * err # set negative power to zero pdc = np.where(pdc < 0, 0, pdc) + # preserve input types if index is not None: pdc = pd.Series(pdc, index=index) elif is_scalar: From 4c3e01d17dff62b671e9d3f856059a8a991b63c4 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:32:30 -0500 Subject: [PATCH 33/41] unit formatting --- pvlib/pvsystem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 7ee982c732..836fe99f79 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2889,14 +2889,14 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., Irradiance transmitted to the PV cells. To be fully consistent with PVWatts, the user must have already applied angle of incidence losses, but not soiling, spectral, - etc. [W/m^2] + etc. [Wm⁻²] temp_cell: numeric Cell temperature [C]. pdc0: numeric - Power of the modules at 1000 W/m^2 and cell reference temperature. [W] + Power of the modules at 1000 Wm⁻² and cell reference temperature. [W] gamma_pdc: numeric The temperature coefficient of power. Typically -0.002 to - -0.005 per degree C. [1/C] + -0.005 per degree C. [1/°C] temp_ref: numeric, default 25.0 Cell reference temperature. PVWatts defines it to be 25 °C and is included here for flexibility. [°C] From 996092a8aa1bec20fc0428b6be1ebeae1273bb33 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:46:42 -0500 Subject: [PATCH 34/41] call keyword arguments as keyword arguments --- tests/test_pvsystem.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 61873a9111..9a8af55942 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -2194,7 +2194,7 @@ def test_pvwatts_dc_arrays_with_k(): expected = np.array([[nan, 8.9125, 118.45], [nan, nan, nan], [nan, 8.9125, 118.45]]) - out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, 25, 0.01) + out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, k=0.01) assert_allclose(out, expected, equal_nan=True) @@ -2202,7 +2202,7 @@ def test_pvwatts_dc_series_with_k(): irrad_trans = pd.Series([np.nan, 100, 100, 1200]) temp_cell = pd.Series([30, np.nan, 30, 30]) expected = pd.Series(np.array([ nan, nan, 8.9125, 118.45])) - out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, 25, 0.01) + out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, 25, k=0.01) assert_series_equal(expected, out) @@ -2217,7 +2217,8 @@ def test_pvwatts_dc_with_k_and_cap_adjustment(): for k in ks: for irrad in irrad_trans: out.append(pvsystem.pvwatts_dc(irrad, temp_cell, 100, -0.003, - 25, k, cap_adjustment)) + k=k, + cap_adjustment=cap_adjustment)) assert_allclose(out, expected) @@ -2228,7 +2229,7 @@ def test_pvwatts_dc_arrays_with_k_and_cap_adjustment(): expected = np.array([[nan, 8.9125, 118.2], [nan, nan, nan], [nan, 8.9125, 118.2]]) - out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, 25, 0.01, + out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, k=0.01, True) assert_allclose(out, expected, equal_nan=True) From 5777613d3864bce6c233ea66b8c87cc854fbd3fd Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:47:52 -0500 Subject: [PATCH 35/41] quick fix --- tests/test_pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 9a8af55942..a6968b2e25 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -2230,7 +2230,7 @@ def test_pvwatts_dc_arrays_with_k_and_cap_adjustment(): [nan, nan, nan], [nan, 8.9125, 118.2]]) out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, k=0.01, - True) + cap_adjustment=True) assert_allclose(out, expected, equal_nan=True) From 3c7f1e97fd60a6ec382517b142bf2707d273f173 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:55:36 -0500 Subject: [PATCH 36/41] a few more missed keyword args --- tests/test_pvsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index a6968b2e25..138715038e 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -2183,7 +2183,7 @@ def test_pvwatts_dc_series(): def test_pvwatts_dc_scalars_with_k(): expected = 8.9125 - out = pvsystem.pvwatts_dc(100, 30, 100, -0.003, 25, 0.01) + out = pvsystem.pvwatts_dc(100, 30, 100, -0.003, k=0.01) assert_allclose(out, expected) @@ -2202,7 +2202,7 @@ def test_pvwatts_dc_series_with_k(): irrad_trans = pd.Series([np.nan, 100, 100, 1200]) temp_cell = pd.Series([30, np.nan, 30, 30]) expected = pd.Series(np.array([ nan, nan, 8.9125, 118.45])) - out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, 25, k=0.01) + out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, k=0.01) assert_series_equal(expected, out) From 86eb06ea92eb68490f0afd317338b249e401eb6b Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:58:48 -0500 Subject: [PATCH 37/41] simplify with_k_and_cap_adjustmen --- tests/test_pvsystem.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 138715038e..4fbd782e65 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -2209,16 +2209,11 @@ def test_pvwatts_dc_series_with_k(): def test_pvwatts_dc_with_k_and_cap_adjustment(): irrad_trans = [100, 1200] temp_cell = 25 - ks = [0.01, 0.15] - cap_adjustments = [False, True] out = [] - expected = [9.0625, 120.25, 0, 123.75, 9.0625, 120.0, 0, 120.0] - for cap_adjustment in cap_adjustments: - for k in ks: - for irrad in irrad_trans: - out.append(pvsystem.pvwatts_dc(irrad, temp_cell, 100, -0.003, - k=k, - cap_adjustment=cap_adjustment)) + expected = [0, 120.0] + for irrad in irrad_trans: + out.append(pvsystem.pvwatts_dc(irrad, temp_cell, 100, -0.003, k=0.15, + cap_adjustment=True)) assert_allclose(out, expected) From 465044318627e98fb4f39cebfde01858cad12f49 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:01:04 -0500 Subject: [PATCH 38/41] Update pvlib/pvsystem.py Co-authored-by: Cliff Hansen --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 836fe99f79..ee995c62aa 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2904,7 +2904,7 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., Irradiance correction factor, defined in [2]_. Typically positive. [unitless] cap_adjustment: Boolean, default False - If True, apply the optional adjustment at and below 1000 Wm⁻² + If True, only apply the optional adjustment at and below 1000 Wm⁻² Returns ------- From c646f21ba4f3073db3de775e0e6aa0f855e96d50 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:02:26 -0500 Subject: [PATCH 39/41] Update pvlib/pvsystem.py Co-authored-by: Cliff Hansen --- pvlib/pvsystem.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index ee995c62aa..7015d6dc18 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2922,11 +2922,13 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., This model has also been referred to as the power temperature coefficient model. - This function accepts an optional irradiance adjustment factor, `k`, based - on [2]_. This applies a piece-wise adjustment to power based on irradiance, + An optional adjustment can be applied to :math:`P_{dc}` as described in [2]_. + The adjustment accounts for the variation in module efficiency with + irradiance. The piece-wise adjustment to power is parameterized by `k`, where `k` is the reduction in actual power at 200 Wm⁻² relative to power - calculated at 200 Wm-2 as 0.2*`pdc0`. For example, a 500 W module that - produces 95 W at 200 Wm⁻² (a 5% relative reduction in efficiency) would + calculated at 200 Wm⁻² as 0.2*`pdc0`. For example, a module that + is rated at 500 W at STC but produces 95 W at 200 Wm⁻² + (a 5% relative reduction in efficiency) would have a value of `k` = 0.01. .. math:: From f3e165e6139003d89144c915b19748d4a0c86fcf Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:02:40 -0500 Subject: [PATCH 40/41] Update pvlib/pvsystem.py Co-authored-by: Cliff Hansen --- pvlib/pvsystem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 7015d6dc18..8a5085ffad 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2936,7 +2936,8 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., k=\frac{0.2P_{dc0}-P_{200}}{P_{dc0}} For positive `k` values, and `k` is typically positive, this adjustment - increases relative efficiency when irradiance is above 1000 Wm⁻². This may + would also increase relative efficiency when irradiance is above 1000 Wm⁻². + This may not be desired, as modules with nonlinear irradiance response often have peak efficiency near 1000 Wm⁻², and it is either flat or declining at higher irradiance. An optional parameter, `cap_adjustment`, can address From 6624239a94318f7013153f7b545b55f669d74cc8 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:51:10 -0500 Subject: [PATCH 41/41] doc formatting/linting --- pvlib/pvsystem.py | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 8a5085ffad..1d993eed31 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2886,20 +2886,19 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., Parameters ---------- effective_irradiance: numeric - Irradiance transmitted to the PV cells. To be - fully consistent with PVWatts, the user must have already - applied angle of incidence losses, but not soiling, spectral, - etc. [Wm⁻²] + Irradiance transmitted to the PV cells. To be fully consistent with + PVWatts, the user must have already applied angle of incidence losses, + but not soiling, spectral, etc. [Wm⁻²] temp_cell: numeric Cell temperature [C]. pdc0: numeric Power of the modules at 1000 Wm⁻² and cell reference temperature. [W] gamma_pdc: numeric - The temperature coefficient of power. Typically -0.002 to - -0.005 per degree C. [1/°C] + The temperature coefficient of power. Typically -0.002 to -0.005 per + degree C. [1/°C] temp_ref: numeric, default 25.0 - Cell reference temperature. PVWatts defines it to be 25 °C and - is included here for flexibility. [°C] + Cell reference temperature. PVWatts defines it to be 25 °C and is + included here for flexibility. [°C] k: numeric, optional Irradiance correction factor, defined in [2]_. Typically positive. [unitless] @@ -2922,14 +2921,13 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., This model has also been referred to as the power temperature coefficient model. - An optional adjustment can be applied to :math:`P_{dc}` as described in [2]_. - The adjustment accounts for the variation in module efficiency with + An optional adjustment can be applied to :math:`P_{dc}` as described in + [2]_. The adjustment accounts for the variation in module efficiency with irradiance. The piece-wise adjustment to power is parameterized by `k`, where `k` is the reduction in actual power at 200 Wm⁻² relative to power - calculated at 200 Wm⁻² as 0.2*`pdc0`. For example, a module that - is rated at 500 W at STC but produces 95 W at 200 Wm⁻² - (a 5% relative reduction in efficiency) would - have a value of `k` = 0.01. + calculated at 200 Wm⁻² as 0.2*`pdc0`. For example, a module that is rated + at 500 W at STC but produces 95 W at 200 Wm⁻² (a 5% relative reduction in + efficiency) would have a value of `k` = 0.01. .. math:: @@ -2937,15 +2935,15 @@ def pvwatts_dc(effective_irradiance, temp_cell, pdc0, gamma_pdc, temp_ref=25., For positive `k` values, and `k` is typically positive, this adjustment would also increase relative efficiency when irradiance is above 1000 Wm⁻². - This may - not be desired, as modules with nonlinear irradiance response often have - peak efficiency near 1000 Wm⁻², and it is either flat or declining at - higher irradiance. An optional parameter, `cap_adjustment`, can address - this by modifying the adjustment from [2]_ to only apply below 1000 Wm⁻². + This may not be desired, as modules with nonlinear irradiance response + often have peak efficiency near 1000 Wm⁻², and it is either flat or + declining at higher irradiance. An optional parameter, `cap_adjustment`, + can address this by modifying the adjustment from [2]_ to only apply below + 1000 Wm⁻². Note that ``pdc0`` is also used as a symbol in - :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC - power of the modules at reference conditions. ``pdc0`` in + :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the + DC power of the modules at reference conditions. ``pdc0`` in :py:func:`pvlib.inverter.pvwatts` refers to the DC power input limit of the inverter.