Skip to content

Create function to calculate average photon energy #2140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4cf9961
create
RDaxini Jul 20, 2024
a9d8dd9
Update spectrum.rst
RDaxini Jul 20, 2024
513d708
Update test_spectrum.py
RDaxini Jul 20, 2024
f376d59
Update mismatch.py
RDaxini Aug 1, 2024
5eb7c13
Update mismatch.py
RDaxini Aug 1, 2024
3420a92
Update mismatch.py
RDaxini Aug 1, 2024
c2049ee
Update mismatch.py
RDaxini Aug 2, 2024
afb06f4
docs, tests
RDaxini Aug 2, 2024
928c38c
docs, data checks, tests
RDaxini Aug 2, 2024
d63298a
Update mismatch.py
RDaxini Aug 2, 2024
3e46831
Update mismatch.py
RDaxini Aug 4, 2024
2f4b17e
Apply suggestions from code review
RDaxini Aug 5, 2024
5a66414
remove comments, inverse fraction
RDaxini Aug 5, 2024
ec5bc6c
remove comment
RDaxini Aug 5, 2024
2534ddd
Merge remote-tracking branch 'upstream/main' into calc_ape
RDaxini Aug 5, 2024
6cb9689
Update irradiance.py
RDaxini Aug 5, 2024
c9b4107
Merge remote-tracking branch 'upstream/main' into calc_ape
RDaxini Aug 8, 2024
1720d7d
Update test_irradiance.py
RDaxini Aug 8, 2024
a869f83
Update test_irradiance.py
RDaxini Aug 8, 2024
af63875
Update irradiance.py
RDaxini Aug 8, 2024
43ae7d3
mixed up my lambdas and gammas:(
RDaxini Aug 8, 2024
c2c373d
change variable name spectral_irr -> spectrum
RDaxini Aug 8, 2024
698d9d7
update variable name in docstring + error messages
RDaxini Aug 9, 2024
e9919f5
Update irradiance.py
RDaxini Aug 9, 2024
458bf16
Update test_irradiance.py
RDaxini Aug 12, 2024
ecba3eb
Apply suggestions from code review
RDaxini Aug 12, 2024
0b95307
Update irradiance.py
RDaxini Aug 12, 2024
39fe12c
Update v0.11.1.rst
RDaxini Aug 13, 2024
112747e
Update irradiance.py
RDaxini Aug 14, 2024
ddc70c5
Update irradiance.py
RDaxini Aug 14, 2024
a8e72d8
Update test_irradiance.py
RDaxini Aug 14, 2024
80bf690
Update test_mismatch.py
RDaxini Aug 14, 2024
1bafc90
Update v0.11.1.rst
RDaxini Aug 14, 2024
6a21228
Update irradiance.py
RDaxini Aug 14, 2024
c224e05
update output datatype and associated tests
RDaxini Aug 14, 2024
ace5a65
Update test_irradiance.py
RDaxini Aug 14, 2024
072eeeb
Update docs/sphinx/source/whatsnew/v0.11.1.rst
RDaxini Aug 14, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ Spectrum
spectrum.spectral_factor_jrc
spectrum.sr_to_qe
spectrum.qe_to_sr
spectrum.average_photon_energy
3 changes: 3 additions & 0 deletions docs/sphinx/source/whatsnew/v0.11.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Deprecations

Enhancements
~~~~~~~~~~~~
* Add new function to calculate the average photon energy,
:py:func:`pvlib.spectrum.average_photon_energy`.
(:issue:`2135`, :pull:`2140`)
* Add new losses function that accounts for non-uniform irradiance on bifacial
modules, :py:func:`pvlib.bifacial.power_mismatch_deline`.
(:issue:`2045`, :pull:`2046`)
Expand Down
1 change: 1 addition & 0 deletions pvlib/spectrum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pvlib.spectrum.irradiance import ( # noqa: F401
get_am15g,
get_reference_spectra,
average_photon_energy,
)
from pvlib.spectrum.response import ( # noqa: F401
get_example_spectral_response,
Expand Down
94 changes: 94 additions & 0 deletions pvlib/spectrum/irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import pandas as pd
from pathlib import Path
from functools import partial
from scipy import constants
from scipy.integrate import trapezoid


@deprecated(
Expand Down Expand Up @@ -176,3 +178,95 @@ def get_reference_spectra(wavelengths=None, standard="ASTM G173-03"):
)

return standard


def average_photon_energy(spectra):
r"""
Calculate the average photon energy of one or more spectral irradiance
distributions.

Parameters
----------
spectra : pandas.Series or pandas.DataFrame

Spectral irradiance, must be positive. [Wm⁻²nm⁻¹]

A single spectrum must be a :py:class:`pandas.Series` with wavelength
[nm] as the index, while multiple spectra must be rows in a
:py:class:`pandas.DataFrame` with column headers as wavelength [nm].

Returns
-------
ape : numeric or pandas.Series
Average Photon Energy [eV].
Note: returns ``np.nan`` in the case of all-zero spectral irradiance
input.

Notes
-----
The average photon energy (APE) is an index used to characterise the solar
spectrum. It has been used widely in the physics literature since the
1900s, but its application for solar spectral irradiance characterisation
in the context of PV performance modelling was proposed in 2002 [1]_. The
APE is calculated based on the principle that a photon's energy is
inversely proportional to its wavelength:

.. math::

E_\gamma = \frac{hc}{\lambda},

where :math:`E_\gamma` is the energy of a photon with wavelength
:math:`\lambda`, :math:`h` is the Planck constant, and :math:`c` is the
speed of light. Therefore, the average energy of all photons within a
single spectral irradiance distribution provides an indication of the
general shape of the spectrum. A higher average photon energy
(shorter wavelength) indicates a blue-shifted spectrum, while a lower
average photon energy (longer wavelength) would indicate a red-shifted
spectrum. This value of the average photon energy can be calculated by
dividing the total energy in the spectrum by the total number of photons
in the spectrum as follows [1]_:

.. math::

\overline{E_\gamma} = \frac{1}{q} \cdot \frac{\int G(\lambda) \,
d\lambda}
{\int \Phi(\lambda) \, d\lambda}.

:math:`\Phi(\lambda)` is the photon flux density as a function of
wavelength, :math:`G(\lambda)` is the spectral irradiance, :math:`q` is the
elementary charge used here so that the average photon energy,
:math:`\overline{E_\gamma}`, is expressed in electronvolts (eV). The
integrals are computed over the full wavelength range of the ``spectra``
parameter.

References
----------
.. [1] Jardine, C., et al., 2002, January. Influence of spectral effects on
the performance of multijunction amorphous silicon cells. In Proc.
Photovoltaic in Europe Conference (pp. 1756-1759).
"""

if not isinstance(spectra, (pd.Series, pd.DataFrame)):
raise TypeError('`spectra` must be either a'
' pandas Series or DataFrame')

if (spectra < 0).any().any():
raise ValueError('Spectral irradiance data must be positive')

hclambda = pd.Series((constants.h*constants.c)/(spectra.T.index*1e-9))
hclambda.index = spectra.T.index
pfd = spectra.div(hclambda)

def integrate(e):
return trapezoid(e, x=e.T.index, axis=-1)

int_spectra = integrate(spectra)
int_pfd = integrate(pfd)

with np.errstate(invalid='ignore'):
ape = (1/constants.elementary_charge)*int_spectra/int_pfd

if isinstance(spectra, pd.DataFrame):
ape = pd.Series(ape, index=spectra.index)

return ape
1 change: 1 addition & 0 deletions pvlib/spectrum/mismatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import numpy as np
import pandas as pd
from scipy.integrate import trapezoid

from warnings import warn


Expand Down
64 changes: 64 additions & 0 deletions pvlib/tests/spectrum/test_irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,67 @@ def test_get_reference_spectra_invalid_reference():
# test that an invalid reference identifier raises a ValueError
with pytest.raises(ValueError, match="Invalid standard identifier"):
spectrum.get_reference_spectra(standard="invalid")


def test_average_photon_energy_series():
# test that the APE is calculated correctly with single spectrum
# series input

spectra = spectrum.get_reference_spectra()
spectra = spectra['global']
ape = spectrum.average_photon_energy(spectra)
expected = 1.45017
assert_allclose(ape, expected, rtol=1e-4)


def test_average_photon_energy_dataframe():
# test that the APE is calculated correctly with multiple spectra
# dataframe input and that the output is a series

spectra = spectrum.get_reference_spectra().T
ape = spectrum.average_photon_energy(spectra)
expected = pd.Series([1.36848, 1.45017, 1.40885])
expected.index = spectra.index
assert_series_equal(ape, expected, rtol=1e-4)


def test_average_photon_energy_invalid_type():
# test that spectrum argument is either a pandas Series or dataframe
spectra = 5
with pytest.raises(TypeError, match='must be either a pandas Series or'
' DataFrame'):
spectrum.average_photon_energy(spectra)


def test_average_photon_energy_neg_irr_series():
# test for handling of negative spectral irradiance values with a
# pandas Series input

spectra = spectrum.get_reference_spectra()['global']*-1
with pytest.raises(ValueError, match='must be positive'):
spectrum.average_photon_energy(spectra)


def test_average_photon_energy_neg_irr_dataframe():
# test for handling of negative spectral irradiance values with a
# pandas DataFrame input

spectra = spectrum.get_reference_spectra().T*-1

with pytest.raises(ValueError, match='must be positive'):
spectrum.average_photon_energy(spectra)


def test_average_photon_energy_zero_irr():
# test for handling of zero spectral irradiance values with
# pandas DataFrame and pandas Series input

spectra_df_zero = spectrum.get_reference_spectra().T
spectra_df_zero.iloc[1] = 0
spectra_series_zero = spectrum.get_reference_spectra()['global']*0
out_1 = spectrum.average_photon_energy(spectra_df_zero)
out_2 = spectrum.average_photon_energy(spectra_series_zero)
expected_1 = np.array([1.36848, np.nan, 1.40885])
expected_2 = np.nan
assert_allclose(out_1, expected_1, atol=1e-3)
assert_allclose(out_2, expected_2, atol=1e-3)
Loading