A comprehensive toolbox for modeling and simulating photovoltaic energy systems.
—
DC-to-AC power conversion models for photovoltaic inverters. Includes multiple inverter efficiency models from empirical (Sandia) to physics-based (ADR) to simplified (PVWatts) approaches, enabling accurate AC power prediction across different operating conditions.
Empirical inverter model developed by Sandia National Laboratories for grid-connected PV inverters. Provides detailed efficiency modeling with voltage and power dependencies.
def sandia(v_dc, p_dc, inverter):
"""
Sandia Grid-Connected PV Inverter model.
Parameters:
- v_dc: numeric, DC voltage input to the inverter (V)
- p_dc: numeric, DC power input to the inverter (W)
- inverter: dict, inverter parameters containing:
- Paco: AC power rating (W)
- Pdco: DC power input that results in Paco output at Vdco (W)
- Vdco: DC voltage at which AC power rating is achieved (V)
- Pso: DC power required to start inversion process (W)
- C0: curvature parameter (1/W)
- C1: empirical coefficient for Pdco voltage variation (1/V)
- C2: empirical coefficient for Pso voltage variation (1/V)
- C3: empirical coefficient for C0 voltage variation (1/V)
- Pnt: AC power consumed at night (W)
Returns:
- power_ac: numeric, AC power output (W)
"""
def sandia_multi(v_dc, p_dc, inverter):
"""
Sandia model for inverters with multiple MPPT inputs.
Parameters:
- v_dc: tuple/list/array, DC voltage on each MPPT input (V)
- p_dc: tuple/list/array, DC power on each MPPT input (W)
- inverter: dict, inverter parameters (same as sandia)
Returns:
- power_ac: numeric, AC power output for the inverter (W)
"""Anton Driesse's efficiency model providing detailed inverter characterization based on input voltage and power dependencies with physical basis.
def adr(v_dc, p_dc, inverter, vtol=0.10):
"""
ADR grid-connected inverter efficiency model.
Parameters:
- v_dc: numeric, DC voltage input to the inverter (V)
- p_dc: numeric, DC power input to the inverter (W)
- inverter: dict, inverter parameters containing:
- Pnom: nominal DC power (W)
- Vnom: nominal DC input voltage (V)
- Vmax: maximum DC input voltage (V)
- Vmin: minimum DC input voltage (V)
- Vdcmax: maximum voltage supplied from DC array (V)
- MPPTHi: maximum DC voltage for MPPT range (V)
- MPPTLow: minimum DC voltage for MPPT range (V)
- Pacmax: maximum AC output power (W)
- ADRCoefficients: list of 9 coefficients for loss modeling
- Pnt: AC power consumed at night (W)
- vtol: numeric, voltage tolerance for extrapolation (0-1)
Returns:
- power_ac: numeric, AC power output (W)
"""NREL's simplified PVWatts inverter model used in the popular PVWatts calculator. Provides good accuracy with minimal parameters.
def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637):
"""
NREL's PVWatts inverter model.
Parameters:
- pdc: numeric, DC power input (W)
- pdc0: numeric, DC input limit of the inverter (W)
- eta_inv_nom: numeric, nominal inverter efficiency (default 0.96)
- eta_inv_ref: numeric, reference efficiency (default 0.9637)
Returns:
- power_ac: numeric, AC power output (W)
"""
def pvwatts_multi(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637):
"""
PVWatts model extended for multiple MPP inputs.
Parameters:
- pdc: tuple/list/array, DC power on each MPPT input (W)
- pdc0: numeric, total DC power limit of the inverter (W)
- eta_inv_nom: numeric, nominal inverter efficiency
- eta_inv_ref: numeric, reference efficiency
Returns:
- power_ac: numeric, AC power output (W)
"""Function for fitting Sandia inverter model parameters from measured performance data.
def fit_sandia(ac_power, dc_power, dc_voltage, dc_voltage_level, p_ac_0, p_nt):
"""
Determine parameters for Sandia inverter model from measured data.
Parameters:
- ac_power: array-like, AC power output at each data point (W)
- dc_power: array-like, DC power input at each data point (W)
- dc_voltage: array-like, DC input voltage at each data point (V)
- dc_voltage_level: array-like, voltage level ('Vmin', 'Vnom', 'Vmax')
- p_ac_0: float, rated AC power of the inverter (W)
- p_nt: float, night tare power consumption (W)
Returns:
- dict, Sandia inverter model parameters
"""import pvlib
from pvlib import inverter, pvsystem
import numpy as np
# Get inverter parameters from SAM database
inverters = pvsystem.retrieve_sam('CECInverter')
inverter_name = 'SMA_America__SB240_240V__CEC_2012_'
inv_params = inverters[inverter_name]
# Operating conditions
dc_voltage = np.array([300, 350, 400, 450, 500]) # V
dc_power = np.array([1000, 2000, 3000, 4000, 5000]) # W
# Calculate AC power using Sandia model
ac_power = inverter.sandia(
v_dc=dc_voltage,
p_dc=dc_power,
inverter=inv_params
)
print("Sandia Inverter Model Results:")
print("DC Power (W) DC Voltage (V) AC Power (W) Efficiency (%)")
for i in range(len(dc_power)):
efficiency = ac_power[i] / dc_power[i] * 100 if dc_power[i] > 0 else 0
print(f"{dc_power[i]:9.0f} {dc_voltage[i]:11.0f} {ac_power[i]:9.0f} {efficiency:11.1f}")import pvlib
from pvlib import inverter, pvsystem
import numpy as np
# Multi-MPPT inverter
inverters = pvsystem.retrieve_sam('CECInverter')
inv_params = inverters['SMA_America__SB240_240V__CEC_2012_']
# Two MPPT inputs with different conditions
# MPPT 1: Higher power
v_dc_1 = np.array([380, 385, 390, 395, 400])
p_dc_1 = np.array([1500, 1800, 2000, 2200, 2400])
# MPPT 2: Lower power
v_dc_2 = np.array([350, 355, 360, 365, 370])
p_dc_2 = np.array([800, 1000, 1200, 1400, 1600])
# Combined inputs
v_dc_inputs = (v_dc_1, v_dc_2)
p_dc_inputs = (p_dc_1, p_dc_2)
# Calculate AC power for multi-input inverter
ac_power_multi = inverter.sandia_multi(
v_dc=v_dc_inputs,
p_dc=p_dc_inputs,
inverter=inv_params
)
# Compare with sum of individual calculations
ac_power_1 = inverter.sandia(v_dc_1, p_dc_1, inv_params)
ac_power_2 = inverter.sandia(v_dc_2, p_dc_2, inv_params)
ac_power_sum = ac_power_1 + ac_power_2
print("Multi-MPPT vs Individual MPPT Comparison:")
print("P1(W) P2(W) Total_DC(W) Multi_AC(W) Sum_AC(W) Difference(W)")
for i in range(len(p_dc_1)):
total_dc = p_dc_1[i] + p_dc_2[i]
diff = ac_power_multi[i] - ac_power_sum[i]
print(f"{p_dc_1[i]:4.0f} {p_dc_2[i]:4.0f} {total_dc:9.0f} {ac_power_multi[i]:8.0f} {ac_power_sum[i]:7.0f} {diff:10.1f}")import pvlib
from pvlib import inverter
import numpy as np
import matplotlib.pyplot as plt
# System parameters
pdc0 = 5000 # DC power rating (W)
eta_inv_nom = 0.96 # Nominal efficiency
eta_inv_ref = 0.9637 # Reference efficiency
# DC power range
pdc = np.linspace(0, 6000, 100)
# Calculate AC power and efficiency
pac = inverter.pvwatts(pdc, pdc0, eta_inv_nom, eta_inv_ref)
efficiency = np.divide(pac, pdc, out=np.zeros_like(pac), where=pdc!=0)
# Plot inverter efficiency curve
plt.figure(figsize=(10, 6))
plt.subplot(1, 2, 1)
plt.plot(pdc/1000, pac/1000, 'b-', linewidth=2)
plt.xlabel('DC Power (kW)')
plt.ylabel('AC Power (kW)')
plt.title('PVWatts Inverter Power Curve')
plt.grid(True)
plt.subplot(1, 2, 2)
plt.plot(pdc/pdc0*100, efficiency*100, 'r-', linewidth=2)
plt.xlabel('DC Power (% of Rating)')
plt.ylabel('Efficiency (%)')
plt.title('PVWatts Inverter Efficiency')
plt.grid(True)
plt.ylim([90, 98])
plt.tight_layout()
plt.show()
print("PVWatts Inverter Performance:")
print("Load (%) DC Power (W) AC Power (W) Efficiency (%)")
for load_pct in [10, 20, 30, 50, 75, 100, 110]:
pdc_test = pdc0 * load_pct / 100
pac_test = inverter.pvwatts(pdc_test, pdc0, eta_inv_nom, eta_inv_ref)
eff_test = pac_test / pdc_test * 100 if pdc_test > 0 else 0
print(f"{load_pct:6.0f} {pdc_test:8.0f} {pac_test:8.0f} {eff_test:9.2f}")import pvlib
from pvlib import inverter, pvsystem
import numpy as np
# Get ADR inverter parameters (if available in database)
# For demonstration, create example parameters
adr_params = {
'Pnom': 5000, # W
'Vnom': 400, # V
'Vmax': 600, # V
'Vmin': 200, # V
'Vdcmax': 600, # V
'MPPTHi': 550, # V
'MPPTLow': 250, # V
'Pacmax': 5000, # W
'ADRCoefficients': [0.01, 0.02, 0.001, 0.001, 0.0001, 0.00001, 0.1, 0.01, 0.001],
'Pnt': 5 # W
}
# Operating conditions
dc_voltage = np.linspace(250, 550, 10)
dc_power = 3000 # W constant power
# Calculate AC power using ADR model
ac_power_adr = inverter.adr(
v_dc=dc_voltage,
p_dc=dc_power,
inverter=adr_params,
vtol=0.10
)
# Calculate efficiency
efficiency_adr = ac_power_adr / dc_power * 100
print("ADR Inverter Model - Voltage Dependency:")
print("DC Voltage (V) AC Power (W) Efficiency (%)")
for i, v in enumerate(dc_voltage):
print(f"{v:11.0f} {ac_power_adr[i]:9.0f} {efficiency_adr[i]:11.2f}")
# Test at different power levels
dc_power_range = np.linspace(500, 5500, 10)
dc_voltage_fixed = 400 # V
ac_power_adr_power = inverter.adr(
v_dc=dc_voltage_fixed,
p_dc=dc_power_range,
inverter=adr_params
)
efficiency_adr_power = ac_power_adr_power / dc_power_range * 100
print("\nADR Inverter Model - Power Dependency:")
print("DC Power (W) AC Power (W) Efficiency (%)")
for i, p in enumerate(dc_power_range):
print(f"{p:9.0f} {ac_power_adr_power[i]:9.0f} {efficiency_adr_power[i]:11.2f}")import pvlib
from pvlib import inverter, pvsystem
import numpy as np
import matplotlib.pyplot as plt
# Get Sandia parameters
inverters = pvsystem.retrieve_sam('CECInverter')
sandia_params = inverters['SMA_America__SB240_240V__CEC_2012_']
# PVWatts parameters
pdc0_pvwatts = 5000 # W
# ADR parameters (example)
adr_params = {
'Pnom': 5000, 'Vnom': 400, 'Vmax': 600, 'Vmin': 200,
'Vdcmax': 600, 'MPPTHi': 550, 'MPPTLow': 250, 'Pacmax': 5000,
'ADRCoefficients': [0.01, 0.02, 0.001, 0.001, 0.0001, 0.00001, 0.1, 0.01, 0.001],
'Pnt': 5
}
# Test conditions
dc_power = np.linspace(100, 5500, 50)
dc_voltage = 400 # V constant
# Calculate AC power using different models
ac_sandia = inverter.sandia(dc_voltage, dc_power, sandia_params)
ac_pvwatts = inverter.pvwatts(dc_power, pdc0_pvwatts)
ac_adr = inverter.adr(dc_voltage, dc_power, adr_params)
# Calculate efficiencies
eff_sandia = np.divide(ac_sandia, dc_power, out=np.zeros_like(ac_sandia), where=dc_power>0) * 100
eff_pvwatts = np.divide(ac_pvwatts, dc_power, out=np.zeros_like(ac_pvwatts), where=dc_power>0) * 100
eff_adr = np.divide(ac_adr, dc_power, out=np.zeros_like(ac_adr), where=dc_power>0) * 100
# Plot comparison
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# Power curves
ax1.plot(dc_power/1000, ac_sandia/1000, 'b-', label='Sandia', linewidth=2)
ax1.plot(dc_power/1000, ac_pvwatts/1000, 'r--', label='PVWatts', linewidth=2)
ax1.plot(dc_power/1000, ac_adr/1000, 'g:', label='ADR', linewidth=2)
ax1.set_xlabel('DC Power (kW)')
ax1.set_ylabel('AC Power (kW)')
ax1.set_title('Inverter Models - Power Output')
ax1.legend()
ax1.grid(True)
# Efficiency curves
ax2.plot(dc_power/pdc0_pvwatts*100, eff_sandia, 'b-', label='Sandia', linewidth=2)
ax2.plot(dc_power/pdc0_pvwatts*100, eff_pvwatts, 'r--', label='PVWatts', linewidth=2)
ax2.plot(dc_power/pdc0_pvwatts*100, eff_adr, 'g:', label='ADR', linewidth=2)
ax2.set_xlabel('DC Power (% of Rating)')
ax2.set_ylabel('Efficiency (%)')
ax2.set_title('Inverter Models - Efficiency')
ax2.legend()
ax2.grid(True)
ax2.set_ylim([85, 98])
plt.tight_layout()
plt.show()
print("Inverter Model Comparison at Key Operating Points:")
print("DC Power (%) Sandia Eff (%) PVWatts Eff (%) ADR Eff (%)")
for pct in [10, 25, 50, 75, 100]:
idx = int(pct/100 * len(dc_power)) - 1
print(f"{pct:9.0f} {eff_sandia[idx]:10.2f} {eff_pvwatts[idx]:12.2f} {eff_adr[idx]:8.2f}")import pvlib
from pvlib import inverter
import numpy as np
import pandas as pd
# Simulate measured inverter performance data
# Typically from CEC test protocol or manufacturer data sheets
np.random.seed(42)
# Test voltages
voltage_levels = ['Vmin', 'Vnom', 'Vmax']
voltages = {'Vmin': 200, 'Vnom': 400, 'Vmax': 600}
# Generate synthetic test data
ac_power_data = []
dc_power_data = []
dc_voltage_data = []
voltage_level_data = []
for level in voltage_levels:
v_test = voltages[level]
# Test power levels (10%, 20%, 30%, 50%, 75%, 100% of rating)
p_ac_fractions = [0.1, 0.2, 0.3, 0.5, 0.75, 1.0]
p_ac_rated = 5000 # W
for frac in p_ac_fractions:
p_ac = p_ac_rated * frac
# Simulate corresponding DC power (with realistic efficiency)
efficiency = 0.95 * (1 - 0.05 * (1 - frac)) # Efficiency varies with load
p_dc = p_ac / efficiency
# Add some measurement noise
p_ac_meas = p_ac * (1 + np.random.normal(0, 0.01))
p_dc_meas = p_dc * (1 + np.random.normal(0, 0.01))
v_dc_meas = v_test * (1 + np.random.normal(0, 0.005))
ac_power_data.append(p_ac_meas)
dc_power_data.append(p_dc_meas)
dc_voltage_data.append(v_dc_meas)
voltage_level_data.append(level)
# Convert to arrays
ac_power_array = np.array(ac_power_data)
dc_power_array = np.array(dc_power_data)
dc_voltage_array = np.array(dc_voltage_data)
voltage_level_array = np.array(voltage_level_data)
# Inverter specifications
p_ac_0 = 5000 # W, rated AC power
p_nt = 2 # W, night tare
# Fit Sandia model parameters
fitted_params = inverter.fit_sandia(
ac_power=ac_power_array,
dc_power=dc_power_array,
dc_voltage=dc_voltage_array,
dc_voltage_level=voltage_level_array,
p_ac_0=p_ac_0,
p_nt=p_nt
)
print("Fitted Sandia Inverter Parameters:")
for param, value in fitted_params.items():
print(f"{param:5s}: {value:8.3f}")
# Validate fit by comparing measured vs modeled AC power
ac_power_modeled = inverter.sandia(
v_dc=dc_voltage_array,
p_dc=dc_power_array,
inverter=fitted_params
)
# Calculate fit quality
residuals = ac_power_array - ac_power_modeled
rmse = np.sqrt(np.mean(residuals**2))
mae = np.mean(np.abs(residuals))
r_squared = 1 - np.sum(residuals**2) / np.sum((ac_power_array - np.mean(ac_power_array))**2)
print(f"\nModel Fit Quality:")
print(f"RMSE: {rmse:.1f} W")
print(f"MAE: {mae:.1f} W")
print(f"R²: {r_squared:.4f}")
# Display comparison for a few points
print(f"\nMeasured vs Modeled Comparison (First 10 points):")
print("Voltage Level Measured AC (W) Modeled AC (W) Error (W)")
for i in range(10):
error = ac_power_array[i] - ac_power_modeled[i]
print(f"{voltage_level_array[i]:12s} {ac_power_array[i]:11.1f} {ac_power_modeled[i]:10.1f} {error:7.1f}")Install with Tessl CLI
npx tessl i tessl/pypi-pvlib@0.13.2