Intro Tutorial#
This page contains introductory examples of pvlib python usage.
Modeling paradigms#
The backbone of pvlib-python is well-tested procedural code that implements PV system models. pvlib-python also provides a collection of classes for users that prefer object-oriented programming. These classes can help users keep track of data in a more organized way, provide some “smart” functions with more flexible inputs, and simplify the modeling process for common situations. The classes do not add any algorithms beyond what’s available in the procedural code, and most of the object methods are simple wrappers around the corresponding procedural code.
Let’s use each of these pvlib modeling paradigms to calculate the yearly energy yield for a given hardware configuration at a handful of sites listed below.
In [1]: import pvlib
In [2]: import pandas as pd
In [3]: import matplotlib.pyplot as plt
# latitude, longitude, name, altitude, timezone
In [4]: coordinates = [
...: (32.2, -111.0, 'Tucson', 700, 'Etc/GMT+7'),
...: (35.1, -106.6, 'Albuquerque', 1500, 'Etc/GMT+7'),
...: (37.8, -122.4, 'San Francisco', 10, 'Etc/GMT+8'),
...: (52.5, 13.4, 'Berlin', 34, 'Etc/GMT-1'),
...: ]
...:
# get the module and inverter specifications from SAM
In [5]: sandia_modules = pvlib.pvsystem.retrieve_sam('SandiaMod')
In [6]: sapm_inverters = pvlib.pvsystem.retrieve_sam('cecinverter')
In [7]: module = sandia_modules['Canadian_Solar_CS5P_220M___2009_']
In [8]: inverter = sapm_inverters['ABB__MICRO_0_25_I_OUTD_US_208__208V_']
In [9]: temperature_model_parameters = pvlib.temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_glass']
In order to retrieve meteorological data for the simulation, we can make use of the IO Tools module. In this example we will be using PVGIS, one of the data sources available, to retrieve a Typical Meteorological Year (TMY) which includes irradiation, temperature and wind speed.
In [10]: tmys = []
In [11]: for location in coordinates:
....: latitude, longitude, name, altitude, timezone = location
....: weather = pvlib.iotools.get_pvgis_tmy(latitude, longitude,
....: map_variables=True)[0]
....: weather.index.name = "utc_time"
....: tmys.append(weather)
....:
---------------------------------------------------------------------------
timeout Traceback (most recent call last)
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/urllib3/connectionpool.py in _make_request(self, conn, method, url, timeout, chunked, **httplib_request_kw)
448 # Otherwise it looks like a bug in the code.
--> 449 six.raise_from(e, None)
450 except (SocketTimeout, BaseSSLError, SocketError) as e:
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/urllib3/packages/six.py in raise_from(value, from_value)
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/urllib3/connectionpool.py in _make_request(self, conn, method, url, timeout, chunked, **httplib_request_kw)
443 try:
--> 444 httplib_response = conn.getresponse()
445 except BaseException as e:
~/.asdf/installs/python/3.7.15/lib/python3.7/http/client.py in getresponse(self)
1372 try:
-> 1373 response.begin()
1374 except ConnectionError:
~/.asdf/installs/python/3.7.15/lib/python3.7/http/client.py in begin(self)
318 while True:
--> 319 version, status, reason = self._read_status()
320 if status != CONTINUE:
~/.asdf/installs/python/3.7.15/lib/python3.7/http/client.py in _read_status(self)
279 def _read_status(self):
--> 280 line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
281 if len(line) > _MAXLINE:
~/.asdf/installs/python/3.7.15/lib/python3.7/socket.py in readinto(self, b)
588 try:
--> 589 return self._sock.recv_into(b)
590 except timeout:
~/.asdf/installs/python/3.7.15/lib/python3.7/ssl.py in recv_into(self, buffer, nbytes, flags)
1070 self.__class__)
-> 1071 return self.read(nbytes, buffer)
1072 else:
~/.asdf/installs/python/3.7.15/lib/python3.7/ssl.py in read(self, len, buffer)
928 if buffer is not None:
--> 929 return self._sslobj.read(len, buffer)
930 else:
timeout: The read operation timed out
During handling of the above exception, another exception occurred:
ReadTimeoutError Traceback (most recent call last)
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/requests/adapters.py in send(self, request, stream, timeout, verify, cert, proxies)
498 retries=self.max_retries,
--> 499 timeout=timeout,
500 )
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/urllib3/connectionpool.py in urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
787 retries = retries.increment(
--> 788 method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]
789 )
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/urllib3/util/retry.py in increment(self, method, url, response, error, _pool, _stacktrace)
549 if read is False or not self._is_method_retryable(method):
--> 550 raise six.reraise(type(error), error, _stacktrace)
551 elif read is not None:
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/urllib3/packages/six.py in reraise(tp, value, tb)
769 raise value.with_traceback(tb)
--> 770 raise value
771 finally:
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/urllib3/connectionpool.py in urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)
709 headers=headers,
--> 710 chunked=chunked,
711 )
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/urllib3/connectionpool.py in _make_request(self, conn, method, url, timeout, chunked, **httplib_request_kw)
450 except (SocketTimeout, BaseSSLError, SocketError) as e:
--> 451 self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
452 raise
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/urllib3/connectionpool.py in _raise_timeout(self, err, url, timeout_value)
340 raise ReadTimeoutError(
--> 341 self, url, "Read timed out. (read timeout=%s)" % timeout_value
342 )
ReadTimeoutError: HTTPSConnectionPool(host='re.jrc.ec.europa.eu', port=443): Read timed out. (read timeout=30)
During handling of the above exception, another exception occurred:
ReadTimeout Traceback (most recent call last)
<ipython-input-11-30c9501a9f2f> in <module>
2 latitude, longitude, name, altitude, timezone = location
3 weather = pvlib.iotools.get_pvgis_tmy(latitude, longitude,
----> 4 map_variables=True)[0]
5 weather.index.name = "utc_time"
6 tmys.append(weather)
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/checkouts/stable/pvlib/iotools/pvgis.py in get_pvgis_tmy(latitude, longitude, outputformat, usehorizon, userhorizon, startyear, endyear, url, map_variables, timeout)
460 if endyear is not None:
461 params['endyear'] = endyear
--> 462 res = requests.get(url + 'tmy', params=params, timeout=timeout)
463 # PVGIS returns really well formatted error messages in JSON for HTTP/1.1
464 # 400 BAD REQUEST so try to return that if possible, otherwise raise the
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/requests/api.py in get(url, params, **kwargs)
71 """
72
---> 73 return request("get", url, params=params, **kwargs)
74
75
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/requests/api.py in request(method, url, **kwargs)
57 # cases, and look like a memory leak in others.
58 with sessions.Session() as session:
---> 59 return session.request(method=method, url=url, **kwargs)
60
61
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/requests/sessions.py in request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
585 }
586 send_kwargs.update(settings)
--> 587 resp = self.send(prep, **send_kwargs)
588
589 return resp
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/requests/sessions.py in send(self, request, **kwargs)
699
700 # Send the request
--> 701 r = adapter.send(request, **kwargs)
702
703 # Total elapsed time of the request (approximately)
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/requests/adapters.py in send(self, request, stream, timeout, verify, cert, proxies)
576 raise SSLError(e, request=request)
577 elif isinstance(e, ReadTimeoutError):
--> 578 raise ReadTimeout(e, request=request)
579 elif isinstance(e, _InvalidHeader):
580 raise InvalidHeader(e, request=request)
ReadTimeout: HTTPSConnectionPool(host='re.jrc.ec.europa.eu', port=443): Read timed out. (read timeout=30)
Procedural#
The straightforward procedural code can be used for all modeling steps in pvlib-python.
The following code demonstrates how to use the procedural code to accomplish our system modeling goal:
In [12]: system = {'module': module, 'inverter': inverter,
....: 'surface_azimuth': 180}
....:
In [13]: energies = {}
In [14]: for location, weather in zip(coordinates, tmys):
....: latitude, longitude, name, altitude, timezone = location
....: system['surface_tilt'] = latitude
....: solpos = pvlib.solarposition.get_solarposition(
....: time=weather.index,
....: latitude=latitude,
....: longitude=longitude,
....: altitude=altitude,
....: temperature=weather["temp_air"],
....: pressure=pvlib.atmosphere.alt2pres(altitude),
....: )
....: dni_extra = pvlib.irradiance.get_extra_radiation(weather.index)
....: airmass = pvlib.atmosphere.get_relative_airmass(solpos['apparent_zenith'])
....: pressure = pvlib.atmosphere.alt2pres(altitude)
....: am_abs = pvlib.atmosphere.get_absolute_airmass(airmass, pressure)
....: aoi = pvlib.irradiance.aoi(
....: system['surface_tilt'],
....: system['surface_azimuth'],
....: solpos["apparent_zenith"],
....: solpos["azimuth"],
....: )
....: total_irradiance = pvlib.irradiance.get_total_irradiance(
....: system['surface_tilt'],
....: system['surface_azimuth'],
....: solpos['apparent_zenith'],
....: solpos['azimuth'],
....: weather['dni'],
....: weather['ghi'],
....: weather['dhi'],
....: dni_extra=dni_extra,
....: model='haydavies',
....: )
....: cell_temperature = pvlib.temperature.sapm_cell(
....: total_irradiance['poa_global'],
....: weather["temp_air"],
....: weather["wind_speed"],
....: **temperature_model_parameters,
....: )
....: effective_irradiance = pvlib.pvsystem.sapm_effective_irradiance(
....: total_irradiance['poa_direct'],
....: total_irradiance['poa_diffuse'],
....: am_abs,
....: aoi,
....: module,
....: )
....: dc = pvlib.pvsystem.sapm(effective_irradiance, cell_temperature, module)
....: ac = pvlib.inverter.sandia(dc['v_mp'], dc['p_mp'], inverter)
....: annual_energy = ac.sum()
....: energies[name] = annual_energy
....:
In [15]: energies = pd.Series(energies)
# based on the parameters specified above, these are in W*hrs
In [16]: print(energies)
Series([], dtype: float64)
In [17]: energies.plot(kind='bar', rot=0)
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-17-ce643011a4ea> in <module>
----> 1 energies.plot(kind='bar', rot=0)
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs)
970 data.columns = label_name
971
--> 972 return plot_backend.plot(data, kind=kind, **kwargs)
973
974 __call__.__doc__ = __doc__
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs)
69 kwargs["ax"] = getattr(ax, "left_ax", ax)
70 plot_obj = PLOT_CLASSES[kind](data, **kwargs)
---> 71 plot_obj.generate()
72 plot_obj.draw()
73 return plot_obj.result
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self)
293 for ax in self.axes:
294 self._post_plot_logic_common(ax, self.data)
--> 295 self._post_plot_logic(ax, self.data)
296
297 def _args_adjust(self):
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _post_plot_logic(self, ax, data)
1526 name = self._get_index_name()
1527
-> 1528 s_edge = self.ax_pos[0] - 0.25 + self.lim_offset
1529 e_edge = self.ax_pos[-1] + 0.25 + self.bar_width + self.lim_offset
1530
IndexError: index 0 is out of bounds for axis 0 with size 0
In [18]: plt.ylabel('Yearly energy yield (W hr)')
Out[18]: Text(0, 0.5, 'Yearly energy yield (W hr)')
Object oriented (Location, Mount, Array, PVSystem, ModelChain)#
The object oriented paradigm uses a model with three main concepts:
System design (modules, inverters etc.) is represented by
PVSystem,Array, andFixedMount/SingleAxisTrackerMountobjects.A particular place on the planet is represented by a
Locationobject.The modeling chain used to calculate power output for a particular system and location is represented by a
ModelChainobject.
This can be a useful paradigm if you prefer to think about the PV system and its location as separate concepts or if you develop your own ModelChain subclasses. It can also be helpful if you make extensive use of Location-specific methods for other calculations.
The following code demonstrates how to use
Location,
PVSystem, and
ModelChain objects to accomplish our
system modeling goal. ModelChain objects provide convenience methods
that can provide default selections for models and can also fill
necessary input with modeled data. For example, no air temperature
or wind speed data is provided in the input weather DataFrame,
so the ModelChain object defaults to 20 C and 0 m/s. Also, no irradiance
transposition model is specified (keyword argument transposition_model for
ModelChain) so the ModelChain defaults to the haydavies model. In this
example, ModelChain infers the DC power model from the module provided
by examining the parameters defined for the module.
In [19]: from pvlib.pvsystem import PVSystem, Array, FixedMount
In [20]: from pvlib.location import Location
In [21]: from pvlib.modelchain import ModelChain
In [22]: energies = {}
In [23]: for location, weather in zip(coordinates, tmys):
....: latitude, longitude, name, altitude, timezone = location
....: location = Location(
....: latitude,
....: longitude,
....: name=name,
....: altitude=altitude,
....: tz=timezone,
....: )
....: mount = FixedMount(surface_tilt=latitude, surface_azimuth=180)
....: array = Array(
....: mount=mount,
....: module_parameters=module,
....: temperature_model_parameters=temperature_model_parameters,
....: )
....: system = PVSystem(arrays=[array], inverter_parameters=inverter)
....: mc = ModelChain(system, location)
....: mc.run_model(weather)
....: annual_energy = mc.results.ac.sum()
....: energies[name] = annual_energy
....:
In [24]: energies = pd.Series(energies)
# based on the parameters specified above, these are in W*hrs
In [25]: print(energies)
Series([], dtype: float64)
In [26]: energies.plot(kind='bar', rot=0)
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-26-ce643011a4ea> in <module>
----> 1 energies.plot(kind='bar', rot=0)
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs)
970 data.columns = label_name
971
--> 972 return plot_backend.plot(data, kind=kind, **kwargs)
973
974 __call__.__doc__ = __doc__
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs)
69 kwargs["ax"] = getattr(ax, "left_ax", ax)
70 plot_obj = PLOT_CLASSES[kind](data, **kwargs)
---> 71 plot_obj.generate()
72 plot_obj.draw()
73 return plot_obj.result
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self)
293 for ax in self.axes:
294 self._post_plot_logic_common(ax, self.data)
--> 295 self._post_plot_logic(ax, self.data)
296
297 def _args_adjust(self):
~/checkouts/readthedocs.org/user_builds/mikofski-pvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _post_plot_logic(self, ax, data)
1526 name = self._get_index_name()
1527
-> 1528 s_edge = self.ax_pos[0] - 0.25 + self.lim_offset
1529 e_edge = self.ax_pos[-1] + 0.25 + self.bar_width + self.lim_offset
1530
IndexError: index 0 is out of bounds for axis 0 with size 0
In [27]: plt.ylabel('Yearly energy yield (W hr)')
Out[27]: Text(0, 0.5, 'Yearly energy yield (W hr)')