PV LIB results x IES VE results

56 views
Skip to first unread message

Ricardo Filho

unread,
Jun 19, 2024, 11:14:05 AMJun 19
to pvlib-python
Hi all, 
i am trying to create a database with 672 variations of PV Panels systems, and to validate the results from PV LIB i am using IES VE and the Solar Atlas website

has anyone ever done anything similar?
I am getting very different results when I compare the 3 sim tools

example

for a 0 degree inclination 0 azimuth 5Kwp system
the VE gives me 4.2 MWh, solar atlas 4 and PV lib 9.1

Happy to share the code 

cwh...@sandia.gov

unread,
Jun 19, 2024, 11:25:48 AMJun 19
to pvlib-python
What location? 4MWhr for a 5kwp system seems very low.

Ricardo Filho

unread,
Jun 19, 2024, 11:26:53 AMJun 19
to pvlib-python
Dublin, Ireland
not the best location

cwh...@sandia.gov

unread,
Jun 19, 2024, 11:28:35 AMJun 19
to pvlib-python
Right, then 4MWhr is credible, and 9.1MWhr is suspect.

Can you share your pvlib code?

Ricardo Filho

unread,
Jun 19, 2024, 11:30:09 AMJun 19
to pvlib-python
import pvlib
import pandas as pd
import numpy as np

# Define PV Types and Locations
pv_types = {
    'Amorphous Silicon': {'Efficiency': 0.0990, 'NOCT': 45.0},
    'Monocrystalline Silicon': {'Efficiency': 0.2010, 'NOCT': 42.0},
    'Polycrystalline Silicon': {'Efficiency': 0.1700, 'NOCT': 44.0},
    'Other Thin Films': {'Efficiency': 0.2285, 'NOCT': 44.0},
    'Thin Film Cadmium-Telluride': {'Efficiency': 0.1200, 'NOCT': 42.8},
    'Thin Film Copper-Indium-Gallium-Selenide': {'Efficiency': 0.1300, 'NOCT': 42.8}
}

# Load EPW file for local weather data
epw_file_path = 'IRL_Dublin.039690_IWEC.epw'  # Adjust the filename as needed
weather_data, metadata = pvlib.iotools.read_epw(epw_file_path, coerce_year=2023)

# Extract necessary data
times = weather_data.index
ghi = weather_data['ghi']
dni = weather_data['dni']
dhi = weather_data['dhi']
dni_extra = pvlib.irradiance.get_extra_radiation(times)  # Calculate dni_extra
latitude, longitude, tz = metadata['latitude'], metadata['longitude'], metadata['TZ']

# User inputs for PV type and output type
print("Select the PV type:")
for idx, pv_type in enumerate(pv_types, 1):
    print(f"{idx}. {pv_type}")
pv_type_index = int(input("Enter the number corresponding to the PV type: ")) - 1
pv_type_name = list(pv_types.keys())[pv_type_index]
pv_type = pv_types[pv_type_name]

output_type = input("Enter 'timeseries', 'total', or 'monthly': ").strip().lower()

# Simulation settings
powers = range(5, 125, 5)
inclinations = range(0, 35, 5)
orientations = [0,180, 225, 135]  # South, South-West, South-East

# Prepare data collection
all_data = {'Datetime': times}
column_names = []

# Perform the simulation
location = pvlib.location.Location(latitude, longitude, tz)
solpos = location.get_solarposition(times)

for power in powers:
    for inclination in inclinations:
        for orientation in orientations:
            poa_irradiance = pvlib.irradiance.get_total_irradiance(
                surface_tilt=inclination,
                surface_azimuth=orientation,
                solar_zenith=solpos['apparent_zenith'],
                solar_azimuth=solpos['azimuth'],
                dni=dni, ghi=ghi, dhi=dhi, dni_extra=dni_extra,  # Include dni_extra here
                model='perez'  # Using Perez model for better accuracy
            )
            system_output = power * pv_type['Efficiency'] * poa_irradiance['poa_global']
            col_name = f'{power}_{inclination}_{orientation}_{pv_type_name}'
            column_names.append(col_name)
            all_data[col_name] = system_output

# Convert dictionary to DataFrame
results = pd.DataFrame(all_data)
results.set_index('Datetime', inplace=True)

# Handle output based on user choice
if output_type == 'timeseries':
    results.to_csv('solar_simulation_time_series_results.csv')
elif output_type == 'total':
    total_generation = results.sum().to_frame(name='Total Generation')
    total_generation.to_csv('solar_simulation_total_generation_results.csv')
elif output_type == 'monthly':
    monthly_totals = results.resample('M').sum()
    monthly_totals.to_csv('solar_simulation_monthly_totals.csv')

print(f"Simulation completed and results are saved according to selected output type: {output_type}.")


weather file from Energy Plus

cwh...@sandia.gov

unread,
Jun 19, 2024, 11:51:13 AMJun 19
to pvlib-python
Fairly sure the problem is with the irradiance-to-power calculation:

            system_output = power * pv_type['Efficiency'] * poa_irradiance['poa_global']

I assume power "5" means 5kW at 1000 W/m2 (rating condition). The conversion efficiency and area are implicit in the rating. 

Instead of the above, you should calculate

             system_output = power * poa_irradiance['poa_global'] / 1000 W/m2

Or better, if you have air temperature in the EPW file (I think it should be there, haven't looked), calculate a module temperature using one of the models in pvlib.temperature, then use pvsystem.pvwatts_dc to get the power. Efficiency decreases as temperature increases; the above equations assume the modules are always at the rating condition of 25C.

I would also check that the poa_global is zero at night.

Ricardo Filho

unread,
Jun 19, 2024, 12:00:50 PMJun 19
to pvlib-python
Does it look better now?


import pvlib
import pandas as pd
import numpy as np

# Define PV Types and Locations
pv_types = {
    'Amorphous Silicon': {'Efficiency': 0.0990, 'NOCT': 45.0},
    'Monocrystalline Silicon': {'Efficiency': 0.2010, 'NOCT': 42.0},
    'Polycrystalline Silicon': {'Efficiency': 0.1700, 'NOCT': 44.0},
    'Other Thin Films': {'Efficiency': 0.2285, 'NOCT': 44.0},
    'Thin Film Cadmium-Telluride': {'Efficiency': 0.1200, 'NOCT': 42.8},
    'Thin Film Copper-Indium-Gallium-Selenide': {'Efficiency': 0.1300, 'NOCT': 42.8}
}

# Load EPW file for local weather data
epw_file_path = 'IRL_Dublin.039690_IWEC.epw'
weather_data, metadata = pvlib.iotools.read_epw(epw_file_path, coerce_year=2023)

# Extract necessary data
times = weather_data.index
ghi = weather_data['ghi']
dni = weather_data['dni']
dhi = weather_data['dhi']
dni_extra = pvlib.irradiance.get_extra_radiation(times)
latitude, longitude, tz = metadata['latitude'], metadata['longitude'], metadata['TZ']

# User inputs for PV type and output type
print("Select the PV type:")
for idx, pv_type in enumerate(pv_types, 1):
    print(f"{idx}. {pv_type}")
pv_type_index = int(input("Enter the number corresponding to the PV type: ")) - 1
pv_type_name = list(pv_types.keys())[pv_type_index]
pv_type = pv_types[pv_type_name]

output_type = input("Enter 'timeseries', 'total', or 'monthly': ").strip().lower()

# Simulation settings
powers = range(5, 125, 5)
inclinations = range(0, 35, 5)
orientations = [0, 180, 225, 135]  # Cardinal orientations


# Prepare data collection
all_data = {'Datetime': times}
column_names = []

# Perform the simulation
location = pvlib.location.Location(latitude, longitude, tz)
solpos = location.get_solarposition(times)

for power in powers:
    for inclination in inclinations:
        for orientation in orientations:
            poa_irradiance = pvlib.irradiance.get_total_irradiance(
                surface_tilt=inclination,
                surface_azimuth=orientation,
                solar_zenith=solpos['apparent_zenith'],
                solar_azimuth=solpos['azimuth'],
                dni=dni, ghi=ghi, dhi=dhi, dni_extra=dni_extra,
                model='perez'
            )
            # Calculate system output based on normalized irradiance

            system_output = power * poa_irradiance['poa_global'] / 1000
            col_name = f'{power}_{inclination}_{orientation}_{pv_type_name}'
            column_names.append(col_name)
            all_data[col_name] = system_output

# Convert dictionary to DataFrame
results = pd.DataFrame(all_data)
results.set_index('Datetime', inplace=True)

# Handle output based on user choice
if output_type == 'timeseries':
    results.to_csv('solar_simulation_time_series_results.csv')
elif output_type == 'total':
    total_generation = results.sum().to_frame(name='Total Generation')
    total_generation.to_csv('solar_simulation_total_generation_results.csv')
elif output_type == 'monthly':
    monthly_totals = results.resample('M').sum()
    monthly_totals.to_csv('solar_simulation_monthly_totals.csv')

print(f"Simulation completed and results are saved according to selected output type: {output_type}.")


cwh...@sandia.gov

unread,
Jun 19, 2024, 12:22:36 PMJun 19
to pvlib-python
>  Does it look better now?

Yes, better. What annual energy do you get?

Since you have a NOCT value for each module type, you could make a crude adjustment for temperature: 

system_output = power * poa_irradiance['poa_global'] * (1 - 0.004 * (NOCT - 25))

This assumes a temperature coefficient for power of 0.4%/C, which is typical for silicon, maybe a bit high for CdTe. You can find temperature coefficients on module datasheets, so I'm assuming you would have values for the 672 modules you want to model.

Ricardo Filho

unread,
Jun 19, 2024, 12:36:50 PMJun 19
to pvlib-python
Based on the code below I am getting 4.3MWh against 4.1 from the VE and 3.9MWh from solar atlas
not sure if this is an Ok error 


import pvlib
import pandas as pd
import numpy as np

# Define PV Types and Locations with additional temperature coefficients
pv_types = {
    'Amorphous Silicon': {'Efficiency': 0.0990, 'NOCT': 45.0, 'TempCoeff': 0.0043},
    'Monocrystalline Silicon': {'Efficiency': 0.2010, 'NOCT': 42.0, 'TempCoeff': 0.0040},
    'Polycrystalline Silicon': {'Efficiency': 0.1700, 'NOCT': 44.0, 'TempCoeff': 0.0042},
    'Other Thin Films': {'Efficiency': 0.2285, 'NOCT': 44.0, 'TempCoeff': 0.0029},
    'Thin Film Cadmium-Telluride': {'Efficiency': 0.1200, 'NOCT': 42.8, 'TempCoeff': 0.0025},
    'Thin Film Copper-Indium-Gallium-Selenide': {'Efficiency': 0.1300, 'NOCT': 42.8, 'TempCoeff': 0.0027}

}

# Load EPW file for local weather data
epw_file_path = 'IRL_Dublin.039690_IWEC.epw'
weather_data, metadata = pvlib.iotools.read_epw(epw_file_path, coerce_year=2023)

# Extract necessary data
times = weather_data.index
ghi = weather_data['ghi']
dni = weather_data['dni']
dhi = weather_data['dhi']
temp_air = weather_data['temp_air']
wind_speed = weather_data['wind_speed']

dni_extra = pvlib.irradiance.get_extra_radiation(times)
latitude, longitude, tz = metadata['latitude'], metadata['longitude'], metadata['TZ']

# User inputs for PV type and output type
print("Select the PV type:")
for idx, pv_type in enumerate(pv_types, 1):
    print(f"{idx}. {pv_type}")
pv_type_index = int(input("Enter the number corresponding to the PV type: ")) - 1
pv_type_name = list(pv_types.keys())[pv_type_index]
pv_type = pv_types[pv_type_name]

output_type = input("Enter 'timeseries', 'total', or 'monthly': ").strip().lower()

# Simulation settings
powers = range(5, 125, 5)  # in kW

inclinations = range(0, 35, 5)
orientations = [0, 180, 225, 135]  # Cardinal orientations

# Prepare data collection
all_data = {'Datetime': times}
column_names = []

# Perform the simulation
location = pvlib.location.Location(latitude, longitude, tz)
solpos = location.get_solarposition(times)

for power in powers:
    for inclination in inclinations:
        for orientation in orientations:
            poa_irradiance = pvlib.irradiance.get_total_irradiance(
                surface_tilt=inclination,
                surface_azimuth=orientation,
                solar_zenith=solpos['apparent_zenith'],
                solar_azimuth=solpos['azimuth'],
                dni=dni, ghi=ghi, dhi=dhi, dni_extra=dni_extra,
                model='perez'
            )
            temp_module = pvlib.temperature.pvsyst_cell(temp_air, poa_irradiance['poa_global'], wind_speed, pv_type['NOCT'])
           
            system_output = power * poa_irradiance['poa_global'] * (1 - 0.004 * (pv_type['NOCT'] - 25))

            col_name = f'{power}_{inclination}_{orientation}_{pv_type_name}'
            column_names.append(col_name)
            all_data[col_name] = system_output

# Convert dictionary to DataFrame
results = pd.DataFrame(all_data)
results.set_index('Datetime', inplace=True)

# Handle output based on user choice
if output_type == 'timeseries':
    results.to_csv('solar_simulation_time_series_results.csv')
elif output_type == 'total':
    total_generation = results.sum().to_frame(name='Total Generation kWh')
    total_generation['Total Generation MWh'] = total_generation['Total Generation kWh'] / 1000

    total_generation.to_csv('solar_simulation_total_generation_results.csv')
elif output_type == 'monthly':
    monthly_totals = results.resample('M').sum()
    monthly_totals['Total Generation kWh'] = monthly_totals.sum(axis=1)
    monthly_totals['Total Generation MWh'] = monthly_totals['Total Generation kWh'] / 1000

    monthly_totals.to_csv('solar_simulation_monthly_totals.csv')

print(f"Simulation completed and results are saved according to selected output type: {output_type}.")

cwh...@sandia.gov

unread,
Jun 19, 2024, 12:57:59 PMJun 19
to pvlib-python
Given the differences in input weather, the unknown weather-to-power modeling in the Solar Atlas and in VE, and the unknown assumptions about module parameters, I'd say 4.3 / 4.1 / 3.9 are reasonably close.

Close enough depends on what you are trying to learn from the comparison.

Ricardo Filho

unread,
Jun 19, 2024, 1:06:53 PMJun 19
to pvlib-python
Thanks!
my goal is to create a database with 672 variations power from 5 to 120kWp, various inclinations and orientations 
and then get an app based on user timeseries data for consumption  x a given scenario from the database

Ricardo Filho

unread,
Jun 19, 2024, 1:17:19 PMJun 19
to pvlib-python
Capture.JPG
Is this the parameter you mentioned before?

cwh...@sandia.gov

unread,
Jun 19, 2024, 1:29:07 PMJun 19
to pvlib-python
Yes. In the implementation, be sure that temperature > 25C reduces power. 

Ricardo Filho

unread,
Jun 19, 2024, 1:30:50 PMJun 19
to pvlib-python
Got even closer now, the VE assumes a degradation factor of .99 and conversion losses of 4%
factor these in I am getting 4.2MWh on PVLIB and 4.12 on the VE with the same weather file
Reply all
Reply to author
Forward
0 new messages