Rust Solar Toolkit (rsun)¶
rsun is a Rust port of GRASS GIS r.sun — a complete implementation of the ESRA clear-sky solar radiation model (Hofierka & Suri, 2002). It computes global solar radiation and sunshine duration on arbitrary terrain, with CPU parallelism, GPU acceleration, and multi-platform deployment.
Why Rust?¶
| Goal | How rsun achieves it |
|---|---|
| Performance | Rayon auto-parallelism over all pixels; wgpu GPU compute shaders |
| Portability | Single CLI binary, Python wheel (PyO3), WebAssembly for browsers |
| Safety | Rust's type system prevents memory bugs; formal Lean 4 proofs verify math |
| Independence | No GRASS GIS installation required; reads/writes GeoTIFF directly via GDAL |
Architecture¶
graph TD
DEM[DEM GeoTIFF] --> IO[rsun-core::io]
IO --> GRID[Grid + SolarParams]
GRID --> CPU[rsun-core::compute_day]
GRID --> GPU[rsun-gpu::RadiationPipeline]
CPU --> RESULT[DayResult]
GPU --> RESULT
RESULT --> TIFF[Output GeoTIFF]
RESULT --> NUMPY[NumPy Array]
RESULT --> JSON[WASM JSON]
CLI[rsun-cli] --> IO
CLI --> CPU
CLI --> GPU
PY[rsun-py] --> CPU
PY --> GPU
WASM[rsun-wasm] --> CPU
style CPU fill:#2d6a4f,color:#fff
style GPU fill:#1d3557,color:#fff
style CLI fill:#457b9d,color:#fff
style PY fill:#e9c46a,color:#000
style WASM fill:#f4a261,color:#000
Crate Workspace¶
| Crate | Purpose | Key Dependencies |
|---|---|---|
rsun-core |
CPU reference implementation | rayon, gdal (optional) |
rsun-cli |
Command-line binary | clap, rsun-core, rsun-gpu |
rsun-gpu |
GPU acceleration | wgpu, bytemuck |
rsun-py |
Python bindings | pyo3, numpy |
rsun-wasm |
WebAssembly module | wasm-bindgen, serde |
Core Module Map¶
| Module | Algorithms |
|---|---|
solar.rs |
Solar declination (Spencer 1971), corrected solar constant, sunrise/sunset times, solar altitude & azimuth, hour angle conversion |
radiation.rs |
Beam radiation with atmospheric refraction and Kasten-Young air mass, diffuse radiation (Suri/ESRA model), reflected radiation, cosine of incidence angle (Jenco transform) |
horizon.rs |
Ray-marching horizon angles in N azimuthal directions, bilinear interpolation |
terrain.rs |
Slope and aspect from DEM via Horn's 3x3 weighted finite difference method |
io.rs |
GeoTIFF I/O via GDAL, automatic CRS detection and lat/lon reprojection |
Algorithm Correspondence¶
Every rsun function maps directly to a GRASS r.sun routine and a Lean 4 formal definition:
| Algorithm | GRASS r.sun | Rust (rsun-core) |
Lean 4 (EEMTVerify.Solar) |
|---|---|---|---|
| Solar declination | com_declin() |
solar::declination() |
Declination.declination |
| Corrected solar constant | com_sol_const() |
solar::corrected_solar_constant() |
SolarConstant.correctedSolarConstant |
| Sunrise & sunset | com_par_const() |
solar::sunrise_sunset() |
SunriseSunset.sunriseHour / sunsetHour |
| Solar position | com_par() |
solar::solar_position() |
SolarPosition.solarAltitude |
| Hour angle | — | solar::hour_to_time_angle() |
SunriseSunset.hourToTimeAngle |
| Beam radiation | brad() |
radiation::brad() |
BeamRadiation.beamTilted + AirMass.* |
| Diffuse + reflected | drad() |
radiation::drad() |
DiffuseRadiation.* |
| Cos incidence | lumcline2() |
radiation::cos_incidence() |
CosIncidence.cosIncidence |
| Daily integration | joules2() |
compute_day() |
TotalRadiation.dailyRadiation |
| Slope & aspect | r.slope.aspect |
terrain::slope_aspect() |
— |
| Horizon angles | r.horizon |
horizon::compute_horizons() |
— |
Lean 4 Proven Properties¶
The Lean 4 theorem prover has formally verified mathematical bounds on every core equation. These bounds are also validated at runtime by unit tests in rsun-core/tests/lean4_validation_tests.rs.
| Lean 4 Theorem | Proven Bound | Rust Validated |
|---|---|---|
declination_bounded |
\(\delta \in [-\arcsin(0.3978),\; \arcsin(0.3978)]\) (~±23.44°) | All 365 days |
eccentricityFactor_lower / _upper |
Factor \(\in [0.96656,\; 1.03344]\) | All 365 days |
correctedSolarConstant_default_range |
\(G \in [1321,\; 1413]\) W/m² at \(G_0 = 1367\) | All 365 days |
hourToTimeAngle_noon |
\(\omega(12) = 0\) | Exact |
sunrise_sunset_sum |
sunrise + sunset \(= 24\) | Multiple latitudes |
dayLength_nonneg / _le_24 |
Day length \(\in [0,\; 24]\) hours | All lat/day combos |
equinox_twelve_hours |
Day length \(= 12\) h when \(\delta = 0\) | Multiple latitudes |
solarAltitude_bounded |
Altitude \(\in [-\pi/2,\; \pi/2]\) | Sampled positions |
noon_altitude_max |
Noon altitude is daily maximum | Multiple latitudes |
beamTransmittance_pos / _le_one |
Transmittance \(\in (0,\; 1]\) | Sampled conditions |
elevationCorrection_antitone |
Correction decreasing with altitude | 0–8848 m |
beamTilted_zero_facing_away |
Beam \(= 0\) when \(s_0 \le 0\) | Direct test |
beamTilted_zero_night |
Beam \(= 0\) when altitude \(\le 0\) | Direct test |
beamTiltedSimplified_le_gExt |
Beam \(\le G_\text{ext}\) | Sampled conditions |
view_factors_sum_one |
\(F_\text{sky} + F_\text{terrain} = 1\) | Multiple slopes |
reflectedRadiation_flat |
Reflected \(= 0\) when slope \(= 0\) | Direct test |
reflectedRadiation_nonneg |
Reflected \(\ge 0\) | Sampled conditions |
dailyRadiation_nonneg |
Daily total \(\ge 0\) | Synthetic DEM |
insolation_le_dayLength |
Insolation \(\le\) day length | Synthetic DEM |
Performance¶
CPU (rsun-core)¶
- Parallelism: Rayon distributes pixel computation across all available cores
- Time complexity: \(O(\text{pixels} \times \text{time\_steps})\) per day, where time steps \(= \lceil\text{day\_length} / \text{step}\rceil\)
- Step impact:
step=0.5(default) gives ~24 steps/day;step=0.25doubles compute time for ~1% accuracy gain
GPU (rsun-gpu)¶
- Backend: wgpu compute shaders (WGSL) — works on Vulkan, Metal, DX12
- Pipeline: Two-pass: (1) horizon pre-computation, (2) per-pixel daily radiation loop
- Workgroup size: 64 threads per workgroup, 2D dispatch for large DEMs
- Fallback: Auto-detects GPU; falls back to CPU if unavailable
CLI Reference¶
rsun compute --dem <PATH> --day <SPEC> [OPTIONS]
rsun terrain --dem <PATH> --slope-out <PATH> --aspect-out <PATH>
rsun info
compute Options¶
| Flag | Default | Description |
|---|---|---|
--dem |
— | Input DEM GeoTIFF (required) |
--day |
— | Day spec: "172", "1-365", or "all" |
--step |
0.5 | Time step in decimal hours |
--linke |
3.0 | Linke turbidity factor |
--albedo |
0.2 | Surface albedo |
--gpu |
auto | GPU mode: "auto", "cpu", or device index |
--glob-rad |
— | Output global radiation GeoTIFF (single-day) |
--insol-time |
— | Output insolation time GeoTIFF (single-day) |
--output-dir |
— | Output directory (multi-day) |
Python API¶
import rsun
# From file (requires GDAL at build time)
result = rsun.compute_day_from_file("dem.tif", day=172, step=0.5, linke=3.0, albedo=0.2)
result.glob_rad # numpy array [Wh/m²]
result.insol_time # numpy array [hours]
# From numpy arrays
result = rsun.compute_day(dem, slope, aspect, lat, lon, day=172)
# Terrain analysis
slope, aspect = rsun.compute_terrain(dem, ew_res=10.0, ns_res=10.0)
# GPU info
gpus = rsun.gpu_info() # list of available GPU adapters
WebAssembly API¶
import init, { compute_day } from './rsun_wasm.js';
await init();
const result = compute_day(dem_array, rows, cols, lat_rad, lon_rad, day, step);
// result.glob_rad — Float32Array
// result.insol_time — Float32Array
CPU-only; limited to DEMs under 1 million pixels.
Validation Against GRASS r.sun¶
The validation/compare.sh script runs rsun and GRASS r.sun on the same DEM (mcn_10m.tif) for 4 representative days (winter solstice, spring equinox, summer solstice, fall equinox) and computes:
- RMSE — Root mean square error across all valid pixels
- MaxRelErr% — Maximum relative error at any pixel
Acceptance criteria: MaxRelErr < 5% for all days.
References¶
- Hofierka, J. & Suri, M. (2002). The solar radiation model for Open Source GIS: implementation and applications. Proceedings of the Open Source GIS - GRASS Users Conference.
- Spencer, J.W. (1971). Fourier series representation of the position of the Sun. Search, 2(5), 172.
- Kasten, F. & Young, A.T. (1989). Revised optical air mass tables and approximation formula. Applied Optics, 28(22), 4735–4738.
- Jenco, M. (1992). Distribution of direct solar radiation on georelief. Geograficky Casopis, 44, 342–355.
- Suri, M. & Hofierka, J. (2004). A new GIS-based solar radiation model and its application to photovoltaic assessments. Transactions in GIS, 8(2), 175–190.