Skip to content

Commit 15b69cf

Browse files
authored
Merge branch 'main' into update/nasa-power-variable-map
2 parents 6c539b2 + 66d43b3 commit 15b69cf

11 files changed

Lines changed: 165 additions & 33 deletions

File tree

docs/examples/irradiance-transposition/plot_rtranpose_limitations.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
#
2929
# In this example we look at a single point in time and consider a full range
3030
# of possible GHI and POA global values as shown in figures 3 and 4 of [1]_.
31-
# Then we use :py:meth:`pvlib.irradiance.ghi_from_poa_driesse_2023` to estimate
31+
# Then we use :py:meth:`pvlib.irradiance.ghi_from_poa_driesse_2024` to estimate
3232
# the original GHI from POA global.
3333
#
3434
# References
@@ -45,7 +45,7 @@
4545

4646
from pvlib.irradiance import (erbs_driesse,
4747
get_total_irradiance,
48-
ghi_from_poa_driesse_2023,
48+
ghi_from_poa_driesse_2024,
4949
)
5050

5151
matplotlib.rcParams['axes.grid'] = True
@@ -92,7 +92,7 @@
9292

9393
poa_test = 200
9494

95-
ghi_hat = ghi_from_poa_driesse_2023(surface_tilt, surface_azimuth,
95+
ghi_hat = ghi_from_poa_driesse_2024(surface_tilt, surface_azimuth,
9696
solar_zenith, solar_azimuth,
9797
poa_test,
9898
dni_extra,
@@ -156,7 +156,7 @@
156156
# out, other times not.
157157
#
158158

159-
result = ghi_from_poa_driesse_2023(surface_tilt, surface_azimuth,
159+
result = ghi_from_poa_driesse_2024(surface_tilt, surface_azimuth,
160160
solar_zenith, solar_azimuth,
161161
poa_global,
162162
dni_extra,

docs/examples/irradiance-transposition/plot_rtranpose_year.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# Recovering GHI from POA irradiance is termed "reverse transposition."
2222
#
2323
# In this example we start with a TMY file and calculate POA global irradiance.
24-
# Then we use :py:meth:`pvlib.irradiance.ghi_from_poa_driesse_2023` to estimate
24+
# Then we use :py:meth:`pvlib.irradiance.ghi_from_poa_driesse_2024` to estimate
2525
# the original GHI from POA global. Details of the method found in [1]_.
2626
#
2727
# Another method for reverse tranposition called GTI-DIRINT is also
@@ -49,7 +49,7 @@
4949
from pvlib import iotools, location
5050
from pvlib.irradiance import (get_extra_radiation,
5151
get_total_irradiance,
52-
ghi_from_poa_driesse_2023,
52+
ghi_from_poa_driesse_2024,
5353
aoi,
5454
)
5555

@@ -114,7 +114,7 @@
114114

115115
start = time.process_time()
116116

117-
df['ghi_rev'] = ghi_from_poa_driesse_2023(TILT, ORIENT,
117+
df['ghi_rev'] = ghi_from_poa_driesse_2024(TILT, ORIENT,
118118
solpos.apparent_zenith,
119119
solpos.azimuth,
120120
df.poa_global,

docs/sphinx/source/reference/irradiance/reverse-transposition.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ Reverse transposition models
66
.. autosummary::
77
:toctree: ../generated/
88

9-
irradiance.ghi_from_poa_driesse_2023
9+
irradiance.ghi_from_poa_driesse_2024
1010
irradiance.gti_dirint

docs/sphinx/source/whatsnew/v0.15.2.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,17 @@ Breaking Changes
1010

1111
Deprecations
1212
~~~~~~~~~~~~
13+
* Rename :py:func:`!pvlib.irradiance.ghi_from_poa_driesse_2023` to
14+
:py:func:`~pvlib.irradiance.ghi_from_poa_driesse_2024`. The year now reflects
15+
the publication date. The old name will be removed in v0.17.0.
16+
(:issue:`2774`, :pull:`2777`)
1317

1418

1519
Bug fixes
1620
~~~~~~~~~
21+
* Added test coverage for :py:func:`pvlib.irradiance.dirint` with
22+
``np.array`` and ``pd.Series`` inputs.
23+
(:issue:`2751`, :pull:`2752`)
1724
* Corrects a bug in :py:func:`pvlib.temperature.fuentes`. If inputs were
1825
data type integer, users can expect modeled cell temperature values to
1926
increase slightly.
@@ -22,6 +29,14 @@ Bug fixes
2229
introduced in v0.15.1 (:pull:`2702`) that caused a broadcasting
2330
``ValueError`` when ``tracker_theta`` was a 2-D (or higher rank) array.
2431
(:issue:`2747`, :pull:`2749`)
32+
* Document that timestamps returned by :py:func:`~pvlib.iotools.get_era5`
33+
represent the end of the averaging interval, consistent with ERA5
34+
conventions. (:issue:`2772`, :pull:`2773`)
35+
36+
* :py:func:`pvlib.iotools.read_nsrdb_psm4` now parses the file header with the
37+
:py:mod:`csv` module instead of a naive ``str.split(',')``, so quoted column
38+
names containing commas (e.g. the material names in spectral-on-demand files)
39+
are no longer split into spurious columns. (:issue:`2736`, :pull:`2771`)
2540

2641
Enhancements
2742
~~~~~~~~~~~~
@@ -61,7 +76,10 @@ Maintenance
6176
Contributors
6277
~~~~~~~~~~~~
6378
* :ghuser:`Omesh37`
79+
* :ghuser:`gaoflow`
6480
* Cliff Hansen (:ghuser:`cwhanse`)
81+
* :ghuser:`shethkajal7`
6582
* Arthur Onno (:ghuser:`ArthurOnnoTerabase`)
6683
* Adam R. Jensen (:ghuser:`AdamRJensen`)
6784
* Karl Hill (:ghuser:`karlhillx`)
85+
* Rajiv Daxini (:ghuser:`RDaxini`)

pvlib/iotools/era5.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def get_era5(latitude, longitude, start, end, variables, api_key,
102102
Returns
103103
-------
104104
data : pd.DataFrame
105-
Time series data. The index corresponds to the start of the interval.
105+
Time series data. The index corresponds to the end of the interval.
106106
meta : dict
107107
Metadata.
108108

pvlib/iotools/psm4.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
https://developer.nlr.gov/docs/solar/nsrdb/nsrdb-GOES-full-disc-v4-0-0-download/
77
"""
88

9+
import csv
910
import io
1011
from urllib.parse import urljoin
1112
import requests
@@ -723,11 +724,16 @@ def read_nsrdb_psm4(filename, map_variables=True):
723724
<https://web.archive.org/web/20170207203107/https://sam.nrel.gov/sites/default/files/content/documents/pdf/wfcsv.pdf>`_
724725
"""
725726
with tools._file_context_manager(filename) as fbuf:
727+
# The first 3 header lines are parsed with the csv module rather than a
728+
# naive str.split(',') so that quoted fields containing commas are kept
729+
# intact. Spectral-on-demand files, for instance, have column names
730+
# like '"GaAs (Bauhuis et al., 2009)"' whose embedded commas would
731+
# otherwise be split into spurious columns (see GH #2736).
726732
# The first 2 lines of the response are headers with metadata
727-
metadata_fields = fbuf.readline().split(',')
728-
metadata_values = fbuf.readline().split(',')
733+
metadata_fields = next(csv.reader([fbuf.readline()]))
734+
metadata_values = next(csv.reader([fbuf.readline()]))
729735
# get the column names so we can set the dtypes
730-
columns = fbuf.readline().split(',')
736+
columns = next(csv.reader([fbuf.readline()]))
731737
columns[-1] = columns[-1].strip() # strip trailing newline
732738
# Since the header has so many columns, excel saves blank cols in the
733739
# data below the header lines.

pvlib/irradiance.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from pvlib import atmosphere, solarposition, tools
1717
import pvlib # used to avoid dni name collision in complete_irradiance
1818

19-
from pvlib._deprecation import pvlibDeprecationWarning
19+
from pvlib._deprecation import pvlibDeprecationWarning, deprecated
2020
import warnings
2121

2222

@@ -132,7 +132,9 @@ def _handle_extra_radiation_types(datetime_or_doy, epoch_year):
132132
# a better way to do it.
133133
if isinstance(datetime_or_doy, pd.DatetimeIndex):
134134
to_doy = tools._pandas_to_doy # won't be evaluated unless necessary
135-
def to_datetimeindex(x): return x # noqa: E306
135+
136+
def to_datetimeindex(x):
137+
return x # noqa: E306
136138
to_output = partial(pd.Series, index=datetime_or_doy)
137139
elif isinstance(datetime_or_doy, pd.Timestamp):
138140
to_doy = tools._pandas_to_doy
@@ -146,12 +148,14 @@ def to_datetimeindex(x): return x # noqa: E306
146148
tools._datetimelike_scalar_to_datetimeindex
147149
to_output = tools._scalar_out
148150
elif np.isscalar(datetime_or_doy): # ints and floats of various types
149-
def to_doy(x): return x # noqa: E306
151+
def to_doy(x):
152+
return x # noqa: E306
150153
to_datetimeindex = partial(tools._doy_to_datetimeindex,
151154
epoch_year=epoch_year)
152155
to_output = tools._scalar_out
153156
else: # assume that we have an array-like object of doy
154-
def to_doy(x): return x # noqa: E306
157+
def to_doy(x):
158+
return x # noqa: E306
155159
to_datetimeindex = partial(tools._doy_to_datetimeindex,
156160
epoch_year=epoch_year)
157161
to_output = tools._array_out
@@ -1075,7 +1079,7 @@ def perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra,
10751079
airmass : numeric
10761080
Relative (not pressure-corrected) airmass values. If AM is a
10771081
DataFrame it must be of the same size as all other DataFrame
1078-
inputs. AM must be >=0 (careful using the 1/sec(z) model of AM
1082+
inputs. AM must be >=0 (careful using the 1/cos(z) model of AM
10791083
generation). [unitless]
10801084
10811085
model : string, default 'allsitescomposite1990'
@@ -1335,7 +1339,7 @@ def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra,
13351339
airmass : numeric, optional
13361340
Relative (not pressure-corrected) airmass values. If ``airmass`` is a
13371341
DataFrame it must be of the same size as all other DataFrame
1338-
inputs. AM must be >=0 (careful using the 1/sec(z) model of AM
1342+
inputs. AM must be >=0 (careful using the 1/cos(z) model of AM
13391343
generation). [unitless]
13401344
13411345
return_components: bool (optional, default=False)
@@ -1442,7 +1446,7 @@ def _poa_from_ghi(surface_tilt, surface_azimuth,
14421446
Transposition function that includes decomposition of GHI using the
14431447
continuous Erbs-Driesse model.
14441448
1445-
Helper function for ghi_from_poa_driesse_2023.
1449+
Helper function for ghi_from_poa_driesse_2024.
14461450
'''
14471451
# Contributed by Anton Driesse (@adriesse), PV Performance Labs. Nov., 2023
14481452

@@ -1468,7 +1472,7 @@ def _ghi_from_poa(surface_tilt, surface_azimuth,
14681472
'''
14691473
Reverse transposition function that uses the scalar bisection from scipy.
14701474
1471-
Helper function for ghi_from_poa_driesse_2023.
1475+
Helper function for ghi_from_poa_driesse_2024.
14721476
'''
14731477
# Contributed by Anton Driesse (@adriesse), PV Performance Labs. Nov., 2023
14741478

@@ -1512,7 +1516,7 @@ def poa_error(ghi):
15121516
return ghi, conv, niter
15131517

15141518

1515-
def ghi_from_poa_driesse_2023(surface_tilt, surface_azimuth,
1519+
def ghi_from_poa_driesse_2024(surface_tilt, surface_azimuth,
15161520
solar_zenith, solar_azimuth,
15171521
poa_global,
15181522
dni_extra, airmass=None, albedo=0.25,
@@ -1614,6 +1618,14 @@ def ghi_from_poa_driesse_2023(surface_tilt, surface_azimuth,
16141618
return ghi
16151619

16161620

1621+
ghi_from_poa_driesse_2023 = deprecated(
1622+
since="0.15.2",
1623+
name="pvlib.irradiance.ghi_from_poa_driesse_2023",
1624+
alternative="pvlib.irradiance.ghi_from_poa_driesse_2024",
1625+
removal="0.17.0",
1626+
)(ghi_from_poa_driesse_2024)
1627+
1628+
16171629
def clearsky_index(ghi, ghi_clear, max_clearsky_index=2.0):
16181630
"""
16191631
Calculate the clearsky index.
@@ -1964,14 +1976,14 @@ def dirint(ghi, solar_zenith, times, pressure=101325., use_delta_kt_prime=True,
19641976
19651977
Returns
19661978
-------
1967-
dni : array-like
1968-
The modeled direct normal irradiance, as provided by the
1969-
DIRINT model. [Wm⁻²]
1979+
dni : pd.Series
1980+
Estimated direct normal irradiance. [Wm⁻²]
19701981
19711982
Notes
19721983
-----
1973-
DIRINT model requires time series data (ie. one of the inputs must
1974-
be a vector of length > 2).
1984+
The DIRINT model was developed for time series data with length > 2.
1985+
The implementation in pvlib assumes the data are periodic which may
1986+
affect the first and last DNI values.
19751987
19761988
References
19771989
----------
@@ -2109,6 +2121,7 @@ def _dirint_bins(times, kt_prime, zenith, w, delta_kt_prime):
21092121
-------
21102122
tuple of kt_prime_bin, zenith_bin, w_bin, delta_kt_prime_bin
21112123
"""
2124+
21122125
# @wholmgren: the following bin assignments use MATLAB's 1-indexing.
21132126
# Later, we'll subtract 1 to conform to Python's 0-indexing.
21142127

pvlib/snow.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ def _townsend_effective_snow(snow_total, snow_events):
248248

249249
def loss_townsend(snow_total, snow_events, surface_tilt, relative_humidity,
250250
temp_air, poa_global, slant_height, lower_edge_height,
251-
string_factor=1.0, angle_of_repose=40):
251+
string_factor=1.0, angle_of_repose=40,
252+
front_side_fraction=1.0):
252253
'''
253254
Calculates monthly snow loss based on the Townsend monthly snow loss
254255
model.
@@ -293,6 +294,12 @@ def loss_townsend(snow_total, snow_events, surface_tilt, relative_humidity,
293294
Piled snow angle, assumed to stabilize at 40°, the midpoint of
294295
25°-55° avalanching slope angles. [deg]
295296
297+
front_side_fraction : numeric or array-like, default 1.0
298+
Fraction of monthly energy from front-side insolation. [unitless]
299+
Multiplies the calculated loss fraction. For example,
300+
use 0.9 when 90% of monthly energy is from the front side
301+
of a bifacial system and 10% is from the rear side.
302+
296303
Returns
297304
-------
298305
loss : array-like
@@ -310,6 +317,10 @@ def loss_townsend(snow_total, snow_events, surface_tilt, relative_humidity,
310317
publication of [1]_, as described in [2]_.
311318
The definition for snow events documented above is based on [3]_.
312319
320+
For bifacial systems, [2]_ recommends including both front-side and
321+
rear-side insolation in ``poa_global``. The resulting loss is
322+
scaled by the front-side energy fraction ``front_side_fraction``.
323+
313324
References
314325
----------
315326
.. [1] Townsend, Tim & Powers, Loren. (2011). Photovoltaics and snow: An
@@ -384,4 +395,6 @@ def loss_townsend(snow_total, snow_events, surface_tilt, relative_humidity,
384395
* string_factor
385396
)
386397

398+
loss_fraction = loss_fraction * front_side_fraction
399+
387400
return np.clip(loss_fraction, 0, 1)

tests/iotools/test_psm4.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,31 @@ def test_read_nsrdb_psm4_map_variables():
185185
assert_index_equal(data.columns, pd.Index(columns_mapped))
186186

187187

188+
def test_read_nsrdb_psm4_quoted_columns_with_commas():
189+
"""spectral-on-demand files have quoted column names containing commas;
190+
these must not be split into spurious columns (GH #2736)"""
191+
# Minimal NSRDB file whose column header (3rd line) has quoted material
192+
# names with embedded commas, which is valid CSV. A naive str.split(',')
193+
# would break these into extra columns and raise on read.
194+
content = (
195+
"Source,Location ID,City,State,Country,Latitude,Longitude,Time Zone,"
196+
"Elevation,Local Time Zone,Version\n"
197+
"NSRDB,1,-,-,-,40.0,-105.0,-7,1600,-7,4.0.1\n"
198+
'Year,Month,Day,Hour,Minute,GHI,"GaAs (Bauhuis et al., 2009)",'
199+
'"InGaP (Gray, 2008)"\n'
200+
"2023,1,1,0,0,0,0.1,0.2\n"
201+
"2023,1,1,1,0,5,0.3,0.4\n"
202+
)
203+
data, metadata = psm4.read_nsrdb_psm4(StringIO(content),
204+
map_variables=False)
205+
assert list(data.columns) == [
206+
'Year', 'Month', 'Day', 'Hour', 'Minute', 'GHI',
207+
'GaAs (Bauhuis et al., 2009)', 'InGaP (Gray, 2008)']
208+
assert data.shape == (2, 8)
209+
# the embedded-comma data columns round-trip as floats
210+
assert data['GaAs (Bauhuis et al., 2009)'].tolist() == [0.1, 0.3]
211+
212+
188213
@pytest.mark.remote_data
189214
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
190215
def test_get_nsrdb_psm4_aggregated_parameter_mapping(nlr_api_key):

0 commit comments

Comments
 (0)