In this page
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 parameterb(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()andis_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)returnsCloudFactorobjects 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