Source code for solarforecastarbiter.pvmodel

"""
Calculate AC power and modeling intermediates from system metadata,
times, and weather data.

Steps are:

1. Calculate solar position using solar_position
2. If not already known, calculate 3 irradiance components from measured
   GHI using irradiance_components or modeled clear sky using clearsky.
3. calculate_poa_effective
4. calculate_power

Steps 3 and 4 are bundled in :py:func:`irradiance_to_power`
"""

from functools import partial

import numpy as np
import pvlib

from solarforecastarbiter import datamodel


[docs]def calculate_solar_position(latitude, longitude, elevation, times): """ Calculates solar position using pvlib's implementation of NREL SPA. Parameters ---------- latitude : float longitude : float elevation : float times : pd.DatetimeIndex Returns ------- solar_position : pd.DataFrame The DataFrame will have the following columns: apparent_zenith (degrees), zenith (degrees), apparent_elevation (degrees), elevation (degrees), azimuth (degrees), equation_of_time (minutes). """ solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude, altitude=elevation, method='nrel_numpy') return solpos
[docs]def complete_irradiance_components(ghi, zenith): """ Uses the Erbs model to calculate DNI and DHI from GHI. Parameters ---------- ghi : pd.Series zenith : pd.Series Solar zenith (not-refraction corrected) Returns ------- dni : pd.Series, dhi : pd.Series """ dni_dhi = pvlib.irradiance.erbs(ghi, zenith, ghi.index) return dni_dhi['dni'], dni_dhi['dhi']
[docs]def calculate_clearsky(latitude, longitude, elevation, apparent_zenith): """ Calculates clear sky irradiance using the Ineichen model and the SoDa climatological turbidity data set. Parameters ---------- latitude : float longitude : float elevation : float apparent_zenith : pd.Series Solar apparent zenith Returns ------- cs : pd.DataFrame Columns are ghi, dni, dhi. """ airmass = pvlib.atmosphere.get_relative_airmass(apparent_zenith) pressure = pvlib.atmosphere.alt2pres(elevation) am_abs = pvlib.atmosphere.get_absolute_airmass(airmass, pressure) tl = pvlib.clearsky.lookup_linke_turbidity(apparent_zenith.index, latitude, longitude) dni_extra = pvlib.irradiance.get_extra_radiation(apparent_zenith.index) cs = pvlib.clearsky.ineichen(apparent_zenith, am_abs, tl, dni_extra=dni_extra, altitude=elevation) return cs
[docs]def aoi_func_factory(modeling_parameters): """ Create a function to calculate AOI, surface tilt, and surface azimuth from system modeling_parameters. Parameters ---------- modeling_parameters : datamodel.FixedTiltModelingParameters or datamodel.SingleAxisModelingParameters Returns ------- function Function that accepts two arguments (apparent_zenith, azimuth) and returns three series (surface_tilt, surface_azimuth, aoi) Raises ------ TypeError if modeling_parameters is invalid. """ if isinstance(modeling_parameters, datamodel.FixedTiltModelingParameters): return partial( aoi_fixed, modeling_parameters.surface_tilt, modeling_parameters.surface_azimuth ) elif isinstance(modeling_parameters, datamodel.SingleAxisModelingParameters): return partial( aoi_tracking, modeling_parameters.axis_tilt, modeling_parameters.axis_azimuth, modeling_parameters.max_rotation_angle, modeling_parameters.backtrack, modeling_parameters.ground_coverage_ratio ) else: raise TypeError('Invalid modeling_parameters type %s' % type(modeling_parameters))
[docs]def aoi_fixed(surface_tilt, surface_azimuth, apparent_zenith, azimuth): """ Calculate AOI for fixed system, bundle return with tilt, azimuth for consistency with similar tracker function. Parameters ---------- surface_tilt : float surface_azimuth : float apparent_zenith : pd.Series Solar apparent zenith azimuth : pd.Series Solar azimuth Returns ------- surface_tilt : pd.Series, surface_azimuth : pd.Series, aoi : pd.Series """ aoi = pvlib.irradiance.aoi(surface_tilt, surface_azimuth, apparent_zenith, azimuth) return surface_tilt, surface_azimuth, aoi
[docs]def aoi_tracking(axis_tilt, axis_azimuth, max_rotation_angle, backtrack, ground_coverage_ratio, apparent_zenith, azimuth): """ Calculate AOI, surface tilt, and surface azimuth for tracking system. Parameters ---------- axis_tilt : float axis_azimuth : float max_rotation_angle : float backtrack : bool ground_coverage_ratio : float apparent_zenith : pd.Series Solar apparent zenith azimuth : pd.Series Solar azimuth Returns ------- surface_tilt : pd.Series, surface_azimuth : pd.Series, aoi : pd.Series """ tracking = pvlib.tracking.singleaxis( apparent_zenith, azimuth, axis_tilt=axis_tilt, axis_azimuth=axis_azimuth, max_angle=max_rotation_angle, backtrack=backtrack, gcr=ground_coverage_ratio ) surface_tilt = tracking['surface_tilt'] surface_azimuth = tracking['surface_azimuth'] aoi = tracking['aoi'] return surface_tilt, surface_azimuth, aoi
[docs]def calculate_poa_effective_explicit(surface_tilt, surface_azimuth, aoi, apparent_zenith, azimuth, ghi, dni, dhi): """ Calculate effective plane of array irradiance from system metadata, solar position, and irradiance components. Accounts for AOI losses. Parameters ---------- surface_tilt : float or pd.Series surface_azimuth : float or pd.Series aoi : pd.Series apparent_zenith : pd.Series Solar apparent zenith azimuth : pd.Series Solar azimuth ghi : pd.Series dni : pd.Series dhi : pd.Series Returns ------- poa_effective : pd.Series """ dni_extra = pvlib.irradiance.get_extra_radiation(apparent_zenith.index) poa_sky_diffuse = pvlib.irradiance.get_sky_diffuse( surface_tilt, surface_azimuth, apparent_zenith, azimuth, dni, ghi, dhi, dni_extra=dni_extra, model='haydavies') poa_ground_diffuse = pvlib.irradiance.get_ground_diffuse( surface_tilt, ghi, albedo=0.25) aoi_loss = pvlib.iam.physical(aoi) beam_effective = dni * np.cos(np.deg2rad(aoi)) * aoi_loss poa_effective = beam_effective + poa_sky_diffuse + poa_ground_diffuse # aoi, tilt, azi is not defined for tracking systems # when sun is below horizon. replace nan with 0 poa_effective = poa_effective.where(aoi.notna(), other=0.) return poa_effective
[docs]def calculate_poa_effective(aoi_func, apparent_zenith, azimuth, ghi, dni, dhi): """ Calculate effective plane of array irradiance from system metadata, solar position, and irradiance components. Accounts for AOI losses. Parameters ---------- aoi_func : function Function with arguments (apparent_zenith, azimuth) and returns surface_tilt, surface_azimuth, aoi apparent_zenith : pd.Series Solar apparent zenith azimuth : pd.Series Solar azimuth ghi : pd.Series dni : pd.Series dhi : pd.Series Returns ------- poa_effective : pd.Series """ surface_tilt, surface_azimuth, aoi = aoi_func(apparent_zenith, azimuth) poa_effective = calculate_poa_effective_explicit( surface_tilt, surface_azimuth, aoi, apparent_zenith, azimuth, ghi, dni, dhi) return poa_effective
[docs]def calculate_power(dc_capacity, temperature_coefficient, dc_loss_factor, ac_capacity, ac_loss_factor, poa_effective, temp_air=20, wind_speed=1): """ Calcuate AC power from system metadata, plane of array irradiance, and weather data using the PVWatts model. Parameters ---------- dc_capacity : float temperature_coefficient : float Specified in units of %/C to be converted to 1/C dc_loss_factor : float ac_capacity : float ac_loss_factor : float poa_effective : pd.Series temp_air : pd.Series, default 20 wind_speed : pd.Series, default 1 Returns ------- ac_power : pd.Series """ pvtemps = pvlib.temperature.pvsyst_cell(poa_effective, temp_air, wind_speed=wind_speed) dc = pvlib.pvsystem.pvwatts_dc(poa_effective, pvtemps, dc_capacity, temperature_coefficient / 100) dc *= (1 - dc_loss_factor / 100) # set eta values to turn off clipping in pvwatts inverter model ac = pvlib.inverter.pvwatts(dc, dc_capacity, eta_inv_nom=1, eta_inv_ref=1) ac = ac.clip(upper=ac_capacity) ac *= (1 - ac_loss_factor / 100) return ac
[docs]def irradiance_to_power(modeling_parameters, apparent_zenith, azimuth, ghi, dni, dhi, temp_air=20, wind_speed=1): """ Calcuate AC power from system metadata, solar position, and ghi, dni, dhi. Parameters ---------- modeling_parameters : datamodel.FixedTiltModelingParameters or datamodel.SingleAxisModelingParameters apparent_zenith : pd.Series Solar apparent zenith azimuth : pd.Series Solar azimuth ghi : pd.Series dni : pd.Series dhi : pd.Series temp_air : pd.Series, default 20 wind_speed : pd.Series, default 1 Returns ------- ac_power : pd.Series """ aoi_func = aoi_func_factory(modeling_parameters) poa_effective = calculate_poa_effective( aoi_func, apparent_zenith, azimuth, ghi, dni, dhi) ac = calculate_power( modeling_parameters.dc_capacity, modeling_parameters.temperature_coefficient, modeling_parameters.dc_loss_factor, modeling_parameters.ac_capacity, modeling_parameters.ac_loss_factor, poa_effective, temp_air=temp_air, wind_speed=wind_speed) return ac