Skip to content

Commit e986e21

Browse files
committed
Merge remote-tracking branch 'upstream/main' into scientific_units_test
2 parents 2115fee + 5aa9ba1 commit e986e21

File tree

15 files changed

+302
-71
lines changed

15 files changed

+302
-71
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""
2+
Use different Perez coefficients with the ModelChain
3+
====================================================
4+
5+
This example demonstrates how to customize the ModelChain
6+
to use site-specific Perez transposition coefficients.
7+
"""
8+
9+
# %%
10+
# The :py:class:`pvlib.modelchain.ModelChain` object provides a useful method
11+
# for easily constructing a PV system model with a simple, unified interface.
12+
# However, a user may want to customize the steps
13+
# in the system model in various ways.
14+
# One such example is during the irradiance transposition step.
15+
# The Perez model perform very well on field data, but
16+
# it requires a set of fitted coefficients from various sites.
17+
# It has been noted that these coefficients can be specific to
18+
# various climates, so users may see improved model accuracy
19+
# when using a site-specific set of coefficients.
20+
# However, the base :py:class:`~pvlib.modelchain.ModelChain`
21+
# only supports the default coefficients.
22+
# This example shows how the :py:class:`~pvlib.modelchain.ModelChain` can
23+
# be adjusted to use a different set of Perez coefficients.
24+
25+
import pandas as pd
26+
from pvlib.pvsystem import PVSystem
27+
from pvlib.modelchain import ModelChain
28+
from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS
29+
from pvlib import iotools, location, irradiance
30+
import pvlib
31+
import os
32+
import matplotlib.pyplot as plt
33+
34+
# load in TMY weather data from North Carolina included with pvlib
35+
PVLIB_DIR = pvlib.__path__[0]
36+
DATA_FILE = os.path.join(PVLIB_DIR, 'data', '723170TYA.CSV')
37+
38+
tmy, metadata = iotools.read_tmy3(DATA_FILE, coerce_year=1990,
39+
map_variables=True)
40+
41+
weather_data = tmy[['ghi', 'dhi', 'dni', 'temp_air', 'wind_speed']]
42+
43+
loc = location.Location.from_tmy(metadata)
44+
45+
#%%
46+
# Now, let's set up a standard PV model using the ``ModelChain``
47+
48+
surface_tilt = metadata['latitude']
49+
surface_azimuth = 180
50+
51+
# define an example module and inverter
52+
sandia_modules = pvlib.pvsystem.retrieve_sam('SandiaMod')
53+
cec_inverters = pvlib.pvsystem.retrieve_sam('cecinverter')
54+
sandia_module = sandia_modules['Canadian_Solar_CS5P_220M___2009_']
55+
cec_inverter = cec_inverters['ABB__MICRO_0_25_I_OUTD_US_208__208V_']
56+
57+
temp_params = TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_glass']
58+
59+
# define the system and ModelChain
60+
system = PVSystem(arrays=None,
61+
surface_tilt=surface_tilt,
62+
surface_azimuth=surface_azimuth,
63+
module_parameters=sandia_module,
64+
inverter_parameters=cec_inverter,
65+
temperature_model_parameters=temp_params)
66+
67+
mc = ModelChain(system, location=loc)
68+
69+
# %%
70+
# Now, let's calculate POA irradiance values outside of the ``ModelChain``.
71+
# We do this for both the default Perez coefficients and the desired
72+
# alternative Perez coefficients. This enables comparison at the end.
73+
74+
# Cape Canaveral seems like the most likely match for climate
75+
model_perez = 'capecanaveral1988'
76+
77+
solar_position = loc.get_solarposition(times=weather_data.index)
78+
dni_extra = irradiance.get_extra_radiation(weather_data.index)
79+
80+
POA_irradiance = irradiance.get_total_irradiance(
81+
surface_tilt=surface_tilt,
82+
surface_azimuth=surface_azimuth,
83+
dni=weather_data['dni'],
84+
ghi=weather_data['ghi'],
85+
dhi=weather_data['dhi'],
86+
solar_zenith=solar_position['apparent_zenith'],
87+
solar_azimuth=solar_position['azimuth'],
88+
model='perez',
89+
dni_extra=dni_extra)
90+
91+
POA_irradiance_new_perez = irradiance.get_total_irradiance(
92+
surface_tilt=surface_tilt,
93+
surface_azimuth=surface_azimuth,
94+
dni=weather_data['dni'],
95+
ghi=weather_data['ghi'],
96+
dhi=weather_data['dhi'],
97+
solar_zenith=solar_position['apparent_zenith'],
98+
solar_azimuth=solar_position['azimuth'],
99+
model='perez',
100+
model_perez=model_perez,
101+
dni_extra=dni_extra)
102+
103+
# %%
104+
# Now, run the ``ModelChain`` with both sets of irradiance data and compare
105+
# (note that to use POA irradiance as input to the ModelChain the method
106+
# `.run_model_from_poa` is used):
107+
108+
mc.run_model_from_poa(POA_irradiance)
109+
ac_power_default = mc.results.ac
110+
111+
mc.run_model_from_poa(POA_irradiance_new_perez)
112+
ac_power_new_perez = mc.results.ac
113+
114+
start, stop = '1990-05-05 06:00:00', '1990-05-05 19:00:00'
115+
plt.plot(ac_power_default.loc[start:stop],
116+
label="Default Composite Perez Model")
117+
plt.plot(ac_power_new_perez.loc[start:stop],
118+
label="Cape Canaveral Perez Model")
119+
plt.xticks(rotation=90)
120+
plt.ylabel("AC Power ($W$)")
121+
plt.legend()
122+
plt.tight_layout()
123+
plt.show()
124+
# %%
125+
# Note that there is a small, but noticeable difference from the default
126+
# coefficients that may add up over longer periods of time.

docs/examples/iv-modeling/plot_singlediode.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@
114114
# mark the MPP
115115
plt.plot([v_mp], [i_mp], ls='', marker='o', c='k')
116116

117+
plt.xlim(left=0)
118+
plt.ylim(bottom=0)
117119
plt.legend(loc=(1.0, 0))
118120
plt.xlabel('Module voltage [V]')
119121
plt.ylabel('Module current [A]')

docs/examples/spectrum/plot_spectrl2_fig51A.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,6 @@
1313
# This example recreates an example figure from the SPECTRL2 NREL Technical
1414
# Report [1]_. The figure shows modeled spectra at hourly intervals across
1515
# a single morning.
16-
#
17-
# References
18-
# ----------
19-
# .. [1] Bird, R, and Riordan, C., 1984, "Simple solar spectral model for
20-
# direct and diffuse irradiance on horizontal and tilted planes at the
21-
# earth's surface for cloudless atmospheres", NREL Technical Report
22-
# TR-215-2436 doi:10.2172/5986936.
2316

2417
# %%
2518
# The SPECTRL2 model has several inputs; some can be calculated with pvlib,
@@ -53,9 +46,10 @@
5346

5447
# %%
5548
# With all the necessary inputs in hand we can model spectral irradiance using
56-
# :py:func:`pvlib.spectrum.spectrl2`. Note that because we are calculating
57-
# the spectra for more than one set of conditions, we will get back 2-D
58-
# arrays (one dimension for wavelength, one for time).
49+
# :py:func:`pvlib.spectrum.spectrl2`. Note that because we are calculating
50+
# the spectra for more than one set of conditions, the spectral irradiance
51+
# components will be returned in a dictionary as 2-D arrays, with one dimension
52+
# for wavelength and one for time.
5953

6054
spectra = spectrum.spectrl2(
6155
apparent_zenith=solpos.apparent_zenith,
@@ -95,3 +89,11 @@
9589
# position and the solar position calculation in the technical report does not
9690
# exactly match the one used here. However, the differences are minor enough
9791
# to not materially change the spectra.
92+
93+
# %%
94+
# References
95+
# ----------
96+
# .. [1] Bird, R, and Riordan, C., 1984, "Simple solar spectral model for
97+
# direct and diffuse irradiance on horizontal and tilted planes at the
98+
# earth's surface for cloudless atmospheres", NREL Technical Report
99+
# TR-215-2436 :doi:`10.2172/5986936`

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ Enhancements
1919
* Add new parameters for min/max absolute air mass to
2020
:py:func:`pvlib.spectrum.spectral_factor_firstsolar`.
2121
(:issue:`2086`, :pull:`2100`)
22+
* Add ``roll_utc_offset`` and ``coerce_year`` arguments to
23+
:py:func:`pvlib.iotools.get_pvgis_tmy` to allow user to specify time zone,
24+
rotate indices of TMY to begin at midnight, and force indices to desired
25+
year. (:issue:`2139`, :pull:`2138`)
2226
* Restructured the pvlib/spectrum folder by breaking up the contents of
2327
pvlib/spectrum/mismatch.py into pvlib/spectrum/mismatch.py,
2428
pvlib/spectrum/irradiance.py, and
@@ -44,6 +48,10 @@ Documentation
4448
* Added gallery example on calculating cell temperature for
4549
floating PV. (:pull:`2110`)
4650

51+
* Added gallery example demonstrating how to use
52+
different Perez coefficients in a ModelChain.
53+
(:issue:`2127`, :pull:`2148`)
54+
4755
* Removed unused "times" input from dni_et() function (:issue:`2105`)
4856

4957
Requirements
@@ -58,4 +66,11 @@ Contributors
5866
* Leonardo Micheli (:ghuser:`lmicheli`)
5967
* Echedey Luis (:ghuser:`echedey-ls`)
6068
* Rajiv Daxini (:ghuser:`RDaxini`)
69+
* Mark A. Mikofski (:ghuser:`mikofski`)
70+
* Ben Pierce (:ghuser:`bgpierc`)
6171
* Jose Meza (:ghuser:`JoseMezaMendieta`)
72+
* Luiz Reis (:ghuser:`luizreiscver`)
73+
* Carlos Cárdenas-Bravo (:ghuser:`cardenca`)
74+
* Marcos R. Escudero (:ghuser:`marc-resc`)
75+
* Bernat Nicolau (:ghuser:`BernatNicolau`)
76+
* Eduardo Sarquis (:ghuser:`EduardoSarquis`)

pvlib/clearsky.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,7 @@ def bird(zenith, airmass_relative, aod380, aod500, precipitable_water,
941941
zenith is never explicitly defined in the report, since the purpose
942942
was to compare existing clear sky models with "rigorous radiative
943943
transfer models" (RTM) it is possible that apparent zenith was
944-
obtained as output from the RTM. However, the implentation presented
944+
obtained as output from the RTM. However, the implementation presented
945945
in PVLIB is tested against the NREL Excel implementation by Daryl
946946
Myers which uses an analytical expression for solar zenith instead
947947
of apparent zenith.

pvlib/data/variables_style_rules.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ bhi;beam/direct horizontal irradiance
1010
ghi;global horizontal irradiance
1111
ghi_extra;horizontal irradiance at top of atmosphere (extraterrestrial)
1212
gri;ground-reflected irradiance
13+
ape;average photon energy
1314
aoi;angle of incidence between :math:`90\deg` and :math:`90\deg`
1415
aoi_projection;cos(aoi)
1516
airmass;airmass

pvlib/iotools/pvgis.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
import json
1919
from pathlib import Path
2020
import requests
21+
import numpy as np
2122
import pandas as pd
23+
import pytz
2224
from pvlib.iotools import read_epw, parse_epw
23-
import warnings
24-
from pvlib._deprecation import pvlibDeprecationWarning
2525

2626
URL = 'https://re.jrc.ec.europa.eu/api/'
2727

@@ -390,9 +390,33 @@ def read_pvgis_hourly(filename, pvgis_format=None, map_variables=True):
390390
raise ValueError(err_msg)
391391

392392

393+
def _coerce_and_roll_tmy(tmy_data, tz, year):
394+
"""
395+
Assumes ``tmy_data`` input is UTC, converts from UTC to ``tz``, rolls
396+
dataframe so timeseries starts at midnight, and forces all indices to
397+
``year``. Only works for integer ``tz``, but ``None`` and ``False`` are
398+
re-interpreted as zero / UTC.
399+
"""
400+
if tz:
401+
tzname = pytz.timezone(f'Etc/GMT{-tz:+d}')
402+
else:
403+
tz = 0
404+
tzname = pytz.timezone('UTC')
405+
new_index = pd.DatetimeIndex([
406+
timestamp.replace(year=year, tzinfo=tzname)
407+
for timestamp in tmy_data.index],
408+
name=f'time({tzname})')
409+
new_tmy_data = pd.DataFrame(
410+
np.roll(tmy_data, tz, axis=0),
411+
columns=tmy_data.columns,
412+
index=new_index)
413+
return new_tmy_data
414+
415+
393416
def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
394417
userhorizon=None, startyear=None, endyear=None,
395-
map_variables=True, url=URL, timeout=30):
418+
map_variables=True, url=URL, timeout=30,
419+
roll_utc_offset=None, coerce_year=None):
396420
"""
397421
Get TMY data from PVGIS.
398422
@@ -424,6 +448,13 @@ def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
424448
base url of PVGIS API, append ``tmy`` to get TMY endpoint
425449
timeout : int, default 30
426450
time in seconds to wait for server response before timeout
451+
roll_utc_offset: int, optional
452+
Use to specify a time zone other than the default UTC zero and roll
453+
dataframe by ``roll_utc_offset`` so it starts at midnight on January
454+
1st. Ignored if ``None``, otherwise will force year to ``coerce_year``.
455+
coerce_year: int, optional
456+
Use to force indices to desired year. Will default to 1990 if
457+
``coerce_year`` is not specified, but ``roll_utc_offset`` is specified.
427458
428459
Returns
429460
-------
@@ -510,6 +541,11 @@ def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
510541
if map_variables:
511542
data = data.rename(columns=VARIABLE_MAP)
512543

544+
if not (roll_utc_offset is None and coerce_year is None):
545+
# roll_utc_offset is specified, but coerce_year isn't
546+
coerce_year = coerce_year or 1990
547+
data = _coerce_and_roll_tmy(data, roll_utc_offset, coerce_year)
548+
513549
return data, months_selected, inputs, meta
514550

515551

pvlib/irradiance.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,8 +395,8 @@ def get_sky_diffuse(surface_tilt, surface_azimuth,
395395
Raises
396396
------
397397
ValueError
398-
If model is one of ``'haydavies'``, ``'reindl'``, or ``'perez'`` and
399-
``dni_extra`` is not specified.
398+
If model is one of ``'haydavies'``, ``'reindl'``, ``'perez'``, or
399+
``'perez_driesse'`` and ``dni_extra`` is not specified.
400400
401401
Notes
402402
-----

pvlib/modelchain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1474,7 +1474,7 @@ def prepare_inputs_from_poa(self, data):
14741474
data : DataFrame, or tuple or list of DataFrame
14751475
Contains plane-of-array irradiance data. Required column names
14761476
include ``'poa_global'``, ``'poa_direct'`` and ``'poa_diffuse'``.
1477-
Columns with weather-related data are ssigned to the
1477+
Columns with weather-related data are assigned to the
14781478
``weather`` attribute. If columns for ``'temp_air'`` and
14791479
``'wind_speed'`` are not provided, air temperature of 20 C and wind
14801480
speed of 0 m/s are assumed.

pvlib/pvsystem.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ def scale_voltage_current_power(self, data):
841841
@_unwrap_single_value
842842
def pvwatts_dc(self, g_poa_effective, temp_cell):
843843
"""
844-
Calcuates DC power according to the PVWatts model using
844+
Calculates DC power according to the PVWatts model using
845845
:py:func:`pvlib.pvsystem.pvwatts_dc`, `self.module_parameters['pdc0']`,
846846
and `self.module_parameters['gamma_pdc']`.
847847
@@ -1550,7 +1550,7 @@ def calcparams_desoto(effective_irradiance, temp_cell,
15501550
Light-generated current in amperes
15511551
15521552
saturation_current : numeric
1553-
Diode saturation curent in amperes
1553+
Diode saturation current in amperes
15541554
15551555
resistance_series : numeric
15561556
Series resistance in ohms

0 commit comments

Comments
 (0)