Advanced: Solar Validation Against HALO-(AC)³#
In this notebook, we’ll validate pyRadtran’s solar broadband simulations against real aircraft measurements from the HALO-(AC)³ campaign. This demonstrates how to compare simulated downwelling irradiance against observed values.
Setup#
import pyradtran
from pyradtran import load_config
import matplotlib.pyplot as plt
import numpy as np
import xarray as xr
from pathlib import Path
import pandas as pd
import logging
# Configure logging for pyradtran
logging.getLogger('pyradtran').setLevel(logging.CRITICAL)
# ── Simulation parameters ─────────────────────────────────────────────────────
cfg = load_config()
# Radiosonde-driven broadband solar simulation
cfg.simulation_defaults.rte_solver = "disort"
cfg.simulation_defaults.mol_abs_param = "reptran per_nm"
cfg.simulation_defaults.source = "solar"
cfg.simulation_defaults.wavelength_nm = [400, 4500]
cfg.simulation_defaults.integrate_wavelength = True
cfg.simulation_defaults.h2o_source = "radiosonde"
cfg.simulation_defaults.albedo_value = 0.3
cfg.simulation_defaults.surface_temperature_k = 253.15
cfg.simulation_defaults.ozone_du = 300.0
cfg.simulation_defaults.output_altitudes_km = [0.0]
cfg.simulation_defaults.output_columns = ["zout", "lambda", "sza", "edir", "eglo", "edn", "eup", "enet", "albedo"]
cfg.execution.max_workers = 16
cfg.execution.cleanup_temp_files = False
config_path = Path("config/radiosonde.yaml")
cfg.to_yaml(config_path)
print(f"Config saved to {config_path}")
2026-04-19 23:57:22,116 - pyradtran.config - INFO - Configuration written to config/radiosonde.yaml
Config saved to config/radiosonde.yaml
Loading Measurement Data#
Note
This notebook requires the HALO-(AC)³ broadband radiation CSV file bundled in the data/ directory. The dataset contains clear-sky aircraft measurements from the HALO, P5, and P6 aircraft.
# Load the CSV file and convert to xarray Dataset
df = pd.read_csv(
'data/HALO-AC3_HALO_P5_P6_aircraft_broadband_radiation_clear_sky_with_ocean_100s.csv',
parse_dates=['time'],
)
df = df.set_index('time').sort_index()
# drop any duplicate time entries
df = df[~df.index.duplicated(keep='first')]
ds = xr.Dataset.from_dataframe(df).interpolate_na('time')
# The albedo has over 30% missing values, so we interpolate for now
ds = ds.rename(
{'Lat' : 'latitude', 'Lon' : 'longitude', 'Alt' : 'altitude'}
)
ds['albedo'] = ds['F_up_solar'] / ds['F_down_solar_corr']
ds['albedo']
<xarray.DataArray 'albedo' (time: 215)> Size: 2kB
array([ nan, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, 0.2307841 , 0.16964852, 0.17234142,
0.16046484, 0.28628052, 0.3088238 , 0.32894586, 0.26386259,
0.65834449, 0.73574771, 0.8383168 , 0.72181846, 0.19864284,
0.1528411 , 0.16446308, 0.16541283, 0.21314093, 0.41164941,
0.66344914, 0.19625716, 0.18684475, 0.18246635, 0.18641166,
0.20010528, 0.38224332, 0.75026962, 0.78093725, 0.8219777 ,
0.84877755, 0.78216714, 0.69602435, 0.6898827 , 0.5555779 ,
0.32258573, 0.38781494, 0.59320847, 0.72504059, 0.19407424,
...
0.21509018, 0.44194946, 0.5049824 , 0.31665841, 0.17980303,
0.31646153, 0.70841603, 0.72856366, 0.15103181, 0.23730826,
0.1848405 , 0.57764898, 0.77033246, 0.75006878, 0.78593929,
0.63858876, 0.20341909, 0.1591294 , inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, 0.41271709, 0.24470363,
0.11090722, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, inf,
inf, inf, inf, inf, nan])
Coordinates:
* time (time) datetime64[ns] 2kB 2022-03-12T12:03:20 ... 2022-04-12T12:...Running Simulations#
# Run a spectral simulation for the dataset
print("Running batch spectral simulation...")
ds_sim = ds.pyradtran.run(
config_path=config_path,
return_dataset=True,
save_to_file=True,
output_path='data/Simulated_HALO-AC3_HALO_aircraft_broadband_radiation_clear_sky_with_ocean_600s.nc',
albedo_var='albedo',
show_progress=False,
)
print("\nSimulation complete!")
ds_sim
Running batch spectral simulation...
Simulation complete!
<xarray.Dataset> Size: 17kB
Dimensions: (time: 215, altitude: 1)
Coordinates:
* time (time) datetime64[ns] 2kB 2022-03-12T12:03:20 ... 2022-04-12T12...
* altitude (altitude) int64 8B 0
Data variables:
zout (altitude, time) float64 2kB nan nan nan nan ... nan nan nan nan
lambda (altitude, time) float64 2kB nan nan nan nan ... nan nan nan nan
sza (altitude, time) float64 2kB nan nan nan nan ... nan nan nan nan
edir (altitude, time) float64 2kB nan nan nan nan ... nan nan nan nan
eglo (altitude, time) float64 2kB nan nan nan nan ... nan nan nan nan
edn (altitude, time) float64 2kB nan nan nan nan ... nan nan nan nan
eup (altitude, time) float64 2kB nan nan nan nan ... nan nan nan nan
enet (altitude, time) float64 2kB nan nan nan nan ... nan nan nan nan
albedo (altitude, time) float64 2kB nan nan nan nan ... nan nan nan nan
Attributes: (12/26)
point_id: 20220410_091500_78.25_15.41_150
time: 2022-04-10T09:15:00
latitude: 78.24617
longitude: 15.408227
albedo: 0.11090721661560271
altitude: 29.866999
... ...
config_integrate_wavelength: 1
config_output_altitudes_km: [0.0]
config_libradtran_bin: /opt/libRadtran-2.0.6/bin/uvspec
config_libradtran_data: /opt/libRadtran-2.0.6/data
config_max_workers: 16
config_timeout_seconds: 300Results#
We compare the simulated albedo at different output altitudes against the aircraft measurements. The scatter plot on the right shows the 1:1 correlation — points close to the diagonal line indicate good agreement between simulation and observation.
ds_sim.eglo.dropna('time')
<xarray.DataArray 'eglo' (altitude: 1, time: 1)> Size: 8B array([[382570.9]]) Coordinates: * time (time) datetime64[ns] 8B 2022-04-10T09:15:00 * altitude (altitude) int64 8B 0