Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
05f5408
add marion's adjustment
williamhobbs Oct 7, 2025
6d7ae12
linting
williamhobbs Oct 7, 2025
40328c8
Update pvlib/pvsystem.py
williamhobbs Oct 7, 2025
a9dfb28
Update pvlib/pvsystem.py
williamhobbs Oct 7, 2025
551e722
Update pvlib/pvsystem.py
williamhobbs Oct 7, 2025
73e80d3
Update pvlib/pvsystem.py
williamhobbs Oct 7, 2025
f25b426
Update pvlib/pvsystem.py
williamhobbs Oct 7, 2025
380bb58
more suggested changes
williamhobbs Oct 7, 2025
5d31dfd
Update pvlib/pvsystem.py
williamhobbs Oct 8, 2025
ad146b5
Update pvlib/pvsystem.py
williamhobbs Oct 8, 2025
69ce5e7
Update pvlib/pvsystem.py
williamhobbs Oct 8, 2025
dc58507
cleaning up docs
williamhobbs Oct 8, 2025
0d8b0b5
Update pvlib/pvsystem.py
williamhobbs Oct 8, 2025
90b7c54
prevent negative power
williamhobbs Oct 9, 2025
1297448
Merge branch 'add_marion' of https://github.com/williamhobbs/pvlib-py…
williamhobbs Oct 9, 2025
4488e3c
fix typo in error eqn
williamhobbs Oct 9, 2025
250af5a
fix the right typo this time...
williamhobbs Oct 9, 2025
bd16ac2
reorder err eqns
williamhobbs Oct 9, 2025
f3041c2
add more detail on `cap_adjustment`
williamhobbs Oct 9, 2025
9f756bf
return same object type that was input
williamhobbs Oct 16, 2025
17f7213
fix issue with scalars
williamhobbs Oct 16, 2025
a018525
another fix
williamhobbs Oct 16, 2025
816c82a
add tests
williamhobbs Oct 16, 2025
a19b721
increase test coverage
williamhobbs Oct 17, 2025
85016fa
more test coverage
williamhobbs Oct 17, 2025
bed78f9
fix shallow copy issue
williamhobbs Oct 17, 2025
c8b5cde
Update pvlib/pvsystem.py
williamhobbs Oct 17, 2025
1f85ed2
Update pvlib/pvsystem.py
williamhobbs Oct 17, 2025
e81b450
Update pvlib/pvsystem.py
williamhobbs Oct 17, 2025
3ea68ef
update whatsnew
williamhobbs Oct 17, 2025
86ccbb1
Merge branch 'add_marion' of https://github.com/williamhobbs/pvlib-py…
williamhobbs Oct 17, 2025
158ff41
reorganize to clean it up
williamhobbs Oct 17, 2025
1e3d413
single comp path on numpy array
williamhobbs Oct 17, 2025
bdf331d
added some comments
williamhobbs Oct 17, 2025
4c3e01d
unit formatting
williamhobbs Oct 17, 2025
996092a
call keyword arguments as keyword arguments
williamhobbs Oct 20, 2025
5777613
quick fix
williamhobbs Oct 20, 2025
3c7f1e9
a few more missed keyword args
williamhobbs Oct 20, 2025
86eb06e
simplify with_k_and_cap_adjustmen
williamhobbs Oct 20, 2025
4650443
Update pvlib/pvsystem.py
williamhobbs Oct 21, 2025
c646f21
Update pvlib/pvsystem.py
williamhobbs Oct 21, 2025
f3e165e
Update pvlib/pvsystem.py
williamhobbs Oct 21, 2025
6624239
doc formatting/linting
williamhobbs Oct 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/sphinx/source/whatsnew/v0.13.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~
Expand All @@ -52,4 +52,4 @@ Maintenance

Contributors
~~~~~~~~~~~~

* Will Hobbs (:ghuser:`williamhobbs`)
107 changes: 86 additions & 21 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2878,53 +2878,118 @@ 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=None, cap_adjustment=False):
r"""
Implements NREL's PVWatts DC power model. The PVWatts DC model [1]_ is:

.. math::

P_{dc} = \frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \gamma_{pdc} (T_{cell} - T_{ref}))

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
----------
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. [W/m^2]
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 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]
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]
cap_adjustment: Boolean, default False
If True, only apply the optional adjustment at and below 1000 Wm⁻²

Returns
-------
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.

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.

.. math::

k=\frac{0.2P_{dc0}-P_{200}}{P_{dc0}}

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⁻².

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"
http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf
(2014).
.. [2] B. Marion, "Comparison of Predictive Models for
Photovoltaic Module Performance,"
:doi:`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 provided
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)

# 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:
pdc = float(pdc)

return pdc


Expand Down
48 changes: 48 additions & 0 deletions tests/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2181,6 +2181,54 @@ 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, k=0.01)
assert_allclose(out, expected)


def test_pvwatts_dc_arrays_with_k():
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.45],
[nan, nan, nan],
[nan, 8.9125, 118.45]])
out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003, k=0.01)
assert_allclose(out, expected, equal_nan=True)


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, k=0.01)
assert_series_equal(expected, out)


def test_pvwatts_dc_with_k_and_cap_adjustment():
irrad_trans = [100, 1200]
temp_cell = 25
out = []
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)


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #2569 (comment); I think this test should help. The rest look good already, nice job!

Suggested change
def test_pvwatts_dc_cap_adjustment_scalar_above_1000():
irrad = 1200
temp_cell = 25
k = 0.01
cap_adjustment = True
out = pvsystem.pvwatts_dc(irrad, temp_cell, 100, -0.003, 25, k, cap_adjustment)
expected = 120.0
assert_allclose(out, expected)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my changes to test_pvwatts_dc_with_k_and_cap_adjustment(). It now includes irradiance > 1000 and cap_adjustment=True. I think I made those changes before seeing your comments here.

Does that cover it?

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, k=0.01,
cap_adjustment=True)
assert_allclose(out, expected, equal_nan=True)


def test_pvwatts_losses_default():
expected = 14.075660688264469
out = pvsystem.pvwatts_losses()
Expand Down