Advanced: Solar Validation Against HALO-(AC)³

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:        300

Results#

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