Switch to stable 8.0.427-dev

solar

@library("solar", "0.0.0");

Solar energy calculation library for GreyCat. All math runs as native C for maximum throughput.

Units convention: power in watts (W), energy in watt-hours (Wh), irradiance in W/m², angles in degrees, pressure in Pa, temperature in °C, speed in m/s, distances in metres.


Quick start

@library("solar", "0.0.0");

fn main() {
    // Sun position at Beirut, summer noon
    var t = time::new(1782043200, DurationUnit::seconds);
    var pos = SolarPosition::position(t, 33.89, 35.50);
    println("Altitude: ${pos.altitude_deg} deg, Azimuth: ${pos.azimuth_deg} deg");

    // Clear-sky irradiance
    var cs = SolarIrradiance::clear_sky(pos.apparent_zenith_deg, 0.0, 2.0);
    println("GHI: ${cs.ghi} W/m2, DNI: ${cs.dni} W/m2, DHI: ${cs.dhi} W/m2");

    // Instant PV output (5 kW system, 30 deg tilt, south-facing)
    var pv = SolarPv::instant_output(
        5000.0, 0.85, 30.0, 180.0,
        cs.dni, cs.ghi, cs.dhi,
        pos.apparent_zenith_deg, pos.azimuth_deg, 0.25
    );
    println("Power: ${pv.power_w} W, POA: ${pv.poa_w_m2} W/m2");

    // Monthly cloud factors from NASA (embedded, no network)
    var factors = SolarData::estimate_monthly_factors(33.89, 35.50);
    println("Jan factor: ${factors[0]}, Jun factor: ${factors[5]}");

    // Simulate one year with realistic cloud factors
    var from = time::new(1735689600, DurationUnit::seconds);
    var to   = time::new(1767225600, DurationUnit::seconds);
    var results = SolarPv::simulate(
        33.89, 35.50, 0.0, 5000.0, 30.0, 180.0,
        from, to, SimulationResolution::monthly, null
    );
    for (i, pt in results) {
        println("${pt.at}: ${pt.wh} Wh (max: ${pt.wh_max}, std: ${pt.wh_std})");
    }
}

Result types

All result types are @volatile (ephemeral, not persisted to storage).

SunPositionResult

Sun position at a point in time.

Field Type Unit Description
altitude_deg float deg Geometric altitude above horizon
apparent_altitude_deg float deg Apparent altitude with atmospheric refraction
azimuth_deg float deg Azimuth clockwise from North (0=N, 90=E, 180=S)
zenith_deg float deg Geometric zenith = 90 - altitude
apparent_zenith_deg float deg Apparent zenith = 90 - apparent_altitude
declination_deg float deg Solar declination
hour_angle_deg float deg Hour angle (negative=morning, positive=afternoon)
equation_of_time_min float min Equation of time

SunTimesResult

Sunrise, sunset, and solar transit for one day.

Field Type Unit Description
sunrise time? UTC Sunrise time, null if polar night
sunset time? UTC Sunset time, null if polar night
transit time? UTC Solar noon / transit time
day_length_hours float hours Day length (0 polar night, 24 polar day)

ClearSkyResult

Clear-sky irradiance components.

Field Type Unit Description
ghi float W/m² Global Horizontal Irradiance
dni float W/m² Direct Normal Irradiance
dhi float W/m² Diffuse Horizontal Irradiance

PoaResult

Plane-of-array irradiance decomposition.

Field Type Unit Description
poa_global float W/m² Total in-plane irradiance
poa_direct float W/m² Direct (beam) component
poa_sky_diffuse float W/m² Sky diffuse component
poa_ground_diffuse float W/m² Ground-reflected component

PvOutputResult

PV system instantaneous output.

Field Type Unit Description
power_w float W Instantaneous DC power output
poa_w_m2 float W/m² POA irradiance used for this calculation
orientation_ratio float Ratio of tilted to horizontal irradiance

TrackerResult

Tracker geometry output.

Field Type Unit Description
surface_tilt float deg Resulting panel tilt after rotation
surface_azimuth float deg Resulting panel azimuth after rotation
aoi float deg Angle of incidence on the rotated surface
tracker_theta float deg Tracker rotation angle (positive = clockwise)

CloudFactor

Cloud attenuation factor with uncertainty.

Field Type Unit Description
mean float Mean cloud attenuation ratio (0-1, 1=clear sky)
std float Standard deviation of cloud attenuation ratio

SimulationPoint

One bucket of a simulation result.

Field Type Unit Description
at time UTC Start of the bucket
wh float Wh Energy produced in bucket, cloud-attenuated
wh_max float Wh Clear-sky theoretical maximum energy
wh_std float Wh Standard deviation from cloud variability

Configuration types

SimulationConfig

Optional configuration for SolarPv::simulate and SolarPv::simulate_clear_sky. All fields are nullable — omit or set to null to use defaults.

Field Type Default Description
efficiency float? 0.85 System efficiency (0-1)
albedo float? 0.25 Ground albedo (0-1)
linke_turbidity float? 2.0 Linke turbidity factor
ambient_temp_c float? Ambient temperature for cell temp derating (°C)
wind_speed_ms float? 1.0 Wind speed for cell temp model (m/s)
ac_capacity_w float? AC inverter capacity for clipping (W)
soiling_factors Array<float>? 12 monthly soiling loss factors (0-1)
monthly_factors Array<float>? 12 monthly cloud factors (0-1), overrides NASA data
measured_ghi Array<float>? Measured GHI array (W/m²), one per simulation hour
degradation_rate float? 0 Annual degradation rate (0-1, typical 0.005 = 0.5%/year)
system_age_years float? 0 System age in years for degradation calculation
measured_temp_c Array<float>? Measured ambient temperature (°C), one per hour

When ambient_temp_c is set, cell temperature derating is enabled (Sandia/King model). When ac_capacity_w is set, the DC-to-AC inverter model is enabled with clipping. When degradation_rate is set, output is multiplied by (1 - rate)^age.


Enums

SimulationResolution

Time granularity for simulation output buckets.

enum SimulationResolution { hourly; daily; weekly; monthly; yearly; }
Value Description
hourly One point per hour
daily One point per day
weekly One point per 7-day period
monthly One point per calendar month
yearly One point per calendar year

Solar position — SolarPosition

Solar ephemeris calculations. position uses a full Meeus-derived algorithm (sub-degree accuracy) with nutation and parallax corrections.

Function Returns Description
position(t, lat, lng) SunPositionResult Full sun position (standard atmosphere refraction)
position_with_refraction(t, lat, lng, pressure_pa, temp_c) SunPositionResult Position with custom pressure/temperature
altitude(t, lat, lng) float Geometric altitude, before refraction (deg)
is_daytime(t, lat, lng) bool True when geometric altitude > 0 (convenience)
sun_times(day_start, lat, lng) SunTimesResult Sunrise, sunset, transit for a day
sun_times_precise(day_start, lat, lng) SunTimesResult Iterative sunrise/sunset refinement (~10 sec accuracy)
declination_spencer(day_of_year) float Solar declination (Spencer 1971, deg)
equation_of_time_spencer(day_of_year) float Equation of time (Spencer 1971, min)
day_of_year(t) int Day of year (1-366) from time

Atmosphere — SolarAtmosphere

Atmospheric calculations for solar energy.

Static field: SolarAtmosphere::solar_constant = 1361.0 W/m² (Kopp & Lean 2011 TSI value).

Function Returns Description
pressure_from_altitude(alt_m) float ICAO standard atmosphere pressure (Pa)
relative_airmass(apparent_zenith_deg) float Relative optical airmass (Kasten & Young 1989)
absolute_airmass(am, pressure) float Pressure-corrected airmass
extraterrestrial_irradiance(doy) float ETI from Earth-Sun distance (W/m²)
relative_airmass_kasten1966(zenith_deg) float Relative airmass (Kasten 1966)
delta_t(year) float Delta-T (TT-UT1) in seconds
linke_turbidity_estimate(lat, month) float Latitude/month-based Linke turbidity estimate
spectral_mismatch(airmass) float c-Si spectral mismatch correction factor

Irradiance — SolarIrradiance

Irradiance transposition, decomposition, and clear-sky models. All tilt/azimuth/zenith angles in degrees. All irradiance values in W/m².

Clear-sky models

Function Returns Description
clear_sky_at_point(t, lat, lng, alt, tl) ClearSkyResult Convenience: position + clear_sky
clear_sky(apparent_zenith_deg, alt, tl) ClearSkyResult Ineichen/Perez clear-sky model

Angle of incidence

Function Returns Description
aoi(...) float Angle of incidence (deg)
aoi_projection(...) float Cosine of AOI (projection factor)

Beam and diffuse transposition

Function Returns Description
beam_component(dni, ...) float Direct irradiance on tilted surface (W/m²)
isotropic_diffuse(tilt, dhi) float Isotropic sky diffuse (Hottel & Woertz)
klucher_diffuse(...) float Klucher 1979 anisotropic diffuse
hay_davies_diffuse(...) float Hay-Davies 1980 anisotropic diffuse
perez_diffuse(..., airmass) float Perez 1990 anisotropic diffuse (industry standard)
ground_diffuse(tilt, ghi, albedo) float Ground-reflected diffuse

Diffuse model comparison

Model Complexity Best for
Isotropic Simplest Quick estimates, overcast skies
Klucher Medium Clear to partly cloudy skies
Hay-Davies Medium General purpose
Perez 1990 Highest Highest accuracy, industry standard

Combined plane-of-array

Function Returns Description
poa_global(...) PoaResult Combined POA (beam + isotropic + ground)
poa_global_perez(...) PoaResult Combined POA with Perez diffuse model

poa_global() uses isotropic diffuse internally. For higher accuracy, use poa_global_perez() or compose beam_component + perez_diffuse + ground_diffuse manually.

Convenience helpers

Function Returns Description
ideal_ghi_at(t, lat, lng, altitude_m, linke_turbidity) float Clear-sky GHI at a point in time, 0 at night (W/m²)
ideal_daily_wh(from, to, lat, lng, altitude_m, linke_turbidity) float Clear-sky GHI integrated hourly over a time range (Wh/m²)

ideal_ghi_at wraps is_daytime + clear_sky_at_point — returns the theoretical maximum GHI at a point in time, or 0 if the sun is below the horizon. Useful for overlaying a “theoretical max” curve on live radiance charts.

ideal_daily_wh integrates clear-sky GHI from from to to in hourly steps. The caller computes midnight in the desired timezone:

var midnight = time::at(time::now(), CalendarUnit::hours, 0, TimeZone::Europe_Paris);
var wh_m2 = SolarIrradiance::ideal_daily_wh(midnight, time::now(), lat, lng, 0.0, 2.0);

Decomposition (GHI to DNI/DHI)

Function Returns Description
erbs_decomposition(ghi, zen, doy) ClearSkyResult Estimate DNI/DHI from measured GHI (Erbs 1982)
disc_decomposition(ghi, zen, doy, pressure) ClearSkyResult Estimate DNI/DHI from GHI (Maxwell 1987)

Both split measured GHI into DNI and DHI components. DISC uses airmass-dependent correlations and is more accurate for clear-sky conditions. Erbs is simpler and robust across all conditions.

Incidence angle modifiers (IAM)

IAM models quantify optical losses when sunlight hits the glass cover at oblique angles. They return a factor in [0, 1] that multiplies the beam component.

Function Returns Description
iam_ashrae(aoi_deg, b) float ASHRAE incidence angle modifier [0…1]
iam_physical(aoi_deg, n_ar, K, L) float Physical IAM (De Soto 2004, Fresnel) [0…1]
  • iam_ashrae: single empirical parameter b (typically 0.05 for glass).
  • iam_physical: physics-based Fresnel reflection + absorption. Typical values: n_ar=1.526, K=4.0 (1/mm), L=0.002 (mm).

Bifacial and shading

Function Returns Description
bifacial_ground(...) float Rear-side irradiance for bifacial modules (W/m²)
horizon_shading(solar_altitude_deg, solar_azimuth_deg, horizon_azimuths, horizon_elevations) float 1.0 if sun above horizon profile, 0.0 if blocked

Tracking — SolarTracking

Single- and dual-axis tracker geometry. Computes the optimal tracker rotation angle to minimise AOI, with optional backtracking to avoid row-to-row shading.

Function Returns Description
single_axis(zen, az, ax_tilt, ax_az, max_angle, backtrack, gcr) TrackerResult Tracker rotation + resulting surface geometry
dual_axis(apparent_zenith, apparent_azimuth, max_zenith) TrackerResult Dual-axis tracker (always faces sun, clamped to max_zenith)
var pos = SolarPosition::position(t, lat, lng);
var tr = SolarTracking::single_axis(
    pos.apparent_zenith_deg, pos.azimuth_deg,
    0.0, 180.0,  // axis tilt, axis azimuth (N-S horizontal)
    45.0,         // max rotation (deg)
    true, 0.35    // backtrack, GCR
);
// tr.surface_tilt, tr.surface_azimuth -> feed to poa_global / instant_output

PV system output — SolarPv

PV system output estimation. Power in watts, energy in watt-hours.

Instantaneous output

Function Returns Description
orientation_ratio(...) float POA vs horizontal ratio
instant_output(...) PvOutputResult Instantaneous DC power (W, isotropic diffuse)
clear_sky_daily_wh(...) float Full day clear-sky energy (Wh)

Thermal and electrical models

Function Returns Description
cell_temperature(...) float PV cell temperature (°C, Sandia/King model)
dc_to_ac(...) float DC-to-AC inverter conversion (W, PVWatts)
snow_coverage_update(...) float Update snow coverage [0-1] for one hour step

Cell temperature uses the Sandia/King (SAPM) thermal model. Typical coefficients for open-rack glass/glass modules: a=-3.47, b=-0.0594, delta_t=3.

var tc = SolarPv::cell_temperature(poa.poa_global, 25.0, 1.5, -3.47, -0.0594, 3.0);

DC-to-AC uses the PVWatts partial-load efficiency curve with clipping at AC capacity.

var ac = SolarPv::dc_to_ac(dc_power_w, 5000.0, 0.96);
// Returns at most 5000 W (inverter clipping)

Array geometry

Function Returns Description
optimal_tilt(lat, lng, altitude_m, linke_turbidity, albedo) float Optimal fixed tilt for max annual energy (deg, clear-sky)
optimal_tilt_real(lat, lng, altitude_m, linke_turbidity, albedo, azimuth_deg?) float Optimal fixed tilt accounting for NASA cloud factors (deg)
optimal_azimuth(lat) float Hemisphere-aware optimal azimuth (180° north, 0° south)
inter_row_shading(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr) float Inter-row shading factor (0=shaded, 1=unshaded)

optimal_azimuth returns the optimal panel azimuth for a given latitude: 180° (south-facing) in the northern hemisphere, 0° (north-facing) in the southern hemisphere. The equator returns 180°.

optimal_tilt_real uses golden-section search with precomputed Perez coefficients and embedded NASA cloud factors. If azimuth_deg is null, it defaults to the optimal azimuth for the hemisphere.

Simulation

Function Returns Description
simulate(...) Array<SimulationPoint> Realistic simulation with NASA cloud factors
simulate_clear_sky(...) Array<SimulationPoint> Theoretical maximum (clear sky)

Both functions compute hourly internally, then aggregate into the requested SimulationResolution (hourly, daily, weekly, monthly, yearly).

simulate uses the embedded NASA POWER satellite data to automatically derate clear-sky irradiance by realistic cloud factors. Override with monthly_factors or measured_ghi via SimulationConfig. When both are null, the embedded NASA tensor is used.

Each SimulationPoint includes wh_max (clear-sky theoretical maximum) and wh_std (standard deviation from cloud variability). When aggregating, wh_std uses day-aware quadrature: linear summation within days (hours are correlated) and quadrature (root-sum-of-squares) across days (independent).

simulate_clear_sky gives theoretical maximum — no cloud derating.

// One-year simulation with default parameters
var from = time::new(1735689600, DurationUnit::seconds);
var to   = time::new(1767225600, DurationUnit::seconds);

// Realistic (NASA cloud factors)
var real = SolarPv::simulate(
    33.89, 35.50, 0.0, 5000.0, 30.0, 180.0,
    from, to, SimulationResolution::monthly, null
);

// With custom config
var config = SimulationConfig {
    efficiency: 0.80,
    ambient_temp_c: 30.0,
    ac_capacity_w: 4500.0,
    monthly_factors: Array<float> {
        0.55, 0.58, 0.62, 0.70, 0.78, 0.85,
        0.92, 0.90, 0.80, 0.70, 0.60, 0.52,
    },
};
var custom = SolarPv::simulate(
    33.89, 35.50, 0.0, 5000.0, 30.0, 180.0,
    from, to, SimulationResolution::weekly, config
);

// Weekly resolution for finer granularity
var weekly = SolarPv::simulate(
    33.89, 35.50, 0.0, 5000.0, 30.0, 180.0,
    from, to, SimulationResolution::weekly, null
);
// Access uncertainty range
for (i, pt in real) {
    var p10 = pt.wh - 1.28 * pt.wh_std;  // pessimistic (P10)
    var p90 = pt.wh + 1.28 * pt.wh_std;  // optimistic (P90)
    println("${pt.at}: ${pt.wh} Wh [${p10} - ${p90}], max: ${pt.wh_max}");
}

Aggregation

Function Returns Description
aggregate(points, from, to) Array<SimulationPoint> Aggregate SimulationPoints into coarser buckets
redistribute(values, from, to) Array<float> Redistribute pre-aggregated Wh values into coarser buckets

Both functions take a source SimulationResolution and a target (coarser) resolution. The target must be strictly coarser than the source; passing the same or a finer resolution returns an empty array.

aggregate works on Array<SimulationPoint> output from simulate() — it aggregates all three channels: wh and wh_max by linear sum, wh_std by day-aware quadrature (linear within days, root-sum-of-squares across days). redistribute works on raw Array<float> from any source (external weather APIs, metering, etc.).

Supported paths (always coarser):

from → to in out
hourly → daily 8760 365
hourly → weekly 8760 52
hourly → monthly 8760 12
hourly → yearly 8760 1
daily → weekly 365 52
daily → monthly 365 12
daily → yearly 365 1
weekly → monthly 52 12
weekly → yearly 52 1
monthly → yearly 12 1

For weekly → monthly, fractional week overlap is computed proportionally so total energy is conserved exactly.

// Simulate daily, then aggregate for charts
var from = time::new(1672531200, DurationUnit::seconds);
var to   = time::new(1704067199, DurationUnit::seconds);
var daily = SolarPv::simulate(33.89, 35.50, 0.0, 5000.0, 30.0, 180.0,
    from, to, SimulationResolution::daily, null);

var weekly  = SolarPv::aggregate(daily, SimulationResolution::daily, SimulationResolution::weekly);
// weekly[0].wh, weekly[0].wh_max, weekly[0].wh_std
var monthly = SolarPv::aggregate(daily, SimulationResolution::daily, SimulationResolution::monthly);
var yearly  = SolarPv::aggregate(daily, SimulationResolution::daily, SimulationResolution::yearly);

// Redistribute external weekly data to monthly
var ext_weekly = Array<float> { /* 52 values from weather API */ };
var ext_monthly = SolarPv::redistribute(ext_weekly, SimulationResolution::weekly, SimulationResolution::monthly);

Climate data — SolarData

Embedded climate data lookups and external data fetchers.

Function Returns Description
estimate_monthly_factors(lat, lng) Array<float> 12 monthly factors from embedded NASA data
estimate_weekly_factors(lat, lng) Array<float> 52 weekly factors from embedded NASA data
estimate_factor(lat, lng, day_of_year) float Single factor for a given day of year
estimate_monthly_cloud(lat, lng) Array<CloudFactor> 12 monthly factors with std from embedded NASA data
estimate_weekly_cloud(lat, lng) Array<CloudFactor> 52 weekly factors with std from embedded NASA data
estimate_cloud(lat, lng, day_of_year) CloudFactor Single factor with std for a given day
fetch_nasa_power_factors(lat, lng) Array<float> 12 factors from NASA POWER API (network)

Factors are in [0, 1] where 1.0 means clear sky and lower values indicate cloud attenuation. estimate_* functions use the embedded tensor (instant, no network). fetch_nasa_power_factors calls the NASA POWER REST API for exact site-specific data (requires network, slower, more accurate for a specific location).

NASA POWER embedded data

The library ships a 180x360x52 tensor of weekly cloud/weather factors derived from NASA POWER satellite observations (2001-2025 climatology). At 1 degree resolution globally, it provides instant factor lookups for any (lat, lng) without network access. Trilinear interpolation across latitude, longitude, and week-of-year. Both mean and standard deviation are embedded; std uses [0, 0.5] quantization for better resolution in the typical variability range.

The data is zlib-compressed in the binary (~374 KB) and decompressed once at library load time.

Regenerate with:

python3 solar/script/generate_nasa_factors.py

Composing advanced pipelines

The library exposes building blocks that can be composed for scenarios beyond the built-in simulate. For example, combining tracker geometry with Perez diffuse and cell temperature:

fn advanced_output(
    t: time, lat: float, lng: float,
    capacity_w: float, ambient_c: float, wind_ms: float
): float {
    var pos = SolarPosition::position(t, lat, lng);
    if (pos.apparent_zenith_deg >= 90.0) { return 0.0; }

    // Single-axis tracker
    var tr = SolarTracking::single_axis(
        pos.apparent_zenith_deg, pos.azimuth_deg,
        0.0, 180.0, 50.0, true, 0.35
    );

    // Clear-sky irradiance
    var cs = SolarIrradiance::clear_sky(pos.apparent_zenith_deg, 0.0, 2.0);

    // Perez diffuse (more accurate than isotropic)
    var doy = SolarPosition::day_of_year(t);
    var am = SolarAtmosphere::relative_airmass(pos.apparent_zenith_deg);
    var dni_extra = SolarAtmosphere::extraterrestrial_irradiance(doy);
    var beam = SolarIrradiance::beam_component(
        cs.dni, tr.surface_tilt, tr.surface_azimuth,
        pos.apparent_zenith_deg, pos.azimuth_deg
    );
    var diffuse = SolarIrradiance::perez_diffuse(
        tr.surface_tilt, tr.surface_azimuth,
        pos.apparent_zenith_deg, pos.azimuth_deg,
        cs.dhi, cs.dni, dni_extra, am
    );
    var ground = SolarIrradiance::ground_diffuse(tr.surface_tilt, cs.ghi, 0.25);
    var poa = beam + diffuse + ground;

    // Cell temperature derating
    var tc = SolarPv::cell_temperature(poa, ambient_c, wind_ms, -3.47, -0.0594, 3.0);
    var temp_coeff = -0.0047; // typical for crystalline Si
    var temp_factor = 1.0 + temp_coeff * (tc - 25.0);

    // DC power
    var dc = poa / 1000.0 * capacity_w * 0.85 * temp_factor;
    if (dc < 0.0) { dc = 0.0; }
    if (dc > capacity_w) { dc = capacity_w; }

    // AC power via inverter
    return SolarPv::dc_to_ac(dc, capacity_w * 0.9, 0.96);
}

Features

  • Solar position — full ephemeris (altitude, azimuth, declination, hour angle, refraction) with nutation and parallax corrections, plus fast altitude() and is_daytime().
  • Atmosphere — pressure from altitude, airmass (Kasten & Young 1989), extraterrestrial irradiance (Spencer 1971), Linke turbidity estimation.
  • Irradiance — angle of incidence, beam/diffuse transposition (isotropic, Klucher, Hay-Davies, Perez 1990), Ineichen/Perez clear-sky model, Erbs and DISC decomposition from measured GHI, ASHRAE and physical (De Soto 2004) incidence angle modifiers, ideal GHI at a point in time, daily clear-sky GHI integration.
  • Tracking — single-axis and dual-axis tracker geometry with backtracking.
  • PV system — instantaneous output, daily energy, cell temperature (Sandia/King model), DC-to-AC inverter (PVWatts model), full simulation with hourly/daily/weekly/monthly/yearly resolution, post-simulation aggregation and redistribution across resolutions, optimal tilt (clear-sky and cloud-aware), optimal azimuth.
  • Bifacial — rear-side ground irradiance via infinite-sheds view-factor model.
  • Spectral correction — airmass-based c-Si spectral mismatch factor.
  • Snow coverage — hourly snow accumulation/melt model for cold climates.
  • Horizon shading — azimuth/elevation profile-based sun obstruction check.
  • NASA POWER climate data — embedded 180x360x52 satellite-derived cloud factor tensor (1 deg resolution, 2001-2025 climatology) with cloud factor uncertainty (mean + std). estimate_monthly_factors(lat, lng) returns 12 monthly factors instantly, no network. estimate_monthly_cloud(lat, lng) returns CloudFactor objects with both mean and standard deviation. Alternatively, fetch_nasa_power_factors(lat, lng) calls the NASA POWER API for exact site-specific data.

References

  • Solar position: Meeus-derived ephemeris, SolTrack (refraction)
  • Clear-sky: Ineichen/Perez model
  • Diffuse transposition: Isotropic (Hottel & Woertz), Klucher 1979, Hay-Davies 1980, Perez 1990
  • Airmass: Kasten & Young 1989, Kasten 1966
  • Declination/EoT: Spencer 1971
  • Decomposition: Erbs et al. 1982, DISC (Maxwell 1987)
  • IAM: ASHRAE, De Soto 2004 (physical/Fresnel)
  • Cell temperature: Sandia/King (SAPM) model
  • Inverter: PVWatts efficiency model
  • Tracker: pvlib-python single-axis algorithm
  • NASA POWER: https://power.larc.nasa.gov/
  • Spectral mismatch: King, Boyson, Kratochvil (Sandia, 2004)
  • Snow cover: Marion, Schaefer, Caine, Sanchez (2013)
  • Nutation: IAU 1980 (1-term approximation via Moon’s ascending node)
  • Parallax: standard topocentric correction (8.794 arcsec horizontal parallax)
  • Delta-T: Espenak & Meeus (2006) polynomial approximations