Forecasting#

Warning

The pvlib.forecast module is deprecated as of version 0.9.1.

Because none of the current pvlib team members are able to continue maintaining it, the functionality in pvlib.forecast is deprecated and will be removed without replacement in a future version. If you are interested in maintaining this functionality, please let us know.

You can fetch forecast data yourself using siphon (see the docs below this warning) and the code from pvlib v0.9.0 as a reference: https://github.com/pvlib/pvlib-python/blob/v0.9.0/pvlib/forecast.py

The Solar Forecast Arbiter Core offers similar (and more robust) forecast processing functionality and may be a suitable replacement for some users.

pvlib python provides a set of functions and classes that make it easy to obtain weather forecast data and convert that data into a PV power forecast. Users can retrieve standardized weather forecast data relevant to PV power modeling from NOAA/NCEP/NWS models including the GFS, NAM, RAP, HRRR, and the NDFD. A PV power forecast can then be obtained using the weather data as inputs to the comprehensive modeling capabilities of pvlib python. Standardized, open source, reference implementations of forecast methods using publicly available data may help advance the state-of-the-art of solar power forecasting.

pvlib python uses Unidata’s Siphon library to simplify access to real-time forecast data hosted on the Unidata THREDDS catalog. Siphon is great for programatic access of THREDDS data, but we also recommend using tools such as Panoply to easily browse the catalog and become more familiar with its contents.

We do not know of a similarly easy way to access archives of forecast data.

This document demonstrates how to use pvlib python to create a PV power forecast using these tools. The forecast and forecast_to_power Jupyter notebooks provide additional example code.

Warning

The forecast module algorithms and features are highly experimental. The API may change, the functionality may be consolidated into an io module, or the module may be separated into its own package.

Note

This documentation is difficult to reliably build on readthedocs. If you do not see images, try building the documentation on your own machine or see the notebooks linked to above.

Accessing Forecast Data#

The Siphon library provides access to, among others, forecasts from the Global Forecast System (GFS), North American Model (NAM), High Resolution Rapid Refresh (HRRR), Rapid Refresh (RAP), and National Digital Forecast Database (NDFD) on a Unidata THREDDS server. Unfortunately, many of these models use different names to describe the same quantity (or a very similar one), and not all variables are present in all models. For example, on the THREDDS server, the GFS has a field named Total_cloud_cover_entire_atmosphere_Mixed_intervals_Average, while the NAM has a field named Total_cloud_cover_entire_atmosphere_single_layer, and a similar field in the HRRR is named Total_cloud_cover_entire_atmosphere.

pvlib python aims to simplify the access of the model fields relevant for solar power forecasts. Model data accessed with pvlib python is returned as a pandas DataFrame with consistent column names: temp_air, wind_speed, total_clouds, low_clouds, mid_clouds, high_clouds, dni, dhi, ghi. To accomplish this, we use an object-oriented framework in which each weather model is represented by a class that inherits from a parent ForecastModel class. The parent ForecastModel class contains the common code for accessing and parsing the data using Siphon, while the child model-specific classes (GFS, HRRR, etc.) contain the code necessary to map and process that specific model’s data to the standardized fields.

The code below demonstrates how simple it is to access and plot forecast data using pvlib python. First, we set up make the basic imports and then set the location and time range data.

In [1]: import pandas as pd

In [2]: import matplotlib.pyplot as plt

In [3]: import datetime

# import pvlib forecast models
In [4]: from pvlib.forecast import GFS, NAM, NDFD, HRRR, RAP

# specify location (Tucson, AZ)
In [5]: latitude, longitude, tz = 32.2, -110.9, 'US/Arizona'

# specify time range.
In [6]: start = pd.Timestamp(datetime.date.today(), tz=tz)

In [7]: end = start + pd.Timedelta(days=7)

In [8]: irrad_vars = ['ghi', 'dni', 'dhi']

Next, we instantiate a GFS model object and get the forecast data from Unidata.

# GFS model, defaults to 0.5 degree resolution
# 0.25 deg available
In [9]: model = GFS()

# retrieve data. returns pandas.DataFrame object
In [10]: raw_data = model.get_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
<ipython-input-10-7f04e38c5a09> in <module>
----> 1 raw_data = model.get_data(latitude, longitude, start, end)

~/checkouts/readthedocs.org/user_builds/pvlib-python/checkouts/stable/pvlib/forecast.py in get_data(self, latitude, longitude, start, end, vert_level, query_variables, close_netcdf_data, **kwargs)
    290         self.query.accept(self.data_format)
    291 
--> 292         self.netcdf_data = self.ncss.get_data(self.query)
    293 
    294         # might be better to go to xarray here so that we can handle

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/ncss.py in get_data(self, query)
    112 
    113         """
--> 114         resp = self.get_query(query)
    115         return response_handlers(resp, self.unit_handler)
    116 

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/http_util.py in get_query(self, query)
    408         """
    409         url = self._base[:-1] if self._base[-1] == '/' else self._base
--> 410         return self.get(url, query)
    411 
    412     def url_path(self, path):

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/http_util.py in get(self, path, params)
    493                                      'Server Error ({1:d}: {2})'.format(resp.request.url,
    494                                                                         resp.status_code,
--> 495                                                                         text))
    496         return resp
    497 

HTTPError: Error accessing https://thredds.ucar.edu/thredds/ncss/grid/grib/NCEP/GFS/Global_0p5deg/Best?var=Wind_speed_gust_surface&var=Medium_cloud_cover_middle_cloud_Mixed_intervals_Average&var=Total_cloud_cover_boundary_layer_cloud_Mixed_intervals_Average&var=Total_cloud_cover_entire_atmosphere_Mixed_intervals_Average&var=Low_cloud_cover_low_cloud_Mixed_intervals_Average&var=High_cloud_cover_high_cloud_Mixed_intervals_Average&var=Downward_Short-Wave_Radiation_Flux_surface_Mixed_intervals_Average&var=v-component_of_wind_isobaric&var=u-component_of_wind_isobaric&var=Total_cloud_cover_convective_cloud&var=Temperature_surface&time_start=2022-12-21T07%3A00%3A00%2B00%3A00&time_end=2022-12-28T07%3A00%3A00%2B00%3A00&longitude=-110.9&latitude=32.2&vertCoord=100000&accept=netcdf
Server Error (500: Throwable exception handled : org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.AssertionError: Multiple feature collections cannot be written as a CF dataset
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1086)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:670)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:337)
	at thredds.servlet.filter.RequestBracketingLogMessageFilter.doFilter(RequestBracketingLogMessageFilter.java:50)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at thredds.servlet.filter.RequestQueryFilter.doFilter(RequestQueryFilter.java:90)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at thredds.servlet.filter.HttpHeadFilter.doFilter(HttpHeadFilter.java:47)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
	at org.apache.coyote.ajp.AjpProcessor.service(AjpProcessor.java:433)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:891)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1784)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.AssertionError: Multiple feature collections cannot be written as a CF dataset
	at thredds.server.ncss.view.dsg.station.StationSubsetWriterNetcdf.&lt;init&gt;(StationSubsetWriterNetcdf.java:43)
	at thredds.server.ncss.view.dsg.DsgSubsetWriterFactory.newStationInstance(DsgSubsetWriterFactory.java:87)
	at thredds.server.ncss.view.dsg.DsgSubsetWriterFactory.newInstance(DsgSubsetWriterFactory.java:42)
	at thredds.server.ncss.controller.NcssGridController.handleRequestGridAsPoint(NcssGridController.java:202)
	at thredds.server.ncss.controller.NcssGridController.handleRequest(NcssGridController.java:98)
	at jdk.internal.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)
	... 44 more
)

In [11]: print(raw_data.head())
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-11-634079c84f2d> in <module>
----> 1 print(raw_data.head())

NameError: name 'raw_data' is not defined

It will be useful to process this data before using it with pvlib. For example, the column names are non-standard, the temperature is in Kelvin, the wind speed is broken into east/west and north/south components, and most importantly, most of the irradiance data is missing. The forecast module provides a number of methods to fix these problems.

In [12]: data = raw_data
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-12-42913fd6f823> in <module>
----> 1 data = raw_data

NameError: name 'raw_data' is not defined

# rename the columns according the key/value pairs in model.variables.
In [13]: data = model.rename(data)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-13-866842c1d864> in <module>
----> 1 data = model.rename(data)

NameError: name 'data' is not defined

# convert temperature
In [14]: data['temp_air'] = model.kelvin_to_celsius(data['temp_air'])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-14-a99ee6715c3b> in <module>
----> 1 data['temp_air'] = model.kelvin_to_celsius(data['temp_air'])

NameError: name 'data' is not defined

# convert wind components to wind speed
In [15]: data['wind_speed'] = model.uv_to_speed(data)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-15-8d56658f3ef3> in <module>
----> 1 data['wind_speed'] = model.uv_to_speed(data)

NameError: name 'data' is not defined

# calculate irradiance estimates from cloud cover.
# uses a cloud_cover to ghi to dni model or a
# uses a cloud cover to transmittance to irradiance model.
# this step is discussed in more detail in the next section
In [16]: irrad_data = model.cloud_cover_to_irradiance(data['total_clouds'])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-16-e2c5ef73f145> in <module>
----> 1 irrad_data = model.cloud_cover_to_irradiance(data['total_clouds'])

NameError: name 'data' is not defined

In [17]: data = data.join(irrad_data, how='outer')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-17-ac2b0be819f0> in <module>
----> 1 data = data.join(irrad_data, how='outer')

NameError: name 'data' is not defined

# keep only the final data
In [18]: data = data[model.output_variables]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-18-326034b3c0c7> in <module>
----> 1 data = data[model.output_variables]

NameError: name 'data' is not defined

In [19]: print(data.head())
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-19-b952a40701c3> in <module>
----> 1 print(data.head())

NameError: name 'data' is not defined

Much better.

The GFS class’s process_data() method combines these steps in a single function. In fact, each forecast model class implements its own process_data method since the data from each weather model is slightly different. The process_data functions are designed to be explicit about how the data is being processed, and users are strongly encouraged to read the source code of these methods.

In [20]: data = model.process_data(raw_data)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-20-3e2b66d76339> in <module>
----> 1 data = model.process_data(raw_data)

NameError: name 'raw_data' is not defined

In [21]: print(data.head())
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-21-b952a40701c3> in <module>
----> 1 print(data.head())

NameError: name 'data' is not defined

Users can easily implement their own process_data methods on inherited classes or implement similar stand-alone functions.

The forecast model classes also implement a get_processed_data() method that combines the get_data() and process_data() calls.

In [22]: data = model.get_processed_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
<ipython-input-22-0acd31bc63ff> in <module>
----> 1 data = model.get_processed_data(latitude, longitude, start, end)

~/checkouts/readthedocs.org/user_builds/pvlib-python/checkouts/stable/pvlib/forecast.py in get_processed_data(self, *args, **kwargs)
    337             Processed forecast data
    338         """
--> 339         return self.process_data(self.get_data(*args, **kwargs), **kwargs)
    340 
    341     def rename(self, data, variables=None):

~/checkouts/readthedocs.org/user_builds/pvlib-python/checkouts/stable/pvlib/forecast.py in get_data(self, latitude, longitude, start, end, vert_level, query_variables, close_netcdf_data, **kwargs)
    290         self.query.accept(self.data_format)
    291 
--> 292         self.netcdf_data = self.ncss.get_data(self.query)
    293 
    294         # might be better to go to xarray here so that we can handle

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/ncss.py in get_data(self, query)
    112 
    113         """
--> 114         resp = self.get_query(query)
    115         return response_handlers(resp, self.unit_handler)
    116 

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/http_util.py in get_query(self, query)
    408         """
    409         url = self._base[:-1] if self._base[-1] == '/' else self._base
--> 410         return self.get(url, query)
    411 
    412     def url_path(self, path):

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/http_util.py in get(self, path, params)
    493                                      'Server Error ({1:d}: {2})'.format(resp.request.url,
    494                                                                         resp.status_code,
--> 495                                                                         text))
    496         return resp
    497 

HTTPError: Error accessing https://thredds.ucar.edu/thredds/ncss/grid/grib/NCEP/GFS/Global_0p5deg/Best?var=Medium_cloud_cover_middle_cloud_Mixed_intervals_Average&var=u-component_of_wind_isobaric&var=Temperature_surface&var=Wind_speed_gust_surface&var=Total_cloud_cover_boundary_layer_cloud_Mixed_intervals_Average&var=Total_cloud_cover_entire_atmosphere_Mixed_intervals_Average&var=Low_cloud_cover_low_cloud_Mixed_intervals_Average&var=High_cloud_cover_high_cloud_Mixed_intervals_Average&var=Downward_Short-Wave_Radiation_Flux_surface_Mixed_intervals_Average&var=v-component_of_wind_isobaric&var=Total_cloud_cover_convective_cloud&time_start=2022-12-21T07%3A00%3A00%2B00%3A00&time_end=2022-12-28T07%3A00%3A00%2B00%3A00&longitude=-110.9&latitude=32.2&vertCoord=100000&accept=netcdf
Server Error (500: Throwable exception handled : org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.AssertionError: Multiple feature collections cannot be written as a CF dataset
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1086)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:670)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:337)
	at thredds.servlet.filter.RequestBracketingLogMessageFilter.doFilter(RequestBracketingLogMessageFilter.java:50)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at thredds.servlet.filter.RequestQueryFilter.doFilter(RequestQueryFilter.java:90)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at thredds.servlet.filter.HttpHeadFilter.doFilter(HttpHeadFilter.java:47)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
	at org.apache.coyote.ajp.AjpProcessor.service(AjpProcessor.java:433)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:891)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1784)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.AssertionError: Multiple feature collections cannot be written as a CF dataset
	at thredds.server.ncss.view.dsg.station.StationSubsetWriterNetcdf.&lt;init&gt;(StationSubsetWriterNetcdf.java:43)
	at thredds.server.ncss.view.dsg.DsgSubsetWriterFactory.newStationInstance(DsgSubsetWriterFactory.java:87)
	at thredds.server.ncss.view.dsg.DsgSubsetWriterFactory.newInstance(DsgSubsetWriterFactory.java:42)
	at thredds.server.ncss.controller.NcssGridController.handleRequestGridAsPoint(NcssGridController.java:202)
	at thredds.server.ncss.controller.NcssGridController.handleRequest(NcssGridController.java:98)
	at jdk.internal.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)
	... 44 more
)

In [23]: print(data.head())
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-23-b952a40701c3> in <module>
----> 1 print(data.head())

NameError: name 'data' is not defined

Cloud cover and radiation#

All of the weather models currently accessible by pvlib include one or more cloud cover forecasts. For example, below we plot the GFS cloud cover forecasts.

# plot cloud cover percentages
In [24]: cloud_vars = ['total_clouds', 'low_clouds',
   ....:               'mid_clouds', 'high_clouds']
   ....: 

In [25]: data[cloud_vars].plot();

In [26]: plt.ylabel('Cloud cover %');

In [27]: plt.xlabel('Forecast Time ({})'.format(tz));

In [28]: plt.title('GFS 0.5 deg forecast for lat={}, lon={}'
   ....:           .format(latitude, longitude));
   ....: 

In [29]: plt.legend();
../_images/gfs_cloud_cover.png

However, many of forecast models do not include radiation components in their output fields, or if they do then the radiation fields suffer from poor solar position or radiative transfer algorithms. It is often more accurate to create empirically derived radiation forecasts from the weather models’ cloud cover forecasts.

pvlib python provides two basic ways to convert cloud cover forecasts to irradiance forecasts. One method assumes a linear relationship between cloud cover and GHI, applies the scaling to a clear sky climatology, and then uses the DISC model to calculate DNI. The second method assumes a linear relationship between cloud cover and atmospheric transmittance, and then uses the Campbell-Norman model to calculate GHI, DNI, and DHI [Cam98]. Campbell-Norman is an approximation of Liu-Jordan [Liu60].

Caveat emptor: these algorithms are not rigorously verified! The purpose of the forecast module is to provide a few exceedingly simple options for users to play with before they develop their own models. We strongly encourage pvlib users first read the source code and second to implement new cloud cover to irradiance algorithms.

The essential parts of the clear sky scaling algorithm are as follows. Clear sky scaling of climatological GHI is also used in Larson et. al. [Lar16].

solpos = location.get_solarposition(cloud_cover.index)
cs = location.get_clearsky(cloud_cover.index, model='ineichen')
# offset and cloud cover in decimal units here
# larson et. al. use offset = 0.35
ghi = (offset + (1 - offset) * (1 - cloud_cover)) * ghi_clear
dni = disc(ghi, solpos['zenith'], cloud_cover.index)['dni']
dhi = ghi - dni * np.cos(np.radians(solpos['zenith']))

The figure below shows the result of the total cloud cover to irradiance conversion using the clear sky scaling algorithm.

# plot irradiance data
In [30]: data = model.rename(raw_data)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-30-eb2d672f9ae8> in <module>
----> 1 data = model.rename(raw_data)

NameError: name 'raw_data' is not defined

In [31]: irrads = model.cloud_cover_to_irradiance(data['total_clouds'], how='clearsky_scaling')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-31-a0ec160ac5de> in <module>
----> 1 irrads = model.cloud_cover_to_irradiance(data['total_clouds'], how='clearsky_scaling')

NameError: name 'data' is not defined

In [32]: irrads.plot();

In [33]: plt.ylabel('Irradiance ($W/m^2$)');

In [34]: plt.xlabel('Forecast Time ({})'.format(tz));

In [35]: plt.title('GFS 0.5 deg forecast for lat={}, lon={} using "clearsky_scaling"'
   ....:           .format(latitude, longitude));
   ....: 

In [36]: plt.legend();
../_images/gfs_irrad_cs.png

The essential parts of the Campbell-Norman cloud cover to irradiance algorithm are as follows.

# cloud cover in percentage units here
transmittance = ((100.0 - cloud_cover) / 100.0) * 0.75
# irrads is a DataFrame containing ghi, dni, dhi
irrads = campbell_norman(apparent_zenith, transmittance)

The figure below shows the result of the Campbell-Norman total cloud cover to irradiance conversion.

# plot irradiance data
In [37]: irrads = model.cloud_cover_to_irradiance(data['total_clouds'], how='campbell_norman')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-37-bcb5c732b78b> in <module>
----> 1 irrads = model.cloud_cover_to_irradiance(data['total_clouds'], how='campbell_norman')

NameError: name 'data' is not defined

In [38]: irrads.plot();

In [39]: plt.ylabel('Irradiance ($W/m^2$)');

In [40]: plt.xlabel('Forecast Time ({})'.format(tz));

In [41]: plt.title('GFS 0.5 deg forecast for lat={}, lon={} using "campbell_norman"'
   ....:           .format(latitude, longitude));
   ....: 

In [42]: plt.legend();
../_images/gfs_irrad_lj.png

Most weather model output has a fairly coarse time resolution, at least an hour. The irradiance forecasts have the same time resolution as the weather data. However, it is straightforward to interpolate the cloud cover forecasts onto a higher resolution time domain, and then recalculate the irradiance.

In [43]: resampled_data = data.resample('5min').interpolate()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-43-880ab2b0dc0f> in <module>
----> 1 resampled_data = data.resample('5min').interpolate()

NameError: name 'data' is not defined

In [44]: resampled_irrads = model.cloud_cover_to_irradiance(resampled_data['total_clouds'], how='clearsky_scaling')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-44-6ff5301ab621> in <module>
----> 1 resampled_irrads = model.cloud_cover_to_irradiance(resampled_data['total_clouds'], how='clearsky_scaling')

NameError: name 'resampled_data' is not defined

In [45]: resampled_irrads.plot();

In [46]: plt.ylabel('Irradiance ($W/m^2$)');

In [47]: plt.xlabel('Forecast Time ({})'.format(tz));

In [48]: plt.title('GFS 0.5 deg forecast for lat={}, lon={} resampled'
   ....:           .format(latitude, longitude));
   ....: 

In [49]: plt.legend();
../_images/gfs_irrad_high_res.png

Users may then recombine resampled_irrads and resampled_data using slicing pandas.concat() or pandas.DataFrame.join().

We reiterate that the open source code enables users to customize the model processing to their liking.

Lar16

Larson et. al. “Day-ahead forecasting of solar power output from photovoltaic plants in the American Southwest” Renewable Energy 91, 11-20 (2016).

Cam98

Campbell, G. S., J. M. Norman (1998) An Introduction to Environmental Biophysics. 2nd Ed. New York: Springer.

Liu60

B. Y. Liu and R. C. Jordan, The interrelationship and characteristic distribution of direct, diffuse, and total solar radiation, Solar Energy 4, 1 (1960).

Weather Models#

Next, we provide a brief description of the weather models available to pvlib users. Note that the figures are generated when this documentation is compiled so they will vary over time.

GFS#

The Global Forecast System (GFS) is the US model that provides forecasts for the entire globe. The GFS is updated every 6 hours. The GFS is run at two resolutions, 0.25 deg and 0.5 deg, and is available with 3 hour time resolution. Forecasts from GFS model were shown above. Use the GFS, among others, if you want forecasts for 1-7 days or if you want forecasts for anywhere on Earth.

HRRR#

The High Resolution Rapid Refresh (HRRR) model is perhaps the most accurate model, however, it is only available for ~15 hours. It is updated every hour and runs at 3 km resolution. The HRRR excels in severe weather situations. See the NOAA ESRL HRRR page for more information. Use the HRRR, among others, if you want forecasts for less than 24 hours. The HRRR model covers the continental United States.

In [50]: model = HRRR()

In [51]: data = model.get_processed_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
<ipython-input-51-0acd31bc63ff> in <module>
----> 1 data = model.get_processed_data(latitude, longitude, start, end)

~/checkouts/readthedocs.org/user_builds/pvlib-python/checkouts/stable/pvlib/forecast.py in get_processed_data(self, *args, **kwargs)
    337             Processed forecast data
    338         """
--> 339         return self.process_data(self.get_data(*args, **kwargs), **kwargs)
    340 
    341     def rename(self, data, variables=None):

~/checkouts/readthedocs.org/user_builds/pvlib-python/checkouts/stable/pvlib/forecast.py in get_data(self, latitude, longitude, start, end, vert_level, query_variables, close_netcdf_data, **kwargs)
    290         self.query.accept(self.data_format)
    291 
--> 292         self.netcdf_data = self.ncss.get_data(self.query)
    293 
    294         # might be better to go to xarray here so that we can handle

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/ncss.py in get_data(self, query)
    112 
    113         """
--> 114         resp = self.get_query(query)
    115         return response_handlers(resp, self.unit_handler)
    116 

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/http_util.py in get_query(self, query)
    408         """
    409         url = self._base[:-1] if self._base[-1] == '/' else self._base
--> 410         return self.get(url, query)
    411 
    412     def url_path(self, path):

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/http_util.py in get(self, path, params)
    493                                      'Server Error ({1:d}: {2})'.format(resp.request.url,
    494                                                                         resp.status_code,
--> 495                                                                         text))
    496         return resp
    497 

HTTPError: Error accessing https://thredds.ucar.edu/thredds/ncss/grid/grib/NCEP/HRRR/CONUS_2p5km/Best?var=Wind_speed_gust_surface&var=High_cloud_cover_high_cloud&var=v-component_of_wind_height_above_ground&var=u-component_of_wind_height_above_ground&var=Total_cloud_cover_entire_atmosphere&var=Low_cloud_cover_low_cloud&var=Pressure_surface&var=Temperature_height_above_ground&var=Medium_cloud_cover_middle_cloud&time_start=2022-12-21T07%3A00%3A00%2B00%3A00&time_end=2022-12-28T07%3A00%3A00%2B00%3A00&longitude=-110.9&latitude=32.2&accept=netcdf
Server Error (400: Request contains variables with different feature types, which is not supported for writing in NETCDF3 format. Select a different format or choose variables that either uniformly have do not have a vertical dimension.)

In [52]: data[irrad_vars].plot();

In [53]: plt.ylabel('Irradiance ($W/m^2$)');

In [54]: plt.xlabel('Forecast Time ({})'.format(tz));

In [55]: plt.title('HRRR 3 km forecast for lat={}, lon={}'
   ....:           .format(latitude, longitude));
   ....: 

In [56]: plt.legend();
../_images/hrrr_irrad.png

RAP#

The Rapid Refresh (RAP) model is the parent model for the HRRR. It is updated every hour and runs at 40, 20, and 13 km resolutions. Only the 20 and 40 km resolutions are currently available in pvlib. It is also excels in severe weather situations. See the NOAA ESRL HRRR page for more information. Use the RAP, among others, if you want forecasts for less than 24 hours. The RAP model covers most of North America.

In [57]: model = RAP()

In [58]: data = model.get_processed_data(latitude, longitude, start, end)

In [59]: data[irrad_vars].plot();

In [60]: plt.ylabel('Irradiance ($W/m^2$)');

In [61]: plt.xlabel('Forecast Time ({})'.format(tz));

In [62]: plt.title('RAP 13 km forecast for lat={}, lon={}'
   ....:           .format(latitude, longitude));
   ....: 

In [63]: plt.legend();
../_images/rap_irrad.png

NAM#

The North American Mesoscale model covers, not surprisingly, North America. It is updated every 6 hours. pvlib provides access to 20 km resolution NAM data with a time horizon of up to 4 days.

In [64]: model = NAM()

In [65]: data = model.get_processed_data(latitude, longitude, start, end)

In [66]: data[irrad_vars].plot();

In [67]: plt.ylabel('Irradiance ($W/m^2$)');

In [68]: plt.xlabel('Forecast Time ({})'.format(tz));

In [69]: plt.title('NAM 20 km forecast for lat={}, lon={}'
   ....:           .format(latitude, longitude));
   ....: 

In [70]: plt.legend();
../_images/nam_irrad.png

NDFD#

The National Digital Forecast Database is not a model, but rather a collection of forecasts made by National Weather Service offices across the country. It is updated every 6 hours. Use the NDFD, among others, for forecasts at all time horizons. The NDFD is available for the United States.

In [71]: model = NDFD()

In [72]: data = model.get_processed_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
<ipython-input-72-0acd31bc63ff> in <module>
----> 1 data = model.get_processed_data(latitude, longitude, start, end)

~/checkouts/readthedocs.org/user_builds/pvlib-python/checkouts/stable/pvlib/forecast.py in get_processed_data(self, *args, **kwargs)
    337             Processed forecast data
    338         """
--> 339         return self.process_data(self.get_data(*args, **kwargs), **kwargs)
    340 
    341     def rename(self, data, variables=None):

~/checkouts/readthedocs.org/user_builds/pvlib-python/checkouts/stable/pvlib/forecast.py in get_data(self, latitude, longitude, start, end, vert_level, query_variables, close_netcdf_data, **kwargs)
    290         self.query.accept(self.data_format)
    291 
--> 292         self.netcdf_data = self.ncss.get_data(self.query)
    293 
    294         # might be better to go to xarray here so that we can handle

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/ncss.py in get_data(self, query)
    112 
    113         """
--> 114         resp = self.get_query(query)
    115         return response_handlers(resp, self.unit_handler)
    116 

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/http_util.py in get_query(self, query)
    408         """
    409         url = self._base[:-1] if self._base[-1] == '/' else self._base
--> 410         return self.get(url, query)
    411 
    412     def url_path(self, path):

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/http_util.py in get(self, path, params)
    493                                      'Server Error ({1:d}: {2})'.format(resp.request.url,
    494                                                                         resp.status_code,
--> 495                                                                         text))
    496         return resp
    497 

HTTPError: Error accessing https://thredds.ucar.edu/thredds/ncss/grid/grib/NCEP/NDFD/NWS/CONUS/CONDUIT/Best?var=Total_cloud_cover_surface&var=Wind_speed_height_above_ground&var=Temperature_height_above_ground&time_start=2022-12-21T07%3A00%3A00%2B00%3A00&time_end=2022-12-28T07%3A00%3A00%2B00%3A00&longitude=-110.9&latitude=32.2&accept=netcdf
Server Error (500: Throwable exception handled : org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.AssertionError: Multiple feature collections cannot be written as a CF dataset
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1086)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:670)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:337)
	at thredds.servlet.filter.RequestBracketingLogMessageFilter.doFilter(RequestBracketingLogMessageFilter.java:50)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at thredds.servlet.filter.RequestQueryFilter.doFilter(RequestQueryFilter.java:90)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at thredds.servlet.filter.HttpHeadFilter.doFilter(HttpHeadFilter.java:47)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
	at org.apache.coyote.ajp.AjpProcessor.service(AjpProcessor.java:433)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:891)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1784)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.AssertionError: Multiple feature collections cannot be written as a CF dataset
	at thredds.server.ncss.view.dsg.station.StationSubsetWriterNetcdf.&lt;init&gt;(StationSubsetWriterNetcdf.java:43)
	at thredds.server.ncss.view.dsg.DsgSubsetWriterFactory.newStationInstance(DsgSubsetWriterFactory.java:87)
	at thredds.server.ncss.view.dsg.DsgSubsetWriterFactory.newInstance(DsgSubsetWriterFactory.java:42)
	at thredds.server.ncss.controller.NcssGridController.handleRequestGridAsPoint(NcssGridController.java:202)
	at thredds.server.ncss.controller.NcssGridController.handleRequest(NcssGridController.java:98)
	at jdk.internal.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)
	... 44 more
)

In [73]: data[irrad_vars].plot();

In [74]: plt.ylabel('Irradiance ($W/m^2$)');

In [75]: plt.xlabel('Forecast Time ({})'.format(tz));

In [76]: plt.title('NDFD forecast for lat={}, lon={}'
   ....:            .format(latitude, longitude));
   ....:  plt.legend();
   ....:  plt.close();
   ....: 
  File "<ipython-input-76-1c7cdda0217f>", line 3
    plt.legend();
    ^
IndentationError: unexpected indent

PV Power Forecast#

Finally, we demonstrate the application of the weather forecast data to a PV power forecast. Please see the remainder of the pvlib documentation for details.

In [77]: from pvlib.pvsystem import PVSystem, retrieve_sam

In [78]: from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS

In [79]: from pvlib.tracking import SingleAxisTracker

In [80]: from pvlib.modelchain import ModelChain

In [81]: sandia_modules = retrieve_sam('sandiamod')

In [82]: cec_inverters = retrieve_sam('cecinverter')

In [83]: module = sandia_modules['Canadian_Solar_CS5P_220M___2009_']

In [84]: inverter = cec_inverters['SMA_America__SC630CP_US__with_ABB_EcoDry_Ultra_transformer_']

In [85]: temperature_model_parameters = TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_glass']

# model a big tracker for more fun
In [86]: system = SingleAxisTracker(module_parameters=module, inverter_parameters=inverter, temperature_model_parameters=temperature_model_parameters, modules_per_string=15, strings_per_inverter=300)

# fx is a common abbreviation for forecast
In [87]: fx_model = GFS()

In [88]: fx_data = fx_model.get_processed_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
<ipython-input-88-16464ad503f1> in <module>
----> 1 fx_data = fx_model.get_processed_data(latitude, longitude, start, end)

~/checkouts/readthedocs.org/user_builds/pvlib-python/checkouts/stable/pvlib/forecast.py in get_processed_data(self, *args, **kwargs)
    337             Processed forecast data
    338         """
--> 339         return self.process_data(self.get_data(*args, **kwargs), **kwargs)
    340 
    341     def rename(self, data, variables=None):

~/checkouts/readthedocs.org/user_builds/pvlib-python/checkouts/stable/pvlib/forecast.py in get_data(self, latitude, longitude, start, end, vert_level, query_variables, close_netcdf_data, **kwargs)
    290         self.query.accept(self.data_format)
    291 
--> 292         self.netcdf_data = self.ncss.get_data(self.query)
    293 
    294         # might be better to go to xarray here so that we can handle

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/ncss.py in get_data(self, query)
    112 
    113         """
--> 114         resp = self.get_query(query)
    115         return response_handlers(resp, self.unit_handler)
    116 

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/http_util.py in get_query(self, query)
    408         """
    409         url = self._base[:-1] if self._base[-1] == '/' else self._base
--> 410         return self.get(url, query)
    411 
    412     def url_path(self, path):

~/checkouts/readthedocs.org/user_builds/pvlib-python/envs/stable/lib/python3.7/site-packages/siphon/http_util.py in get(self, path, params)
    493                                      'Server Error ({1:d}: {2})'.format(resp.request.url,
    494                                                                         resp.status_code,
--> 495                                                                         text))
    496         return resp
    497 

HTTPError: Error accessing https://thredds.ucar.edu/thredds/ncss/grid/grib/NCEP/GFS/Global_0p5deg/Best?var=Wind_speed_gust_surface&var=Medium_cloud_cover_middle_cloud_Mixed_intervals_Average&var=Total_cloud_cover_boundary_layer_cloud_Mixed_intervals_Average&var=Total_cloud_cover_entire_atmosphere_Mixed_intervals_Average&var=Low_cloud_cover_low_cloud_Mixed_intervals_Average&var=High_cloud_cover_high_cloud_Mixed_intervals_Average&var=Downward_Short-Wave_Radiation_Flux_surface_Mixed_intervals_Average&var=v-component_of_wind_isobaric&var=u-component_of_wind_isobaric&var=Total_cloud_cover_convective_cloud&var=Temperature_surface&time_start=2022-12-21T07%3A00%3A00%2B00%3A00&time_end=2022-12-28T07%3A00%3A00%2B00%3A00&longitude=-110.9&latitude=32.2&vertCoord=100000&accept=netcdf
Server Error (500: Throwable exception handled : org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.AssertionError: Multiple feature collections cannot be written as a CF dataset
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1086)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:670)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:337)
	at thredds.servlet.filter.RequestBracketingLogMessageFilter.doFilter(RequestBracketingLogMessageFilter.java:50)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at thredds.servlet.filter.RequestQueryFilter.doFilter(RequestQueryFilter.java:90)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at thredds.servlet.filter.HttpHeadFilter.doFilter(HttpHeadFilter.java:47)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
	at org.apache.coyote.ajp.AjpProcessor.service(AjpProcessor.java:433)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:891)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1784)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.AssertionError: Multiple feature collections cannot be written as a CF dataset
	at thredds.server.ncss.view.dsg.station.StationSubsetWriterNetcdf.&lt;init&gt;(StationSubsetWriterNetcdf.java:43)
	at thredds.server.ncss.view.dsg.DsgSubsetWriterFactory.newStationInstance(DsgSubsetWriterFactory.java:87)
	at thredds.server.ncss.view.dsg.DsgSubsetWriterFactory.newInstance(DsgSubsetWriterFactory.java:42)
	at thredds.server.ncss.controller.NcssGridController.handleRequestGridAsPoint(NcssGridController.java:202)
	at thredds.server.ncss.controller.NcssGridController.handleRequest(NcssGridController.java:98)
	at jdk.internal.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)
	... 44 more
)

# use a ModelChain object to calculate modeling intermediates
In [89]: mc = ModelChain(system, fx_model.location)

# extract relevant data for model chain
In [90]: mc.run_model(fx_data);

Now we plot a couple of modeling intermediates and the forecast power. Here’s the forecast plane of array irradiance…

In [91]: mc.results.total_irrad.plot();

In [92]: plt.ylabel('Plane of array irradiance ($W/m^2$)');

In [93]: plt.legend(loc='best');
../_images/poa_irrad.png

…the cell and module temperature…

In [94]: mc.results.cell_temperature.plot();

In [95]: plt.ylabel('Cell Temperature (C)');
../_images/pv_temps.png

…and finally AC power…

In [96]: mc.results.ac.fillna(0).plot();

In [97]: plt.ylim(0, None);

In [98]: plt.ylabel('AC Power (W)');
../_images/ac_power.png