A comprehensive toolbox for modeling and simulating photovoltaic energy systems.
—
Calculate clear sky irradiance using multiple models including Ineichen, Bird, Haurwitz, and simplified SOLIS. Clear sky models provide baseline irradiance estimates for system modeling, clear sky detection, and performance analysis.
The most commonly used clear sky model, recommended by the IEA Task 12 and WMO.
def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
altitude=0, dni_extra=1366.1):
"""
Calculate clear sky irradiance using Ineichen model.
Parameters:
- apparent_zenith: numeric, apparent solar zenith angle in degrees
- airmass_absolute: numeric, absolute airmass
- linke_turbidity: numeric, Linke turbidity factor
- altitude: numeric, altitude above sea level in meters
- dni_extra: numeric, extraterrestrial direct normal irradiance (W/m^2)
Returns:
OrderedDict with keys:
- ghi: global horizontal irradiance (W/m^2)
- dni: direct normal irradiance (W/m^2)
- dhi: diffuse horizontal irradiance (W/m^2)
"""Physically-based clear sky model with detailed atmospheric parameter inputs.
def bird(zenith, airmass_relative, aod380, aod500, precipitable_water,
ozone=0.3, pressure=101325.0, dni_extra=1366.1,
asymmetry=0.85, albedo=0.2):
"""
Calculate clear sky irradiance using Bird model.
Parameters:
- zenith: numeric, solar zenith angle in degrees
- airmass_relative: numeric, relative airmass
- aod380: numeric, aerosol optical depth at 380 nm
- aod500: numeric, aerosol optical depth at 500 nm
- precipitable_water: numeric, precipitable water in cm
- ozone: numeric, ozone in atm-cm
- pressure: numeric, atmospheric pressure in pascals
- dni_extra: numeric, extraterrestrial direct normal irradiance (W/m^2)
- asymmetry: numeric, aerosol asymmetry parameter
- albedo: numeric, ground albedo
Returns:
OrderedDict with keys:
- ghi: global horizontal irradiance (W/m^2)
- dni: direct normal irradiance (W/m^2)
- dhi: diffuse horizontal irradiance (W/m^2)
"""Simple empirical clear sky model requiring only solar zenith angle.
def haurwitz(apparent_zenith):
"""
Calculate clear sky GHI using Haurwitz model.
Parameters:
- apparent_zenith: numeric, apparent solar zenith angle in degrees
Returns:
numeric, global horizontal irradiance (W/m^2)
"""Simplified version of the SOLIS clear sky model.
def simplified_solis(apparent_elevation, aod700=0.1, precipitable_water=1.0,
pressure=101325.0, dni_extra=1366.1):
"""
Calculate clear sky irradiance using simplified SOLIS model.
Parameters:
- apparent_elevation: numeric, apparent solar elevation angle in degrees
- aod700: numeric, aerosol optical depth at 700 nm
- precipitable_water: numeric, precipitable water in cm
- pressure: numeric, atmospheric pressure in pascals
- dni_extra: numeric, extraterrestrial direct normal irradiance (W/m^2)
Returns:
OrderedDict with keys:
- ghi: global horizontal irradiance (W/m^2)
- dni: direct normal irradiance (W/m^2)
- dhi: diffuse horizontal irradiance (W/m^2)
"""Look up Linke turbidity values from monthly climatology database.
def lookup_linke_turbidity(time, latitude, longitude, filepath=None,
interp_turbidity=True):
"""
Look up Linke turbidity values from climatology database.
Parameters:
- time: pandas.DatetimeIndex, times for lookup
- latitude: float, decimal degrees north
- longitude: float, decimal degrees east
- filepath: str, path to Linke turbidity file
- interp_turbidity: bool, interpolate turbidity values
Returns:
pandas.Series, Linke turbidity values
"""Detect clear sky conditions from measured irradiance data.
def detect_clearsky(measured, clearsky, times=None, infer_limits=False,
window_length=10, mean_diff=75, max_diff=75,
lower_line_length=-5, upper_line_length=10,
var_diff=0.005, slope_dev=8, max_iterations=20,
return_components=False):
"""
Detect clear sky conditions from measured vs. clear sky irradiance.
Parameters:
- measured: pandas.Series or DataFrame, measured irradiance
- clearsky: pandas.Series or DataFrame, clear sky irradiance
- times: pandas.DatetimeIndex, timestamps
- infer_limits: bool, infer detection limits from data
- window_length: int, window length for moving statistics
- mean_diff: numeric, mean difference threshold
- max_diff: numeric, maximum difference threshold
- lower_line_length: numeric, lower line length parameter
- upper_line_length: numeric, upper line length parameter
- var_diff: numeric, variance difference threshold
- slope_dev: numeric, slope deviation threshold
- max_iterations: int, maximum number of iterations
- return_components: bool, return intermediate results
Returns:
pandas.Series or tuple, clear sky mask and optional components
"""import pvlib
from pvlib import clearsky, solarposition, atmosphere
import pandas as pd
# Location and time
lat, lon = 40.0583, -74.4057 # Princeton, NJ
times = pd.date_range('2023-06-21', periods=24, freq='H', tz='US/Eastern')
# Solar position
solar_pos = solarposition.get_solarposition(times, lat, lon)
# Atmospheric parameters
airmass = atmosphere.get_relative_airmass(solar_pos['zenith'])
airmass_abs = atmosphere.get_absolute_airmass(airmass)
# Look up Linke turbidity (or use typical value)
linke_turbidity = 3.0 # Typical value for mid-latitudes
# Calculate clear sky irradiance
clear_sky = clearsky.ineichen(
apparent_zenith=solar_pos['apparent_zenith'],
airmass_absolute=airmass_abs,
linke_turbidity=linke_turbidity
)
print("Time, GHI, DNI, DHI")
for i in range(8, 16): # Show daytime hours
time_str = times[i].strftime('%H:%M')
ghi = clear_sky['ghi'].iloc[i]
dni = clear_sky['dni'].iloc[i]
dhi = clear_sky['dhi'].iloc[i]
print(f"{time_str}, {ghi:.0f}, {dni:.0f}, {dhi:.0f}")import pvlib
from pvlib import clearsky, solarposition, atmosphere
import pandas as pd
# Location and conditions
lat, lon = 35.05, -106.54 # Albuquerque, NM (high altitude, dry)
times = pd.date_range('2023-06-21 12:00', periods=1, tz='US/Mountain')
# Solar position
solar_pos = solarposition.get_solarposition(times, lat, lon, altitude=1619)
zenith = solar_pos['zenith'].iloc[0]
# Atmospheric parameters for high altitude, dry location
airmass_rel = atmosphere.get_relative_airmass(zenith)
aod380 = 0.1 # Low aerosol loading
aod500 = 0.05
precipitable_water = 0.8 # Low water vapor
ozone = 0.25 # Typical ozone
pressure = atmosphere.alt2pres(1619) # Pressure at altitude
# Calculate clear sky irradiance
clear_sky = clearsky.bird(
zenith=zenith,
airmass_relative=airmass_rel,
aod380=aod380,
aod500=aod500,
precipitable_water=precipitable_water,
ozone=ozone,
pressure=pressure
)
print(f"Bird clear sky model results:")
print(f"GHI: {clear_sky['ghi']:.0f} W/m²")
print(f"DNI: {clear_sky['dni']:.0f} W/m²")
print(f"DHI: {clear_sky['dhi']:.0f} W/m²")import pvlib
from pvlib import clearsky, solarposition, atmosphere
import pandas as pd
import matplotlib.pyplot as plt
# Location and time
lat, lon = 40.0583, -74.4057
times = pd.date_range('2023-06-21 06:00', '2023-06-21 18:00',
freq='H', tz='US/Eastern')
# Solar position and atmospheric parameters
solar_pos = solarposition.get_solarposition(times, lat, lon)
airmass_rel = atmosphere.get_relative_airmass(solar_pos['zenith'])
airmass_abs = atmosphere.get_absolute_airmass(airmass_rel)
# Model comparisons
results = {}
# Ineichen model
results['Ineichen'] = clearsky.ineichen(
solar_pos['apparent_zenith'], airmass_abs, linke_turbidity=3.0
)
# Haurwitz model (GHI only)
results['Haurwitz'] = {'ghi': clearsky.haurwitz(solar_pos['apparent_zenith'])}
# Simplified SOLIS model
results['SOLIS'] = clearsky.simplified_solis(
solar_pos['apparent_elevation'],
aod700=0.1,
precipitable_water=1.5
)
# Print comparison at solar noon
noon_idx = 12
print("Clear sky model comparison at solar noon:")
print(f"{'Model':<10} {'GHI':<6} {'DNI':<6} {'DHI':<6}")
for model_name, data in results.items():
ghi = data['ghi'].iloc[noon_idx] if hasattr(data['ghi'], 'iloc') else data['ghi'][noon_idx]
dni = data.get('dni', [0]*len(times))
dhi = data.get('dhi', [0]*len(times))
if hasattr(dni, 'iloc'):
dni_val = dni.iloc[noon_idx]
dhi_val = dhi.iloc[noon_idx]
else:
dni_val = dni[noon_idx]
dhi_val = dhi[noon_idx]
print(f"{model_name:<10} {ghi:<6.0f} {dni_val:<6.0f} {dhi_val:<6.0f}")import pvlib
from pvlib import clearsky, solarposition, atmosphere
import pandas as pd
import numpy as np
# Generate synthetic clear sky and measured data
lat, lon = 40.0583, -74.4057
times = pd.date_range('2023-06-21', periods=24, freq='H', tz='US/Eastern')
# Solar position and clear sky
solar_pos = solarposition.get_solarposition(times, lat, lon)
airmass_abs = atmosphere.get_absolute_airmass(
atmosphere.get_relative_airmass(solar_pos['zenith'])
)
clear_sky = clearsky.ineichen(
solar_pos['apparent_zenith'], airmass_abs, linke_turbidity=3.0
)
# Create synthetic measured data with some cloudy periods
np.random.seed(42)
measured_ghi = clear_sky['ghi'].copy()
# Add some clouds (reduce irradiance) for certain hours
cloudy_hours = [9, 10, 14, 15]
for hour in cloudy_hours:
measured_ghi.iloc[hour] *= 0.3 + 0.4 * np.random.random()
# Detect clear sky periods
clear_mask = clearsky.detect_clearsky(
measured=measured_ghi,
clearsky=clear_sky['ghi'],
times=times
)
print("Clear sky detection results:")
print("Hour, Measured GHI, Clear Sky GHI, Clear Sky?")
for i in range(8, 17): # Daytime hours
hour = times[i].hour
measured = measured_ghi.iloc[i]
clear = clear_sky['ghi'].iloc[i]
is_clear = clear_mask.iloc[i]
print(f"{hour:2d}:00, {measured:6.0f}, {clear:6.0f}, {is_clear}")import pvlib
from pvlib import clearsky
import pandas as pd
# Multiple locations with different turbidity characteristics
locations = [
{'name': 'Rural', 'lat': 45.0, 'lon': -100.0}, # Clean rural
{'name': 'Urban', 'lat': 40.7, 'lon': -74.0}, # Urban pollution
{'name': 'Desert', 'lat': 25.0, 'lon': 55.0}, # Desert dust
]
times = pd.date_range('2023-01-01', '2023-12-31', freq='MS')
print("Monthly Linke turbidity values:")
print(f"{'Month':<10}", end='')
for loc in locations:
print(f"{loc['name']:<8}", end='')
print()
for i, time in enumerate(times):
print(f"{time.strftime('%b'):<10}", end='')
for loc in locations:
try:
lt = clearsky.lookup_linke_turbidity(
pd.DatetimeIndex([time]),
loc['lat'],
loc['lon']
)
print(f"{lt.iloc[0]:<8.2f}", end='')
except:
print(f"{'N/A':<8}", end='')
print()Install with Tessl CLI
npx tessl i tessl/pypi-pvlib@0.13.2