Background
Traditionally I've used NREL SAM tool to estimate solar output. I've been experimenting with PVLIB which is great due to the open nature and flexibility, however I can't seem to reconcile the solar production estimates between PVLIB and NREL SAM.
What I've done
I'm modeling a hypothetical solar farm near Gympie QLD. I've gone to the climate.onebuiling website, and downloaded the zip folder / epw file for "AUS_QLD_Gympie.AP.945660_TMYx.2003-2017". I've then used that weather file in NREL's SAM tool using PVwatts, with the following specs;
In NREL SAM i get an annual Energy Yield (AC GWh) of 415.96 GWh p.a.
I then took that same epw file and converted it to a csv, keeping just the columns for ghi, dni, dhi, temp_air & wind_speed (Google Drive link to CSV file). I've used this file as an import into PVLIB. I spec'd a PVLIB system the same specs above, with addition of albedo = 0.2 and max angle = 90 degrees (Code Below).
The result I get in PVLIB is 395.61 GWh.
Problem
The results I got are pretty different. PVLIB = ~395 GWh p.a. vs SAM = ~415 GWH p.a. I expected around a 1-2% difference, but not 5%.
Figures are even worse when I compare to a PVLIB system using clearsky.ineichen (adjusted with linke_turbidity) which yields ~475 GWh p.a.
Help Requested
Anyone know why my results are so different? is there anything I can do to narrow the gap?
PVLIB Code
# **********************************************************
# IMPORT LIBRARIES
# **********************************************************
import pandas as pd
from pvlib.pvsystem import PVSystem
from pvlib import clearsky, atmosphere, solarposition, irradiance
from pvlib.location import Location
from pvlib.tracking import SingleAxisTracker
from pvlib.modelchain import ModelChain
from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS
# **********************************************************
# LOCATION & SOLAR SIZE INPUTS
# **********************************************************
# Lat and Long desired
lat = -26.18
lon = 152.63
# Set Location
tz, altitude, name = 'Australia/Queensland', 10, 'Gympie/QLD'
# Set location details for model
latitude, longitude, = lat, lon
location = Location(latitude, longitude, tz, altitude, name)
# load some module and inverter specifications
module_parameters = {'pdc0': 200000000, 'gamma_pdc': -0.004}
inverter_parameters = {'pdc': 166666666, 'pdc0': 166666666, 'eta_inv_nom': 0.96}
temperature_model_parameters = TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_glass']
# **********************************************************
# ONEBUILDING DATA
# **********************************************************
df = pd.read_csv('weather import.csv')
df['time'] = df['time'].astype('datetime64[ns]')
df.set_index(['time'], inplace=True)
df.index = df.index.tz_localize(tz=tz)
df = df.asfreq(freq='1h')
onebuilding = df
# **********************************************************
# INEICHEN CLEAR SKIES ADJUSTED FOR TURBIDITY
# **********************************************************
# Create PVLib inputs
times = df.index
solpos = solarposition.get_solarposition(times, latitude, longitude)
apparent_zenith = solpos['zenith']
rel_airmass = atmosphere.get_relative_airmass(apparent_zenith)
pressure = atmosphere.alt2pres(altitude)
abs_airmass = atmosphere.get_absolute_airmass(rel_airmass, pressure)
linke_turbidity = clearsky.lookup_linke_turbidity(times, latitude, longitude)
dni_extra = irradiance.get_extra_radiation(times)
ineichen = clearsky.ineichen(apparent_zenith, abs_airmass, linke_turbidity, altitude, dni_extra)
ineichen.to_csv('ineichen.csv')
# **********************************************************
# SELECT WHICH WEATHER DATA TO USE (ineichen v onebuilding)
# **********************************************************
# Select which version we wish to use (onebuilding, ineichen)
selected_irrad = onebuilding
print(selected_irrad)
# Create Weather File
weather = pd.DataFrame(data={'ghi': selected_irrad.ghi, 'dni': selected_irrad.dni,
'dhi': selected_irrad.dhi, 'temp_air': df['temp_air'],
'wind_speed': df['wind_speed']})
# **********************************************************
# CREATE PV SYSTEM AND PV MODEL CHAIN
# **********************************************************
# Define the specs for the PV System (fixed system)
f_system = PVSystem(
surface_tilt=abs(lat),
surface_azimuth=0,
albedo=0.2,
module='pvwatts_dc',
inverter='pvwatts_ac',
module_parameters=module_parameters,
inverter_parameters=inverter_parameters,
racking_model='open_rack_glass_glass',
name='fixed',
temperature_model_parameters=temperature_model_parameters
)
# Define the specs for the PV System (1 axis tracking system)
t_system = SingleAxisTracker(
axis_tilt=0,
axis_azimuth=0,
max_angle=90,
backtrack=True,
module='pvwatts_dc',
inverter='pvwatts_ac',
module_parameters=module_parameters,
inverter_parameters=inverter_parameters,
name='tracking',
gcr=.40,
)
# build model chain
mc = ModelChain(
system=t_system,
location=location,
name='pvwatts',
dc_model='pvwatts',
ac_model='pvwatts',
losses_model='pvwatts',
aoi_model='physical',
spectral_model='no_loss',
temperature_model='sapm')
# run model chain
mc.run_model(weather=weather)
print(mc.ac.sum())
Hard to say exactly why the annual energy is different without a detailed comparison of intermediate results. One contributing factor appears to be the transposition model (GHI, DHI and DNI to plane-of-array): pvlib ModelChain defaults to the Hay/Davies model, and I believe SAM defaults to the Perez 1990 model. The two models will differ by a few percent in annual plane-of-array irradiance, which varies with the relative levels of diffuse and direct irradiance; see Lave et al. Figure 6.
You can select the Perez 1990 model in pvlib by adding transposition_model = 'perez',
to the mc
instance. I expect that will narrow the difference between pvlib and SAM results, and am interested in what you find.
Calculations using a TMY weather file won't give the same result as a calculation using irradiance from a clear sky model, since TMY is assembled from historical weather records and so includes cloudy periods.