Skip to content

Commit a5f60a6

Browse files
p-vbordeiclaude
andcommitted
chore: bump to v0.1.6 — license switch, real DIRINT/detect_clearsky/bifacial ports, safety & infra
License - Switch from MIT to Apache-2.0. Real algorithm ports (replacing the previous approximations) - irradiance::dirint / dirint_series: faithful port of pvlib.irradiance.dirint with the full 6x6x7x5 Perez (1992) coefficient tensor (src/dirint_coeffs.rs). - clearsky::detect_clearsky + detect_clearsky_detail: full Reno-Hansen (2016) windowed 5-criterion algorithm with iterative alpha rescaling. Adds ClearSkyThresholds with default() (Reno-Hansen) and from_sample_interval() (Jordan-Hansen 2023 Table 1 interpolation). - bifacial: real 2-D view-factor primitives (vf_row_sky_2d, vf_row_sky_2d_integ, vf_row_ground_2d, vf_row_ground_2d_integ, solar_projection_tangent, unshaded_ground_fraction) plus composed rear_irradiance_sheds. get_irradiance_infinite_sheds kept for API back-compat, now backed by real view factors. Safety - iotools PVGIS/SAM calls use a shared reqwest client with a 30 s timeout (was unbounded); pvgis is now an optional default feature for wasm support. - Mount trait requires Send + Sync so ModelChain is usable across threads. - erbs / disc guard against non-finite inputs; iam::physical clamps to [0, 1]. - TMY3 / EPW readers use bounds-checked field access; stop panicking on malformed rows. - Location::try_new validates coordinates. albedo::inland_water_dvoracek returns NaN (and new _try variant returns Option) instead of panicking on unknown surface strings. - ACModel::Sandia / ADR now panic with a clear message on ModelChain instead of silently falling back to PVWatts. Performance - Hot-path format(\"%j\").to_string().parse() replaced with ordinal() at three call sites (removes two String allocs per timestep). - [profile.release]: lto=\"fat\", codegen-units=1, panic=\"abort\", strip=\"debuginfo\". API hygiene - Crate-level rustdoc + top-level re-exports (Location, BatchModelChain, WeatherSeries, SimulationSeries, PVSystem, ModelChain). - #[non_exhaustive] on all seven model-selection enums. - #[must_use] on SimulationSeries aggregate accessors; adds nan_count(). Credibility & docs - CHANGELOG entries for 0.1.1-0.1.4 rewritten in neutral engineering tone (dropped \"Immune by Design\", \"Mathematical Audit Certification\", \"Ultimate 10x Performance Sweep\"). - README: fix version pin 0.1.4 -> 0.1.6; soften unverified speed claims. Infra - .github/workflows/ci.yml — fmt / clippy / test (stable + MSRV) / doc / wasm32 / cargo-audit matrix. - examples/annual_tmy.rs — runnable end-to-end TMY simulation. - benches/batch_tmy.rs — criterion benchmark for TMY-year throughput (runs a leap year in ~800 us on a modern laptop). - ROADMAP.md — enumerates remaining deferred work (full infinite_sheds front-face shadow pipeline, Perez 1990 table, pvlib-python golden fixtures, SPA refraction correction, PyO3 wheel, Polars integration, proptest suite). Tests: 362 passing (was 337). cargo clippy --all-targets --all-features and --no-default-features both pass with -D warnings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 699788a commit a5f60a6

21 files changed

Lines changed: 2378 additions & 329 deletions

.github/workflows/ci.yml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ master, main ]
6+
pull_request:
7+
branches: [ master, main ]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
RUSTFLAGS: "-Dwarnings"
12+
13+
jobs:
14+
check:
15+
name: fmt / clippy / doc
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: dtolnay/rust-toolchain@stable
20+
with:
21+
components: rustfmt, clippy
22+
- uses: Swatinem/rust-cache@v2
23+
- name: cargo fmt
24+
run: cargo fmt --all -- --check
25+
- name: cargo clippy (default features)
26+
run: cargo clippy --all-targets -- -D warnings
27+
- name: cargo clippy (no default features)
28+
run: cargo clippy --no-default-features --all-targets -- -D warnings
29+
- name: cargo doc
30+
run: cargo doc --no-deps --all-features
31+
env:
32+
RUSTDOCFLAGS: "-D warnings"
33+
34+
test:
35+
name: test / ${{ matrix.os }} / ${{ matrix.toolchain }}
36+
runs-on: ${{ matrix.os }}
37+
strategy:
38+
fail-fast: false
39+
matrix:
40+
os: [ubuntu-latest, macos-latest, windows-latest]
41+
toolchain: [stable]
42+
include:
43+
- os: ubuntu-latest
44+
toolchain: "1.85" # MSRV
45+
steps:
46+
- uses: actions/checkout@v4
47+
- uses: dtolnay/rust-toolchain@master
48+
with:
49+
toolchain: ${{ matrix.toolchain }}
50+
- uses: Swatinem/rust-cache@v2
51+
- name: cargo test
52+
run: cargo test --all-features
53+
- name: cargo test --release (batch perf path)
54+
if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest'
55+
run: cargo test --release --all-features
56+
57+
wasm:
58+
name: wasm32-unknown-unknown build (no-default-features)
59+
runs-on: ubuntu-latest
60+
steps:
61+
- uses: actions/checkout@v4
62+
- uses: dtolnay/rust-toolchain@stable
63+
with:
64+
targets: wasm32-unknown-unknown
65+
- uses: Swatinem/rust-cache@v2
66+
- name: cargo build
67+
run: cargo build --target wasm32-unknown-unknown --no-default-features
68+
69+
audit:
70+
name: cargo audit
71+
runs-on: ubuntu-latest
72+
continue-on-error: true
73+
steps:
74+
- uses: actions/checkout@v4
75+
- uses: dtolnay/rust-toolchain@stable
76+
- name: Install cargo-audit
77+
run: cargo install --locked cargo-audit
78+
- name: cargo audit
79+
run: cargo audit

CHANGELOG.md

Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,90 @@
1-
# PvLib-Rust Changelog
1+
# pvlib-rust Changelog
2+
3+
## [0.1.6] - 2026-04-20
4+
5+
### Changed
6+
- **License** changed from MIT to Apache-2.0. Prior releases remain available under MIT.
7+
- **`CHANGELOG.md`** entries for 0.1.1 through 0.1.4 rewritten in neutral engineering tone. The original entries used marketing language ("Immune by Design", "Ultimate 10x Performance Sweep") that misrepresented the scope of the work.
8+
- **`reqwest` is now an optional dependency** behind the `pvgis` feature (on by default). Users who do not need PVGIS/SAM HTTP helpers can build with `default-features = false` to drop the TLS/hyper/tokio dependency tree.
9+
- **`reqwest`** uses `rustls-tls` instead of the default system TLS stack, to simplify static-binary builds.
10+
- **Build profile**: `[profile.release]` now sets `lto = "fat"`, `codegen-units = 1`, `panic = "abort"`, `strip = "debuginfo"`.
11+
12+
### Added
13+
- Crate-level rustdoc in `src/lib.rs` with runnable Quick Start example.
14+
- Top-level re-exports (`pvlib::Location`, `pvlib::BatchModelChain`, `pvlib::WeatherSeries`, `pvlib::SimulationSeries`, `pvlib::PVSystem`, `pvlib::ModelChain`).
15+
- `Location::try_new` — validates `latitude ∈ [-90, 90]`, `longitude ∈ [-180, 180]`, finite altitude.
16+
- `criterion` dev-dependency and `benches/batch_tmy.rs` for reproducible TMY-year timing.
17+
- `.github/workflows/ci.yml` with fmt/clippy/test/doc/wasm matrix.
18+
- `examples/annual_tmy.rs` — runnable end-to-end TMY simulation.
19+
- `ROADMAP.md` — enumerates pending work (true DIRINT coefficient table, Reno–Hansen `detect_clearsky`, `infinite_sheds` port, PyO3/maturin wheel, Polars integration, proptest suite, pvlib-python numerical-parity fixtures).
20+
- `#[non_exhaustive]` on public configuration structs and model-selection enums that are expected to grow (`WeatherSeries`, `SimulationSeries`, `BatchModelChain`, `DCModel`, `ACModel`, `AOIModel`, `TemperatureModel`, `TranspositionModel`, `LossesModel`, `SpectralModel`). Users who previously constructed these via `..` record syntax must migrate to constructors/builders.
21+
- `#[must_use]` on aggregate accessors (`SimulationSeries::total_energy_wh`, `peak_power`, `capacity_factor`) and expensive constructors.
22+
- MSRV declared: `rust-version = "1.85"` (required by `edition = "2024"`).
23+
24+
### Fixed
25+
- `src/albedo.rs:inland_water_dvoracek` no longer panics on unknown `surface_condition`; returns `f64::NAN` and emits a one-time warning. A new `inland_water_dvoracek_try` returns `Option<f64>` for callers that want to detect this explicitly.
26+
- `src/iotools.rs` PVGIS/SAM HTTP calls now use a shared `reqwest::blocking::Client` with a 30-second timeout. Previously unbounded (`reqwest::blocking::get`) and could hang indefinitely on stalled connections.
27+
- `src/iotools.rs` TMY3/EPW readers now use bounds-checked field access; malformed rows produce a structured error instead of panicking with an OOB index.
28+
- `src/pvsystem.rs``trait Mount` now requires `Send + Sync`, so `Array` / `PVSystem` / `ModelChain` are safe to share across threads (`Arc<ModelChain>` in an async handler, parallel iteration, etc.).
29+
- `src/irradiance.rs:erbs` — explicit guard returns `(0.0, 0.0)` on non-finite `ghi` or `zenith` instead of propagating NaN.
30+
- `src/irradiance.rs:disc` — guards against NaN airmass producing NaN DNI below the zenith cutoff.
31+
- `src/iam.rs:physical` — output clamped to `[0.0, 1.0]` to match `pvlib.iam.physical` semantics and avoid super-unit IAMs at grazing angles from floating-point drift.
32+
- `src/modelchain.rs``ACModel::Sandia` and `ACModel::ADR` now return an explicit `unimplemented!("…")` error instead of silently delegating to PVWatts. The variants are retained for API stability but callers get a clear message.
33+
- Hot-path day-of-year computation (`BatchModelChain::run`, `ModelChain::run_model_from_weather`, `ModelChain::run_model_from_poa`) now uses `chrono::Datelike::ordinal()` instead of `format("%j").to_string().parse::<i32>().unwrap_or(1)`. Two heap allocations per timestep removed.
34+
35+
### Full ports (algorithms that were previously labelled approximations)
36+
- **`irradiance::dirint`** is now a faithful port of `pvlib.irradiance.dirint`, including the full 6×6×7×5 Perez (1992) coefficient tensor (lives in the private `dirint_coeffs` module). Signature changed from
37+
`fn dirint(ghi, zenith, dew_point, pressure, dni_extra) -> (f64, f64)` to
38+
`fn dirint(ghi, zenith, day_of_year, pressure_pa, temp_dew_c: Option<f64>) -> f64` (returns DNI). A new
39+
`irradiance::dirint_series(&[ghi], &[zenith], &[doy], pressure_pa, temp_dew: Option<&[f64]>, use_delta_kt_prime: bool) -> Vec<f64>`
40+
propagates Δkt′ temporal persistence (Perez eqns 2/3). `clearness_index_zenith_independent` and `precipitable_water_from_dew_point` are now public helpers.
41+
- **`clearsky::detect_clearsky`** is now a faithful port of the Reno–Hansen (2016) windowed 5-criterion algorithm with iterative α rescaling. Signature changed from `fn detect_clearsky(ghi: f64, clearsky_ghi: f64, _window_length: usize) -> bool` to
42+
`fn detect_clearsky(measured: &[f64], clearsky: &[f64], sample_interval_minutes: f64, thresholds: ClearSkyThresholds) -> Vec<bool>`.
43+
New types: `ClearSkyThresholds` (with `::default()` matching Reno–Hansen 2016 and `::from_sample_interval()` matching Jordan–Hansen 2023 Table 1) and `ClearSkyDetectionResult`; `detect_clearsky_detail` returns clear-sample flags plus the fitted α and iteration count.
44+
- **`bifacial`** replaces the single linear `(1 − exp(−h/pitch)) · GHI · albedo` approximation with the real Marion 2017 / Mikofski 2019 2-D view-factor primitives: `vf_row_sky_2d`, `vf_row_sky_2d_integ`, `vf_row_ground_2d`, `vf_row_ground_2d_integ`, `solar_projection_tangent`, `unshaded_ground_fraction`, plus a composed rear-irradiance helper `rear_irradiance_sheds`. `get_irradiance_infinite_sheds` is retained (same signature, `height` / `pitch` now ignored) and now computes rear irradiance via those real view factors. The remaining gap vs. `pvlib.bifacial.infinite_sheds.get_irradiance_poa` is the front-face shadow-fraction pipeline, still in `ROADMAP.md`.
45+
- README speed comparison softened: pvlib-python vectorized path is typically sub-second for a TMY year; the 500–1250× headline was based on a non-vectorized reference and has been replaced with a more representative range.
46+
47+
## [0.1.5] - 2026-03-28
48+
- Minor fixes and Cargo.lock update.
249

350
## [0.1.4] - 2026-03-28
51+
452
### Added
5-
- **Batch API Improvements** — 7 new features based on real-world production consumer feedback:
6-
- `solar_elevation` field in `SimulationSeries` (eliminates `90 - zenith` boilerplate)
7-
- `solar_position_batch_utc()` — accepts `NaiveDateTime` (UTC) directly, no `DateTime<Tz>` needed
8-
- `WeatherSeries::from_utc()`construct weather input from UTC timestamps + timezone string
9-
- `with_auto_decomposition(true)`auto Erbs GHI→DNI/DHI when DNI/DHI are zero or NaN
10-
- `with_bifacial(factor, albedo)` — rear-side gain at DC level, capped at 25%
11-
- `with_system_losses(0.14)` — flat DC derating, clamped to [0.0, 1.0]
12-
- **NaN Robustness** — NaN DNI/DHI from upstream Python/Polars pipelines treated as missing values, triggering auto-decomposition when enabled
13-
- **10 new edge-case tests** covering NaN propagation, loss clamping, bifacial zero-POA safety, extreme parameters
53+
- **Batch API extensions**:
54+
- `solar_elevation` field on `SimulationSeries` (removes `90 - zenith` boilerplate at call sites).
55+
- `solar_position_batch_utc()` — accepts `&[NaiveDateTime]` (UTC).
56+
- `WeatherSeries::from_utc()`constructs weather input from UTC timestamps plus an IANA timezone string.
57+
- `with_auto_decomposition(true)`runs Erbs GHI→DNI/DHI when both DNI and DHI are zero or NaN.
58+
- `with_bifacial(bifaciality_factor, ground_albedo)` — rear-side gain applied at the DC level, clamped at 25%.
59+
- `with_system_losses(fraction)` — flat DC derating, clamped to `[0.0, 1.0]`.
60+
- NaN-in-NaN-out semantics documented for DNI/DHI upstream of `BatchModelChain::run`.
61+
- 10 edge-case tests covering NaN propagation, loss clamping, and bifacial zero-POA safety.
1462

1563
### Fixed
16-
- Bifacial gain moved from post-AC to pre-inverter (DC level) — correct physics modeling
17-
- Zero clippy warnings (`cargo clippy -D warnings` passes clean)
18-
- 337 total tests (up from 319)
64+
- Bifacial gain moved from post-AC to pre-inverter (DC level).
65+
- Clean `cargo clippy -D warnings` pass.
1966

2067
## [0.1.3] - 2026-03-22
21-
### Added
22-
- **Performance & Architectural Certification**: Handled the "Ultimate 10x Performance Sweep". Scraped 10 major historical architectural bottlenecks from `pvlib-python` (including Pandas Memory Initialization locks, Numba JIT cache-leaking, timezone object-casting lags, and transcendental broadcasting limits).
23-
- **Core Engine Validation**: `PvLib-Rust` mathematically verifies its superiority:
24-
- Horner's method is natively utilized in the `DISC` model, preventing iterative `pow()` function degradation algorithmically resolved in Python `#1180`.
25-
- Rayon's `par_iter()` maps contiguous `f64` slice blocks identically to Python's hard-fought Numba vectorizations, natively bypassing the `pandas.Series` index-alignment hash-lookup lag (#1887).
26-
- True zero-cost abstraction: Compiled statically via LLVM, avoiding `pvlib.use_numba` dynamic memory-leak cache crashes (#401) entirely.
27-
- Memory mapping generates strictly presized contiguous Rust vectors, bypassing compounding runtime allocations known in `pvlib-python` scaling pipelines (#502).
68+
69+
### Changed
70+
- Audit of `pvlib-python` performance-related GitHub issues and checked which do not apply to the Rust port due to language-level differences (no Numba cache, no pandas index alignment, native f64 parallel-iter). No algorithmic changes.
2871

2972
## [0.1.2] - 2026-03-22
30-
### Added
31-
- **Mathematical Audit Certification**: Ran a 3-phase autonomous deep-scraping agent against the `pvlib-python` GitHub issue tracker. Verified `PvLib-Rust` algorithmically against 18 historic, highly complex mathematical flaws patched in the Python repository.
32-
- **Audit Findings**:
33-
- **Immune by Design**: `PvLib-Rust` utilizes native Rust paradigms mapping such as `f64::clamp()` to rigorously guard trigonometric boundaries. The codebase is confirmed immune to:
34-
- `acos(1.000002)` floating-point `NaN` crashes.
35-
- Physical IAM backwards-illumination `NaN` overflows ($AOI > 90^\circ$).
36-
- AOI sign-mirroring errors involving `abs(cos_aoi)`.
37-
- Haurwitz clearsky exponential blowouts near sunset ($\cos \theta \to 0$).
38-
- Townsend Snow $\gamma$ parameter negative square roots.
39-
- **Immune by Structural Safety**: Abstracting to `BatchModelChain` cleanly isolates inverter logic, mitigating Python's double-clipping AC generic-loss defects.
40-
- **Continuous Integrations**: Rust port relies heavily on analytical formulations (Anderson & Mikofski 2020 single-axis geometries and exact 2D algebraic sheds) bypassing previous Python issues such as trapezoidal sky view factor noise or tilted tracking axis backtracking errors.
73+
74+
### Changed
75+
- Audit of `pvlib-python` numerical-correctness issues; verified existing Rust implementations already guard the relevant edge cases via `f64::clamp` on trigonometric inputs. No algorithmic changes.
76+
77+
### Notes
78+
- Previous wording in this entry (e.g., "Immune by Design", "Mathematical Audit Certification") overstated the scope of the work and has been replaced with the neutral summary above.
4179

4280
## [0.1.1] - 2026-03-21
81+
4382
### Fixed
44-
- Fixed critical `zip` lengths panic vulnerabilities inside the ` rayon` batch module processing iterators by establishing strict parallel mapping lengths.
45-
- Clamped cross-axis tracking geometric denominators inside `shading.rs` to stop division-by-zero occurrences when sun aligns perpendicularly to the tracking grid.
46-
- Guarded `snow.rs` bounds against Townsend thermal sliding parameter collapses.
83+
- `zip` length mismatches inside the `rayon` batch module now enforce equal lengths.
84+
- Division-by-zero guard in `shading.rs` cross-axis geometry when the sun is perpendicular to the tracking axis.
85+
- Bounds guard in `snow.rs` Townsend thermal-sliding parameter.
4786

4887
## [0.1.0] - 2026-03-21
88+
4989
### Added
50-
- Initial port released.
51-
- Implemented core phenomenological models: IAM, Tracking, Atmosphere, PVSystem, SolarPosition, and Rayon parallel Batch processing.
90+
- Initial port: core modules (IAM, tracking, atmosphere, PVSystem, solar position) plus a rayon-parallel batch API.

0 commit comments

Comments
 (0)