From fdc65f0bb2ccd88bf1199e39312f9b5344ae7a7f Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 24 Mar 2024 13:24:51 +0100 Subject: [PATCH 01/17] Add quantum_efficiency_to_spectral_responsivity and spectral_responsivity_to_quantum_efficiency --- .../effects_on_pv_system_output/spectrum.rst | 2 + pvlib/spectrum/__init__.py | 2 + pvlib/spectrum/mismatch.py | 137 +++++++++++++++++- pvlib/tests/test_spectrum.py | 90 +++++++++++- 4 files changed, 229 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst b/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst index 8041d8f49b..9d2e4c51a5 100644 --- a/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst +++ b/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst @@ -13,3 +13,5 @@ Spectrum spectrum.spectral_factor_caballero spectrum.spectral_factor_firstsolar spectrum.spectral_factor_sapm + spectrum.spectral_responsivity_to_quantum_efficiency + spectrum.quantum_efficiency_to_spectral_responsivity diff --git a/pvlib/spectrum/__init__.py b/pvlib/spectrum/__init__.py index 6c97df978e..00473e1708 100644 --- a/pvlib/spectrum/__init__.py +++ b/pvlib/spectrum/__init__.py @@ -6,4 +6,6 @@ spectral_factor_caballero, spectral_factor_firstsolar, spectral_factor_sapm, + spectral_responsivity_to_quantum_efficiency, + quantum_efficiency_to_spectral_responsivity, ) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index e51cdf8625..500573efdf 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -6,11 +6,20 @@ import numpy as np import pandas as pd from scipy.interpolate import interp1d +import scipy.constants import os from warnings import warn +_PLANCK_BY_LIGHT_SPEED_OVER_ELEMENTAL_CHARGE_BY_BILLION = ( + scipy.constants.speed_of_light + * scipy.constants.Planck + / scipy.constants.elementary_charge + * 1e9 +) + + def get_example_spectral_response(wavelength=None): ''' Generate a generic smooth spectral response (SR) for tests and experiments. @@ -153,7 +162,7 @@ def calc_spectral_mismatch_field(sr, e_sun, e_ref=None): e_sun: pandas.DataFrame or pandas.Series One or more measured solar irradiance spectra in a pandas.DataFrame - having wavelength in nm as column index. A single spectrum may be + having wavelength in nm as column index. A single spectrum may be be given as a pandas.Series having wavelength in nm as index. [(W/m^2)/nm] @@ -570,3 +579,129 @@ def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500, ) modifier = f_AM + f_AOD + f_PW # Eq 5 return modifier + + +def spectral_responsivity_to_quantum_efficiency(sr, wavelength=None): + """ + Convert spectral responsivities to quantum efficiencies [1]_. + If ``wavelength`` is not provided, the spectral responsivity ``sr`` must be + a :py:class:`pandas.Series` or :py:class:`pandas.DataFrame`, with the + wavelengths in the index. + + Provide wavelengths in nanometers :math:`[nm]`. + + .. versionadded:: 0.10.5 + + Parameters + ---------- + sr : numeric, pandas.Series or pandas.DataFrame + Spectral response. + Index must be the wavelength in nanometers :math:`nm`. + + wavelength : numeric, optional + Points where spectral response is measured, in nanometers :math:`nm`. + + Returns + ------- + quantum_efficiency : numeric, same type as ``sr`` + Quantum efficiency in the interval :math:`[0, 1]`. + + Notes + ----- + - If ``sr`` is of type ``pandas.Series`` or ``pandas.DataFrame``, + column names will remain unchanged in the returned object. + - If ``wavelength`` is provided it will be used independently of the + datatype of ``sr``. + + References + ---------- + .. [1] “Spectral Response,” PV Performance Modeling Collaborative (PVPMC). + https://pvpmc.sandia.gov/modeling-guide/2-dc-module-iv/ + effective-irradiance/spectral-response/ + .. [2] “Spectral Response | PVEducation,” www.pveducation.org. + https://www.pveducation.org/pvcdrom/solar-cell-operation/ + spectral-response + + See Also + -------- + pvlib.spectrum.quantum_efficiency_to_spectral_responsivity + """ + if wavelength is None: + if hasattr(sr, "index"): # true for pandas objects + # use reference to index values instead of index alone so + # sr / wavelength returns a series with the same name + wavelength = sr.index.array + else: + raise TypeError( + "'sr' must have an '.index' attribute" + + " or 'wavelength' must be provided" + ) + quantum_efficiency = ( + sr + / wavelength + * _PLANCK_BY_LIGHT_SPEED_OVER_ELEMENTAL_CHARGE_BY_BILLION + ) + return quantum_efficiency + + +def quantum_efficiency_to_spectral_responsivity(qe, wavelength=None): + """ + Convert quantum efficiencies to spectral responsivities [1]_. + If ``wavelength`` is not provided, the quantum efficiency ``qe`` must be + a :py:class:`pandas.Series` or :py:class:`pandas.DataFrame`, with the + wavelengths in the index. + + Provide wavelengths in nanometers :math:`[nm]`. + + .. versionadded:: 0.10.5 + + Parameters + ---------- + qe : numeric, pandas.Series or pandas.DataFrame + Quantum efficiency. + If pandas subtype, index must be the wavelength in nanometers. + + wavelength : numeric, optional + Points where quantum efficiency is measured, in nanometers :math:`nm`. + + Returns + ------- + spectral_response : numeric, same type as ``qe`` + Spectral response in :math:`A/W`. + + Notes + ----- + - If ``qe`` is of type ``pandas.Series`` or ``pandas.DataFrame``, + column names will remain unchanged in the returned object. + - If ``wavelength`` is provided it will be used independently of the + datatype of ``qe``. + + References + ---------- + .. [1] “Spectral Response,” PV Performance Modeling Collaborative (PVPMC). + https://pvpmc.sandia.gov/modeling-guide/2-dc-module-iv/ + effective-irradiance/spectral-response/ + .. [2] “Spectral Response | PVEducation,” www.pveducation.org. + https://www.pveducation.org/pvcdrom/solar-cell-operation/ + spectral-response + + See Also + -------- + pvlib.spectrum.spectral_responsivity_to_quantum_efficiency + """ + if wavelength is None: + if hasattr(qe, "index"): # true for pandas objects + # use reference to index values instead of index alone so + # sr / wavelength returns a series with the same name + wavelength = qe.index.array + else: + raise TypeError( + "'qe' must have an '.index' attribute" + + " or 'wavelength' must be provided" + ) + spectral_responsivity = ( + qe + * wavelength + / _PLANCK_BY_LIGHT_SPEED_OVER_ELEMENTAL_CHARGE_BY_BILLION + ) + return spectral_responsivity diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 793eaacfdf..9aa126a5a0 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -140,7 +140,7 @@ def test_get_am15g(): def test_calc_spectral_mismatch_field(spectrl2_data): # test that the mismatch is calculated correctly with - # - default and custom reference sepctrum + # - default and custom reference spectrum # - single or multiple sun spectra # sample data @@ -315,3 +315,91 @@ def test_spectral_factor_caballero_supplied_ambiguous(): with pytest.raises(ValueError): spectrum.spectral_factor_caballero(1, 1, 1, module_type=None, coefficients=None) + + +@pytest.fixture +def sr_and_eqe_fixture(): + df = pd.DataFrame( + columns=("wavelength", "quantum_efficiency", "spectral_response"), + data=[ + # nm, [0,1], A/W + [300, 0.85, 0.205671370402405], + [350, 0.86, 0.242772872514211], + [400, 0.87, 0.280680929019753], + [450, 0.88, 0.319395539919029], + [500, 0.89, 0.358916705212040], + [550, 0.90, 0.399244424898786], + [600, 0.91, 0.440378698979267], + [650, 0.92, 0.482319527453483], + [700, 0.93, 0.525066910321434], + [750, 0.94, 0.568620847583119], + [800, 0.95, 0.612981339238540], + [850, 0.90, 0.617014111207215], + [900, 0.80, 0.580719163489143], + [950, 0.70, 0.536358671833723], + [1000, 0.6, 0.483932636240953], + [1050, 0.4, 0.338752845368667], + ], + ) + df.set_index("wavelength", inplace=True) + return df + + +def test_spectral_responsivity_to_quantum_efficiency(sr_and_eqe_fixture): + #- scalar type + qe = spectrum.spectral_responsivity_to_quantum_efficiency( + sr_and_eqe_fixture["spectral_response"].values[0], + sr_and_eqe_fixture.index.values[0], # wavelength, nm + ) + assert_approx_equal(qe, sr_and_eqe_fixture["quantum_efficiency"].values[0]) + #- vector type + qe = spectrum.spectral_responsivity_to_quantum_efficiency( + sr_and_eqe_fixture["spectral_response"].values, + sr_and_eqe_fixture.index.values, # wavelength, nm + ) + assert_allclose(qe, sr_and_eqe_fixture["quantum_efficiency"]) + #- pandas series type + # note: output Series' name should match the input + qe = spectrum.spectral_responsivity_to_quantum_efficiency( + sr_and_eqe_fixture["spectral_response"] + ) + pd.testing.assert_series_equal( + qe, sr_and_eqe_fixture["quantum_efficiency"], + check_names=False + ) + assert qe.name == "spectral_response" + #- error on lack of wavelength parameter if no pandas obj. provided + with pytest.raises(TypeError, match="must have an '.index' attribute"): + _ = spectrum.spectral_responsivity_to_quantum_efficiency( + sr_and_eqe_fixture["spectral_response"].values + ) + + +def test_quantum_efficiency_to_spectral_responsivity(sr_and_eqe_fixture): + #- scalar type + sr = spectrum.quantum_efficiency_to_spectral_responsivity( + sr_and_eqe_fixture["quantum_efficiency"].values[0], + sr_and_eqe_fixture.index.values[0], # wavelength, nm + ) + assert_approx_equal(sr, sr_and_eqe_fixture["spectral_response"].values[0]) + #- vector type + sr = spectrum.quantum_efficiency_to_spectral_responsivity( + sr_and_eqe_fixture["quantum_efficiency"].values, + sr_and_eqe_fixture.index.values, # wavelength, nm + ) + assert_allclose(sr, sr_and_eqe_fixture["spectral_response"]) + #- pandas series type + # note: output Series' name should match the input + sr = spectrum.quantum_efficiency_to_spectral_responsivity( + sr_and_eqe_fixture["quantum_efficiency"] + ) + pd.testing.assert_series_equal( + sr, sr_and_eqe_fixture["spectral_response"], + check_names=False + ) + assert sr.name == "quantum_efficiency" + #- error on lack of wavelength parameter if no pandas obj. provided + with pytest.raises(TypeError, match="must have an '.index' attribute"): + _ = spectrum.quantum_efficiency_to_spectral_responsivity( + sr_and_eqe_fixture["quantum_efficiency"].values + ) From 52a4752dce263f161560be587a06a159508bb175 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 24 Mar 2024 13:31:38 +0100 Subject: [PATCH 02/17] Update v0.10.5.rst --- docs/sphinx/source/whatsnew/v0.10.5.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.10.5.rst b/docs/sphinx/source/whatsnew/v0.10.5.rst index e7097ea5b4..096aa67c6a 100644 --- a/docs/sphinx/source/whatsnew/v0.10.5.rst +++ b/docs/sphinx/source/whatsnew/v0.10.5.rst @@ -11,6 +11,10 @@ Deprecations Enhancements ~~~~~~~~~~~~ +* Added :py:func:`pvlib.spectrum.spectral_responsivity_to_quantum_efficiency` + and :py:func:`pvlib.spectrum.quantum_efficiency_to_spectral_responsivity` + to convert quantum efficiencies into spectral responsivities and viceversa. + (:issue:`1963`, :pull:`TODO`) Bug fixes @@ -31,3 +35,6 @@ Requirements Contributors ~~~~~~~~~~~~ +* Echedey Luis (:ghuser:`echedey-ls`) +* Mark Mikofski (:ghuser:`mikofski`) +* Mark Campanelli (:ghuser:`markcampanelli`) From 8fcabde1fedb9432f1e46886053591a1a741631c Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 11 Apr 2024 18:53:35 +0200 Subject: [PATCH 03/17] Add docstring examples --- pvlib/spectrum/mismatch.py | 48 ++++++++++++++++++++++++++++++++++++ pvlib/tests/test_spectrum.py | 5 ++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index 500573efdf..98f8d83f7c 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -613,6 +613,30 @@ def spectral_responsivity_to_quantum_efficiency(sr, wavelength=None): - If ``wavelength`` is provided it will be used independently of the datatype of ``sr``. + Examples + -------- + >>> import numpy as np + >>> from pvlib import spectrum + >>> wavelengths = np.array([350, 550, 750]) + >>> spectral_response = np.array([0.25, 0.40, 0.57]) + >>> quantum_efficiency = \ + >>> spectrum.spectral_responsivity_to_quantum_efficiency( + >>> spectral_response, wavelengths + >>> ) + >>> quantum_efficiency + array([0.88560142, 0.90170326, 0.94227991]) + + >>> spectral_response_series = \ + >>> pd.Series(spectral_response, index=wavelengths, name="dataset") + >>> qe = spectrum.spectral_responsivity_to_quantum_efficiency( + >>> spectral_response_series + >>> ) + >>> qe + 350 0.885601 + 550 0.901703 + 750 0.942280 + Name: dataset, dtype: float64 + References ---------- .. [1] “Spectral Response,” PV Performance Modeling Collaborative (PVPMC). @@ -676,6 +700,30 @@ def quantum_efficiency_to_spectral_responsivity(qe, wavelength=None): - If ``wavelength`` is provided it will be used independently of the datatype of ``qe``. + Examples + -------- + >>> import numpy as np + >>> from pvlib import spectrum + >>> wavelengths = np.array([350, 550, 750]) + >>> quantum_efficiency = np.array([0.86, 0.90, 0.94]) + >>> spectral_response = \ + >>> spectrum.quantum_efficiency_to_spectral_responsivity( + >>> quantum_efficiency, wavelengths + >>> ) + >>> spectral_response + array([0.24277287, 0.39924442, 0.56862085]) + + >>> quantum_efficiency_series = \ + >>> pd.Series(quantum_efficiency, index=wavelengths, name="dataset") + >>> sr = spectrum.quantum_efficiency_to_spectral_responsivity( + >>> quantum_efficiency_series + >>> ) + >>> sr + 350 0.242773 + 550 0.399244 + 750 0.568621 + Name: dataset, dtype: float64 + References ---------- .. [1] “Spectral Response,” PV Performance Modeling Collaborative (PVPMC). diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 9aa126a5a0..3eee204af7 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -319,6 +319,7 @@ def test_spectral_factor_caballero_supplied_ambiguous(): @pytest.fixture def sr_and_eqe_fixture(): + # Just some arbitrary data for testing the conversion functions df = pd.DataFrame( columns=("wavelength", "quantum_efficiency", "spectral_response"), data=[ @@ -368,7 +369,7 @@ def test_spectral_responsivity_to_quantum_efficiency(sr_and_eqe_fixture): check_names=False ) assert qe.name == "spectral_response" - #- error on lack of wavelength parameter if no pandas obj. provided + #- error on lack of wavelength parameter if no pandas object is provided with pytest.raises(TypeError, match="must have an '.index' attribute"): _ = spectrum.spectral_responsivity_to_quantum_efficiency( sr_and_eqe_fixture["spectral_response"].values @@ -398,7 +399,7 @@ def test_quantum_efficiency_to_spectral_responsivity(sr_and_eqe_fixture): check_names=False ) assert sr.name == "quantum_efficiency" - #- error on lack of wavelength parameter if no pandas obj. provided + #- error on lack of wavelength parameter if no pandas object is provided with pytest.raises(TypeError, match="must have an '.index' attribute"): _ = spectrum.quantum_efficiency_to_spectral_responsivity( sr_and_eqe_fixture["quantum_efficiency"].values From 1b5be03176293da15e8bde9921e2fa21dafbc3da Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Tue, 7 May 2024 00:21:59 +0200 Subject: [PATCH 04/17] Update v0.11.0.rst --- docs/sphinx/source/whatsnew/v0.11.0.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 7ea847a6d9..440372d2be 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -15,6 +15,10 @@ Deprecations Enhancements ~~~~~~~~~~~~ +* Added conversion functions from spectral response :math:`[A/W]` to quantum + efficiency :math:`[unitless]` and vice versa. The conversion functions are + :py:func:`spectral_responsivity_to_quantum_efficiency` and :py:func:`quantum_efficiency_to_spectral_responsivity` respectively. + (:issue:`2040`, :pull:`2041`) Bug fixes @@ -35,3 +39,4 @@ Requirements Contributors ~~~~~~~~~~~~ +* Mark Campanelli (:ghuser:`markcampanelli`) From 3631830a5ce894b3f1ede93cf7a8a187898abddc Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Tue, 7 May 2024 00:32:55 +0200 Subject: [PATCH 05/17] Linter --- pvlib/tests/test_spectrum.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 3eee204af7..e6f56c4070 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -347,19 +347,19 @@ def sr_and_eqe_fixture(): def test_spectral_responsivity_to_quantum_efficiency(sr_and_eqe_fixture): - #- scalar type + # scalar type qe = spectrum.spectral_responsivity_to_quantum_efficiency( sr_and_eqe_fixture["spectral_response"].values[0], sr_and_eqe_fixture.index.values[0], # wavelength, nm ) assert_approx_equal(qe, sr_and_eqe_fixture["quantum_efficiency"].values[0]) - #- vector type + # vector type qe = spectrum.spectral_responsivity_to_quantum_efficiency( sr_and_eqe_fixture["spectral_response"].values, sr_and_eqe_fixture.index.values, # wavelength, nm ) assert_allclose(qe, sr_and_eqe_fixture["quantum_efficiency"]) - #- pandas series type + # pandas series type # note: output Series' name should match the input qe = spectrum.spectral_responsivity_to_quantum_efficiency( sr_and_eqe_fixture["spectral_response"] @@ -369,7 +369,7 @@ def test_spectral_responsivity_to_quantum_efficiency(sr_and_eqe_fixture): check_names=False ) assert qe.name == "spectral_response" - #- error on lack of wavelength parameter if no pandas object is provided + # error on lack of wavelength parameter if no pandas object is provided with pytest.raises(TypeError, match="must have an '.index' attribute"): _ = spectrum.spectral_responsivity_to_quantum_efficiency( sr_and_eqe_fixture["spectral_response"].values @@ -377,19 +377,19 @@ def test_spectral_responsivity_to_quantum_efficiency(sr_and_eqe_fixture): def test_quantum_efficiency_to_spectral_responsivity(sr_and_eqe_fixture): - #- scalar type + # scalar type sr = spectrum.quantum_efficiency_to_spectral_responsivity( sr_and_eqe_fixture["quantum_efficiency"].values[0], sr_and_eqe_fixture.index.values[0], # wavelength, nm ) assert_approx_equal(sr, sr_and_eqe_fixture["spectral_response"].values[0]) - #- vector type + # vector type sr = spectrum.quantum_efficiency_to_spectral_responsivity( sr_and_eqe_fixture["quantum_efficiency"].values, sr_and_eqe_fixture.index.values, # wavelength, nm ) assert_allclose(sr, sr_and_eqe_fixture["spectral_response"]) - #- pandas series type + # pandas series type # note: output Series' name should match the input sr = spectrum.quantum_efficiency_to_spectral_responsivity( sr_and_eqe_fixture["quantum_efficiency"] @@ -399,7 +399,7 @@ def test_quantum_efficiency_to_spectral_responsivity(sr_and_eqe_fixture): check_names=False ) assert sr.name == "quantum_efficiency" - #- error on lack of wavelength parameter if no pandas object is provided + # error on lack of wavelength parameter if no pandas object is provided with pytest.raises(TypeError, match="must have an '.index' attribute"): _ = spectrum.quantum_efficiency_to_spectral_responsivity( sr_and_eqe_fixture["quantum_efficiency"].values From 75a27d62fd36705df86fd2e5e9a68e52c44ac61e Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Tue, 7 May 2024 00:33:27 +0200 Subject: [PATCH 06/17] More linter xD --- pvlib/tests/test_spectrum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index e6f56c4070..779276fdff 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -366,7 +366,7 @@ def test_spectral_responsivity_to_quantum_efficiency(sr_and_eqe_fixture): ) pd.testing.assert_series_equal( qe, sr_and_eqe_fixture["quantum_efficiency"], - check_names=False + check_names=False ) assert qe.name == "spectral_response" # error on lack of wavelength parameter if no pandas object is provided @@ -396,7 +396,7 @@ def test_quantum_efficiency_to_spectral_responsivity(sr_and_eqe_fixture): ) pd.testing.assert_series_equal( sr, sr_and_eqe_fixture["spectral_response"], - check_names=False + check_names=False ) assert sr.name == "quantum_efficiency" # error on lack of wavelength parameter if no pandas object is provided From b057d28ca1e3c36c5125b1c3930ff8fe6dd42ef4 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Tue, 7 May 2024 00:37:32 +0200 Subject: [PATCH 07/17] Remove reference links from first sentence Since it messes the links in the toc table. --- pvlib/spectrum/mismatch.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index a8a8bc8215..b7b624289c 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -584,13 +584,15 @@ def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500, def spectral_responsivity_to_quantum_efficiency(sr, wavelength=None): """ - Convert spectral responsivities to quantum efficiencies [1]_. + Convert spectral responsivities to quantum efficiencies. If ``wavelength`` is not provided, the spectral responsivity ``sr`` must be a :py:class:`pandas.Series` or :py:class:`pandas.DataFrame`, with the wavelengths in the index. Provide wavelengths in nanometers :math:`[nm]`. + Conversion is described in [1]_. + .. versionadded:: 0.10.5 Parameters @@ -671,13 +673,15 @@ def spectral_responsivity_to_quantum_efficiency(sr, wavelength=None): def quantum_efficiency_to_spectral_responsivity(qe, wavelength=None): """ - Convert quantum efficiencies to spectral responsivities [1]_. + Convert quantum efficiencies to spectral responsivities. If ``wavelength`` is not provided, the quantum efficiency ``qe`` must be a :py:class:`pandas.Series` or :py:class:`pandas.DataFrame`, with the wavelengths in the index. Provide wavelengths in nanometers :math:`[nm]`. + Conversion is described in [1]_. + .. versionadded:: 0.10.5 Parameters From 27f160e8deeddcc2b4f0236146963a27b82f5e41 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 10 May 2024 10:57:17 +0200 Subject: [PATCH 08/17] remove old whatsme entries Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- docs/sphinx/source/whatsnew/v0.10.5.rst | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.5.rst b/docs/sphinx/source/whatsnew/v0.10.5.rst index 5f7372403e..61e2eac5e5 100644 --- a/docs/sphinx/source/whatsnew/v0.10.5.rst +++ b/docs/sphinx/source/whatsnew/v0.10.5.rst @@ -1,20 +1,6 @@ .. _whatsnew_01050: -v0.10.5 (Anticipated June 2024) -------------------------------- - - -Deprecations -~~~~~~~~~~~~ - - -Enhancements -~~~~~~~~~~~~ -* Added :py:func:`pvlib.spectrum.spectral_responsivity_to_quantum_efficiency` - and :py:func:`pvlib.spectrum.quantum_efficiency_to_spectral_responsivity` - to convert quantum efficiencies into spectral responsivities and viceversa. - (:issue:`1963`, :pull:`TODO`) v0.10.5 (May 6, 2024) --------------------- From d7d9b81b78329ff90f493ca4642db8dc393fdd48 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 10 May 2024 11:04:46 +0200 Subject: [PATCH 09/17] rename funcs and change versionadded's Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- .../effects_on_pv_system_output/spectrum.rst | 4 +- docs/sphinx/source/whatsnew/v0.11.0.rst | 4 +- pvlib/spectrum/__init__.py | 4 +- pvlib/spectrum/mismatch.py | 38 +++++++------------ pvlib/tests/test_spectrum.py | 20 +++++----- 5 files changed, 30 insertions(+), 40 deletions(-) diff --git a/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst b/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst index 9d2e4c51a5..fde3b170a9 100644 --- a/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst +++ b/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst @@ -13,5 +13,5 @@ Spectrum spectrum.spectral_factor_caballero spectrum.spectral_factor_firstsolar spectrum.spectral_factor_sapm - spectrum.spectral_responsivity_to_quantum_efficiency - spectrum.quantum_efficiency_to_spectral_responsivity + spectrum.sr_to_qe + spectrum.qe_to_sr diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 440372d2be..0f93837c6a 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -17,8 +17,8 @@ Enhancements ~~~~~~~~~~~~ * Added conversion functions from spectral response :math:`[A/W]` to quantum efficiency :math:`[unitless]` and vice versa. The conversion functions are - :py:func:`spectral_responsivity_to_quantum_efficiency` and :py:func:`quantum_efficiency_to_spectral_responsivity` respectively. - (:issue:`2040`, :pull:`2041`) + :py:func:`pvlib.spectrum.sr_to_qe` and :py:func:`pvlib.spectrum.qe_to_sr` + respectively. (:issue:`2040`, :pull:`2041`) Bug fixes diff --git a/pvlib/spectrum/__init__.py b/pvlib/spectrum/__init__.py index 00473e1708..0b9f7b03e9 100644 --- a/pvlib/spectrum/__init__.py +++ b/pvlib/spectrum/__init__.py @@ -6,6 +6,6 @@ spectral_factor_caballero, spectral_factor_firstsolar, spectral_factor_sapm, - spectral_responsivity_to_quantum_efficiency, - quantum_efficiency_to_spectral_responsivity, + sr_to_qe, + qe_to_sr, ) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index b7b624289c..2982eb0b58 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -582,7 +582,7 @@ def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500, return modifier -def spectral_responsivity_to_quantum_efficiency(sr, wavelength=None): +def sr_to_qe(sr, wavelength=None): """ Convert spectral responsivities to quantum efficiencies. If ``wavelength`` is not provided, the spectral responsivity ``sr`` must be @@ -593,7 +593,7 @@ def spectral_responsivity_to_quantum_efficiency(sr, wavelength=None): Conversion is described in [1]_. - .. versionadded:: 0.10.5 + .. versionadded:: 0.11.0 Parameters ---------- @@ -622,19 +622,14 @@ def spectral_responsivity_to_quantum_efficiency(sr, wavelength=None): >>> from pvlib import spectrum >>> wavelengths = np.array([350, 550, 750]) >>> spectral_response = np.array([0.25, 0.40, 0.57]) - >>> quantum_efficiency = \ - >>> spectrum.spectral_responsivity_to_quantum_efficiency( - >>> spectral_response, wavelengths - >>> ) - >>> quantum_efficiency + >>> quantum_efficiency = spectrum.sr_to_qe(spectral_response, wavelengths) + >>> print(quantum_efficiency) array([0.88560142, 0.90170326, 0.94227991]) >>> spectral_response_series = \ >>> pd.Series(spectral_response, index=wavelengths, name="dataset") - >>> qe = spectrum.spectral_responsivity_to_quantum_efficiency( - >>> spectral_response_series - >>> ) - >>> qe + >>> qe = spectrum.sr_to_qe(spectral_response_series) + >>> print(qe) 350 0.885601 550 0.901703 750 0.942280 @@ -651,7 +646,7 @@ def spectral_responsivity_to_quantum_efficiency(sr, wavelength=None): See Also -------- - pvlib.spectrum.quantum_efficiency_to_spectral_responsivity + pvlib.spectrum.qe_to_sr """ if wavelength is None: if hasattr(sr, "index"): # true for pandas objects @@ -671,7 +666,7 @@ def spectral_responsivity_to_quantum_efficiency(sr, wavelength=None): return quantum_efficiency -def quantum_efficiency_to_spectral_responsivity(qe, wavelength=None): +def qe_to_sr(qe, wavelength=None): """ Convert quantum efficiencies to spectral responsivities. If ``wavelength`` is not provided, the quantum efficiency ``qe`` must be @@ -682,7 +677,7 @@ def quantum_efficiency_to_spectral_responsivity(qe, wavelength=None): Conversion is described in [1]_. - .. versionadded:: 0.10.5 + .. versionadded:: 0.11.0 Parameters ---------- @@ -711,19 +706,14 @@ def quantum_efficiency_to_spectral_responsivity(qe, wavelength=None): >>> from pvlib import spectrum >>> wavelengths = np.array([350, 550, 750]) >>> quantum_efficiency = np.array([0.86, 0.90, 0.94]) - >>> spectral_response = \ - >>> spectrum.quantum_efficiency_to_spectral_responsivity( - >>> quantum_efficiency, wavelengths - >>> ) - >>> spectral_response + >>> spectral_response = spectrum.qe_to_sr(quantum_efficiency, wavelengths) + >>> print(spectral_response) array([0.24277287, 0.39924442, 0.56862085]) >>> quantum_efficiency_series = \ >>> pd.Series(quantum_efficiency, index=wavelengths, name="dataset") - >>> sr = spectrum.quantum_efficiency_to_spectral_responsivity( - >>> quantum_efficiency_series - >>> ) - >>> sr + >>> sr = spectrum.qe_to_sr(quantum_efficiency_series) + >>> print(sr) 350 0.242773 550 0.399244 750 0.568621 @@ -740,7 +730,7 @@ def quantum_efficiency_to_spectral_responsivity(qe, wavelength=None): See Also -------- - pvlib.spectrum.spectral_responsivity_to_quantum_efficiency + pvlib.spectrum.sr_to_qe """ if wavelength is None: if hasattr(qe, "index"): # true for pandas objects diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 779276fdff..979cc2d644 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -346,22 +346,22 @@ def sr_and_eqe_fixture(): return df -def test_spectral_responsivity_to_quantum_efficiency(sr_and_eqe_fixture): +def test_sr_to_qe(sr_and_eqe_fixture): # scalar type - qe = spectrum.spectral_responsivity_to_quantum_efficiency( + qe = spectrum.sr_to_qe( sr_and_eqe_fixture["spectral_response"].values[0], sr_and_eqe_fixture.index.values[0], # wavelength, nm ) assert_approx_equal(qe, sr_and_eqe_fixture["quantum_efficiency"].values[0]) # vector type - qe = spectrum.spectral_responsivity_to_quantum_efficiency( + qe = spectrum.sr_to_qe( sr_and_eqe_fixture["spectral_response"].values, sr_and_eqe_fixture.index.values, # wavelength, nm ) assert_allclose(qe, sr_and_eqe_fixture["quantum_efficiency"]) # pandas series type # note: output Series' name should match the input - qe = spectrum.spectral_responsivity_to_quantum_efficiency( + qe = spectrum.sr_to_qe( sr_and_eqe_fixture["spectral_response"] ) pd.testing.assert_series_equal( @@ -371,27 +371,27 @@ def test_spectral_responsivity_to_quantum_efficiency(sr_and_eqe_fixture): assert qe.name == "spectral_response" # error on lack of wavelength parameter if no pandas object is provided with pytest.raises(TypeError, match="must have an '.index' attribute"): - _ = spectrum.spectral_responsivity_to_quantum_efficiency( + _ = spectrum.sr_to_qe( sr_and_eqe_fixture["spectral_response"].values ) -def test_quantum_efficiency_to_spectral_responsivity(sr_and_eqe_fixture): +def test_qe_to_sr(sr_and_eqe_fixture): # scalar type - sr = spectrum.quantum_efficiency_to_spectral_responsivity( + sr = spectrum.qe_to_sr( sr_and_eqe_fixture["quantum_efficiency"].values[0], sr_and_eqe_fixture.index.values[0], # wavelength, nm ) assert_approx_equal(sr, sr_and_eqe_fixture["spectral_response"].values[0]) # vector type - sr = spectrum.quantum_efficiency_to_spectral_responsivity( + sr = spectrum.qe_to_sr( sr_and_eqe_fixture["quantum_efficiency"].values, sr_and_eqe_fixture.index.values, # wavelength, nm ) assert_allclose(sr, sr_and_eqe_fixture["spectral_response"]) # pandas series type # note: output Series' name should match the input - sr = spectrum.quantum_efficiency_to_spectral_responsivity( + sr = spectrum.qe_to_sr( sr_and_eqe_fixture["quantum_efficiency"] ) pd.testing.assert_series_equal( @@ -401,6 +401,6 @@ def test_quantum_efficiency_to_spectral_responsivity(sr_and_eqe_fixture): assert sr.name == "quantum_efficiency" # error on lack of wavelength parameter if no pandas object is provided with pytest.raises(TypeError, match="must have an '.index' attribute"): - _ = spectrum.quantum_efficiency_to_spectral_responsivity( + _ = spectrum.qe_to_sr( sr_and_eqe_fixture["quantum_efficiency"].values ) From 0b5ad4b07d1917b91efc36460e6c63f81a850680 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 10 May 2024 11:05:43 +0200 Subject: [PATCH 10/17] Update v0.10.5.rst Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- docs/sphinx/source/whatsnew/v0.10.5.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.5.rst b/docs/sphinx/source/whatsnew/v0.10.5.rst index 61e2eac5e5..00daed3a0f 100644 --- a/docs/sphinx/source/whatsnew/v0.10.5.rst +++ b/docs/sphinx/source/whatsnew/v0.10.5.rst @@ -33,9 +33,6 @@ Requirements Contributors ~~~~~~~~~~~~ -* Echedey Luis (:ghuser:`echedey-ls`) -* Mark Mikofski (:ghuser:`mikofski`) -* Mark Campanelli (:ghuser:`markcampanelli`) * Cliff Hansen (:ghuser:`cwhanse`) * :ghuser:`apct69` * Mark Mikofski (:ghuser:`mikofski`) From 28ebce8daa37fec90b3ffada240457719df7d687 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Tue, 21 May 2024 18:53:41 +0200 Subject: [PATCH 11/17] This is test driven development --- pvlib/tests/test_tools.py | 26 ++++++++++++++++++++++++++ pvlib/tools.py | 27 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/pvlib/tests/test_tools.py b/pvlib/tests/test_tools.py index 583141a726..ddf56c53ef 100644 --- a/pvlib/tests/test_tools.py +++ b/pvlib/tests/test_tools.py @@ -3,6 +3,7 @@ from pvlib import tools import numpy as np import pandas as pd +from numpy.testing import assert_allclose @pytest.mark.parametrize('keys, input_dict, expected', [ @@ -120,3 +121,28 @@ def test_get_pandas_index(args, args_idx): assert index is None else: pd.testing.assert_index_equal(args[args_idx].index, index) + + +@pytest.mark.parametrize('data_in,expected', [ + (np.array([1, 2, 3, 4, 5]), + np.array([0.2, 0.4, 0.6, 0.8, 1])), + (np.array([[0, 1, 2], [0, 3, 6]]), + np.array([[0, 0.5, 1], [0, 0.5, 1]])), + (pd.Series([1, 2, 3, 4, 5]), + pd.Series([0.2, 0.4, 0.6, 0.8, 1])), + (pd.DataFrame({"a": [0, 1, 2], "b": [0, 2, 8]}), + pd.DataFrame({"a": [0, 0.5, 1], "b": [0, 0.25, 1]})), + # test with NaN and all zeroes + (np.array([1, 2, 3, 4, 5]), + np.array([0.2, 0.4, 0.6, 0.8, 1])), + (pd.DataFrame({"a": [0, np.nan, 1], "b": [0, 0, 0]}), + pd.DataFrame({"a": [0, np.nan, 1], "b": [np.nan]*3})), + # test with negative values + (np.array([1, 2, -3, 4, -5]), + np.array([0.2, 0.4, -0.6, 0.8, -1])), + (pd.Series([-2, np.nan, 1]), + pd.Series([-1, np.nan, 0.5])), +]) +def test_normalize_max2one(data_in, expected): + result = tools.normalize_max2one(data_in) + assert_allclose(result, expected) diff --git a/pvlib/tools.py b/pvlib/tools.py index adf502a79d..3d766b6f72 100644 --- a/pvlib/tools.py +++ b/pvlib/tools.py @@ -507,3 +507,30 @@ def get_pandas_index(*args): (a.index for a in args if isinstance(a, (pd.DataFrame, pd.Series))), None ) + + +def normalize_max2one(a): + r""" + Normalize an array so that the largest absolute value is ±1. + + Handles both numpy arrays and pandas objects. + On 2D arrays, normalization is row-wise. + On pandas DataFrame, normalization is column-wise. + + If all values of row are 0, the array is set to NaNs. + + Parameters + ---------- + a : array-like + The array to normalize. + + Returns + ------- + array-like + The normalized array. + """ + try: # expect numpy array + res = a / np.max(np.absolute(a), axis=-1, keepdims=True) + except ValueError: # fails for pandas objects + res = a.div(a.abs().max(axis=0, skipna=True)) + return res From 9bfa003923c02014b240e96958c2e2eaf5906064 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Tue, 21 May 2024 22:26:27 +0200 Subject: [PATCH 12/17] apply normalization to functions --- pvlib/spectrum/mismatch.py | 51 ++++++++++++++++++++++++++++++------ pvlib/tests/test_spectrum.py | 24 ++++++++++++++--- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index 2982eb0b58..2877f306f7 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -3,6 +3,7 @@ """ import pvlib +from pvlib.tools import normalize_max2one import numpy as np import pandas as pd import scipy.constants @@ -582,7 +583,7 @@ def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500, return modifier -def sr_to_qe(sr, wavelength=None): +def sr_to_qe(sr, wavelength=None, normalize=False): """ Convert spectral responsivities to quantum efficiencies. If ``wavelength`` is not provided, the spectral responsivity ``sr`` must be @@ -604,6 +605,12 @@ def sr_to_qe(sr, wavelength=None): wavelength : numeric, optional Points where spectral response is measured, in nanometers :math:`nm`. + normalize : bool, default False + If True, the quantum efficiency is normalized so that the maximum value + is 1. + For ``pandas.DataFrame``, normalization is done for each column. + For 2D arrays, normalization is done for each sub-array. + Returns ------- quantum_efficiency : numeric, same type as ``sr`` @@ -619,6 +626,7 @@ def sr_to_qe(sr, wavelength=None): Examples -------- >>> import numpy as np + >>> import pandas as pd >>> from pvlib import spectrum >>> wavelengths = np.array([350, 550, 750]) >>> spectral_response = np.array([0.25, 0.40, 0.57]) @@ -626,8 +634,7 @@ def sr_to_qe(sr, wavelength=None): >>> print(quantum_efficiency) array([0.88560142, 0.90170326, 0.94227991]) - >>> spectral_response_series = \ - >>> pd.Series(spectral_response, index=wavelengths, name="dataset") + >>> spectral_response_series = pd.Series(spectral_response, index=wavelengths, name="dataset") >>> qe = spectrum.sr_to_qe(spectral_response_series) >>> print(qe) 350 0.885601 @@ -635,6 +642,13 @@ def sr_to_qe(sr, wavelength=None): 750 0.942280 Name: dataset, dtype: float64 + >>> qe = spectrum.sr_to_qe(spectral_response_series, normalize=True) + >>> print(qe) + 350 0.939850 + 550 0.956938 + 750 1.000000 + Name: dataset, dtype: float64 + References ---------- .. [1] “Spectral Response,” PV Performance Modeling Collaborative (PVPMC). @@ -647,7 +661,7 @@ def sr_to_qe(sr, wavelength=None): See Also -------- pvlib.spectrum.qe_to_sr - """ + """ # noqa: E501 if wavelength is None: if hasattr(sr, "index"): # true for pandas objects # use reference to index values instead of index alone so @@ -663,10 +677,14 @@ def sr_to_qe(sr, wavelength=None): / wavelength * _PLANCK_BY_LIGHT_SPEED_OVER_ELEMENTAL_CHARGE_BY_BILLION ) + + if normalize: + quantum_efficiency = normalize_max2one(quantum_efficiency) + return quantum_efficiency -def qe_to_sr(qe, wavelength=None): +def qe_to_sr(qe, wavelength=None, normalize=False): """ Convert quantum efficiencies to spectral responsivities. If ``wavelength`` is not provided, the quantum efficiency ``qe`` must be @@ -688,6 +706,12 @@ def qe_to_sr(qe, wavelength=None): wavelength : numeric, optional Points where quantum efficiency is measured, in nanometers :math:`nm`. + normalize : bool, default False + If True, the spectral response is normalized so that the maximum value + is 1. + For ``pandas.DataFrame``, normalization is done for each column. + For 2D arrays, normalization is done for each sub-array. + Returns ------- spectral_response : numeric, same type as ``qe`` @@ -703,6 +727,7 @@ def qe_to_sr(qe, wavelength=None): Examples -------- >>> import numpy as np + >>> import pandas as pd >>> from pvlib import spectrum >>> wavelengths = np.array([350, 550, 750]) >>> quantum_efficiency = np.array([0.86, 0.90, 0.94]) @@ -710,8 +735,7 @@ def qe_to_sr(qe, wavelength=None): >>> print(spectral_response) array([0.24277287, 0.39924442, 0.56862085]) - >>> quantum_efficiency_series = \ - >>> pd.Series(quantum_efficiency, index=wavelengths, name="dataset") + >>> quantum_efficiency_series = pd.Series(quantum_efficiency, index=wavelengths, name="dataset") >>> sr = spectrum.qe_to_sr(quantum_efficiency_series) >>> print(sr) 350 0.242773 @@ -719,6 +743,13 @@ def qe_to_sr(qe, wavelength=None): 750 0.568621 Name: dataset, dtype: float64 + >>> sr = spectrum.qe_to_sr(quantum_efficiency_series, normalize=True) + >>> print(sr) + 350 0.426950 + 550 0.702128 + 750 1.000000 + Name: dataset, dtype: float64 + References ---------- .. [1] “Spectral Response,” PV Performance Modeling Collaborative (PVPMC). @@ -731,7 +762,7 @@ def qe_to_sr(qe, wavelength=None): See Also -------- pvlib.spectrum.sr_to_qe - """ + """ # noqa: E501 if wavelength is None: if hasattr(qe, "index"): # true for pandas objects # use reference to index values instead of index alone so @@ -747,4 +778,8 @@ def qe_to_sr(qe, wavelength=None): * wavelength / _PLANCK_BY_LIGHT_SPEED_OVER_ELEMENTAL_CHARGE_BY_BILLION ) + + if normalize: + spectral_responsivity = normalize_max2one(spectral_responsivity) + return spectral_responsivity diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 979cc2d644..2cb9d07c22 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -369,11 +369,19 @@ def test_sr_to_qe(sr_and_eqe_fixture): check_names=False ) assert qe.name == "spectral_response" + # series normalization + qe = spectrum.sr_to_qe( + sr_and_eqe_fixture["spectral_response"] * 10, normalize=True + ) + pd.testing.assert_series_equal( + qe, + sr_and_eqe_fixture["quantum_efficiency"] + / max(sr_and_eqe_fixture["quantum_efficiency"]), + check_names=False, + ) # error on lack of wavelength parameter if no pandas object is provided with pytest.raises(TypeError, match="must have an '.index' attribute"): - _ = spectrum.sr_to_qe( - sr_and_eqe_fixture["spectral_response"].values - ) + _ = spectrum.sr_to_qe(sr_and_eqe_fixture["spectral_response"].values) def test_qe_to_sr(sr_and_eqe_fixture): @@ -399,6 +407,16 @@ def test_qe_to_sr(sr_and_eqe_fixture): check_names=False ) assert sr.name == "quantum_efficiency" + # series normalization + sr = spectrum.qe_to_sr( + sr_and_eqe_fixture["quantum_efficiency"] * 10, normalize=True + ) + pd.testing.assert_series_equal( + sr, + sr_and_eqe_fixture["spectral_response"] + / max(sr_and_eqe_fixture["spectral_response"]), + check_names=False, + ) # error on lack of wavelength parameter if no pandas object is provided with pytest.raises(TypeError, match="must have an '.index' attribute"): _ = spectrum.qe_to_sr( From 4d6b44078e2ad6672b187c4792dffc2338504263 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 25 May 2024 01:27:20 +0200 Subject: [PATCH 13/17] Update test_spectrum.py Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- pvlib/tests/test_spectrum.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 2cb9d07c22..7b86cb713e 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -347,12 +347,6 @@ def sr_and_eqe_fixture(): def test_sr_to_qe(sr_and_eqe_fixture): - # scalar type - qe = spectrum.sr_to_qe( - sr_and_eqe_fixture["spectral_response"].values[0], - sr_and_eqe_fixture.index.values[0], # wavelength, nm - ) - assert_approx_equal(qe, sr_and_eqe_fixture["quantum_efficiency"].values[0]) # vector type qe = spectrum.sr_to_qe( sr_and_eqe_fixture["spectral_response"].values, @@ -385,12 +379,6 @@ def test_sr_to_qe(sr_and_eqe_fixture): def test_qe_to_sr(sr_and_eqe_fixture): - # scalar type - sr = spectrum.qe_to_sr( - sr_and_eqe_fixture["quantum_efficiency"].values[0], - sr_and_eqe_fixture.index.values[0], # wavelength, nm - ) - assert_approx_equal(sr, sr_and_eqe_fixture["spectral_response"].values[0]) # vector type sr = spectrum.qe_to_sr( sr_and_eqe_fixture["quantum_efficiency"].values, @@ -422,3 +410,12 @@ def test_qe_to_sr(sr_and_eqe_fixture): _ = spectrum.qe_to_sr( sr_and_eqe_fixture["quantum_efficiency"].values ) + + +def test_qe_and_sr_reciprocal_conversion(sr_and_eqe_fixture): + # test that the conversion functions are reciprocal + qe = spectrum.sr_to_qe(sr_and_eqe_fixture["spectral_response"]) + sr = spectrum.qe_to_sr(qe) + assert_allclose(sr, sr_and_eqe_fixture["spectral_response"]) + qe = spectrum.sr_to_qe(sr) + assert_allclose(qe, sr_and_eqe_fixture["quantum_efficiency"]) From 0b00fb74f095b7a82b52654256eaed55f20e32da Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 25 May 2024 01:28:56 +0200 Subject: [PATCH 14/17] Update test_tools.py Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- pvlib/tests/test_tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pvlib/tests/test_tools.py b/pvlib/tests/test_tools.py index ddf56c53ef..eb9e65c895 100644 --- a/pvlib/tests/test_tools.py +++ b/pvlib/tests/test_tools.py @@ -133,8 +133,6 @@ def test_get_pandas_index(args, args_idx): (pd.DataFrame({"a": [0, 1, 2], "b": [0, 2, 8]}), pd.DataFrame({"a": [0, 0.5, 1], "b": [0, 0.25, 1]})), # test with NaN and all zeroes - (np.array([1, 2, 3, 4, 5]), - np.array([0.2, 0.4, 0.6, 0.8, 1])), (pd.DataFrame({"a": [0, np.nan, 1], "b": [0, 0, 0]}), pd.DataFrame({"a": [0, np.nan, 1], "b": [np.nan]*3})), # test with negative values From 8c30356bfb08eb3736817275dee565b6b58f2766 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 25 May 2024 01:32:09 +0200 Subject: [PATCH 15/17] Units formatting Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> --- pvlib/spectrum/mismatch.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index b14daf5730..7acb5a5d88 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -590,7 +590,7 @@ def sr_to_qe(sr, wavelength=None, normalize=False): a :py:class:`pandas.Series` or :py:class:`pandas.DataFrame`, with the wavelengths in the index. - Provide wavelengths in nanometers :math:`[nm]`. + Provide wavelengths in nanometers, [nm]. Conversion is described in [1]_. @@ -599,11 +599,11 @@ def sr_to_qe(sr, wavelength=None, normalize=False): Parameters ---------- sr : numeric, pandas.Series or pandas.DataFrame - Spectral response. - Index must be the wavelength in nanometers :math:`nm`. + Spectral response, [A/W]. + Index must be the wavelength in nanometers, [nm]. wavelength : numeric, optional - Points where spectral response is measured, in nanometers :math:`nm`. + Points where spectral response is measured, in nanometers, [nm]. normalize : bool, default False If True, the quantum efficiency is normalized so that the maximum value @@ -614,7 +614,7 @@ def sr_to_qe(sr, wavelength=None, normalize=False): Returns ------- quantum_efficiency : numeric, same type as ``sr`` - Quantum efficiency in the interval :math:`[0, 1]`. + Quantum efficiency, in the interval [0, 1]. Notes ----- @@ -691,7 +691,7 @@ def qe_to_sr(qe, wavelength=None, normalize=False): a :py:class:`pandas.Series` or :py:class:`pandas.DataFrame`, with the wavelengths in the index. - Provide wavelengths in nanometers :math:`[nm]`. + Provide wavelengths in nanometers, [nm]. Conversion is described in [1]_. @@ -701,10 +701,10 @@ def qe_to_sr(qe, wavelength=None, normalize=False): ---------- qe : numeric, pandas.Series or pandas.DataFrame Quantum efficiency. - If pandas subtype, index must be the wavelength in nanometers. + If pandas subtype, index must be the wavelength in nanometers, [nm]. wavelength : numeric, optional - Points where quantum efficiency is measured, in nanometers :math:`nm`. + Points where quantum efficiency is measured, in nanometers, [nm]. normalize : bool, default False If True, the spectral response is normalized so that the maximum value @@ -715,7 +715,7 @@ def qe_to_sr(qe, wavelength=None, normalize=False): Returns ------- spectral_response : numeric, same type as ``qe`` - Spectral response in :math:`A/W`. + Spectral response, [A/W]. Notes ----- From 606e9b6a36d4b6af65a9aebdcd5fcb3bd54004b3 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 25 May 2024 01:33:19 +0200 Subject: [PATCH 16/17] Links Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- pvlib/spectrum/mismatch.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index 7acb5a5d88..e3f434f99c 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -652,11 +652,9 @@ def sr_to_qe(sr, wavelength=None, normalize=False): References ---------- .. [1] “Spectral Response,” PV Performance Modeling Collaborative (PVPMC). - https://pvpmc.sandia.gov/modeling-guide/2-dc-module-iv/ - effective-irradiance/spectral-response/ + https://pvpmc.sandia.gov/modeling-guide/2-dc-module-iv/effective-irradiance/spectral-response/ .. [2] “Spectral Response | PVEducation,” www.pveducation.org. - https://www.pveducation.org/pvcdrom/solar-cell-operation/ - spectral-response + https://www.pveducation.org/pvcdrom/solar-cell-operation/spectral-response See Also -------- @@ -753,11 +751,9 @@ def qe_to_sr(qe, wavelength=None, normalize=False): References ---------- .. [1] “Spectral Response,” PV Performance Modeling Collaborative (PVPMC). - https://pvpmc.sandia.gov/modeling-guide/2-dc-module-iv/ - effective-irradiance/spectral-response/ + https://pvpmc.sandia.gov/modeling-guide/2-dc-module-iv/effective-irradiance/spectral-response/ .. [2] “Spectral Response | PVEducation,” www.pveducation.org. - https://www.pveducation.org/pvcdrom/solar-cell-operation/ - spectral-response + https://www.pveducation.org/pvcdrom/solar-cell-operation/spectral-response See Also -------- From 83d875ef3cf3fb7bcb367e3ac0e5dd42bf73adb2 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 25 May 2024 02:09:01 +0200 Subject: [PATCH 17/17] I'm obsessed with math mode --- docs/sphinx/source/whatsnew/v0.11.0.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 4fda536780..2f056cd3e4 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -19,8 +19,8 @@ Enhancements shade perpendicular to ``axis_azimuth``. The function is applicable to both fixed-tilt and one-axis tracking systems. (:issue:`1689`, :pull:`1725`, :pull:`1962`) -* Added conversion functions from spectral response :math:`[A/W]` to quantum - efficiency :math:`[unitless]` and vice versa. The conversion functions are +* Added conversion functions from spectral response ([A/W]) to quantum + efficiency ([unitless]) and vice versa. The conversion functions are :py:func:`pvlib.spectrum.sr_to_qe` and :py:func:`pvlib.spectrum.qe_to_sr` respectively. (:issue:`2040`, :pull:`2041`)