Skip to content

scaling.wvm: return unchanged signal for a single site - #2896

Merged
cwhanse merged 7 commits into
pvlib:mainfrom
bfs2021:fix/wvm-single-site
Oct 20, 2026
Merged

cwhanse merged 7 commits into
pvlib:mainfrom
bfs2021:fix/wvm-single-site

Conversation

@bfs2021

@bfs2021 bfs2021 commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Problem

pvlib.scaling.wvm returns an entirely NaN output for a layout with one coordinate pair. See #2895 for the full report, including the float-precision analysis (0.29 * 100 == 28.999...-style rounding is a separate symptom of the same rounder).

Root cause

With a single position, scipy.spatial.distance.pdist returns an empty distance array, so n_pairs = 0 and the effective-size objective has two roots (x=0 and x=1). The optimizer converges to n_dist = 0, which makes the Eq. 6 denominator 2 * sum(rho) + n_dist equal to zero — dividing by zero yields NaN for every timescale, and the reconstructed signal is all NaN.

Fix

_compute_vr now returns a variability reduction factor of 1 for every timescale when positions contains a single site. Per the WVM equations, one site has no pairwise correlations and therefore no variability reduction, so wvm passes the input signal through unchanged — the behavior the issue describes as expected.

Testing

  • test_compute_vr_single_site: _compute_vr with one position returns all ones for every timescale.
  • test_wvm_single_site: wvm with one position returns the input signal unchanged (previously all NaNs).
  • pytest tests/test_scaling.py → 18 passed (16 existing + 2 new), Python 3.12, Windows.

Fixes #2895

Checklist

  • Closes BUG: scaling.wvm returns all NaNs for a single-site layout #2895
  • I am familiar with the contributing guidelines
  • I attest that all AI-generated material has been vetted for accuracy and is in compliance with the pvlib license
  • Tests added
  • Adds a "what's new" entry in docs/sphinx/source/whatsnew/v0.16.2.rst (:issue:2895, :pull:2896, by :ghuser:bfs2021)
  • New code is fully documented. (no public API change; the single-site early return is commented in code with a link to this issue)
  • Pull request is nearly complete and ready for detailed review.

With one position, pdist returns an empty distance array and the
effective-size optimizer converges to n_dist=0, so _compute_vr divided
by zero and scaling.wvm returned an all-NaN signal. A single site has no
variability reduction, so _compute_vr now returns a reduction factor of
1 for every timescale and wvm passes the input through unchanged.

Fixes pvlib#2895

Signed-off-by: bfs2021 <101915083+bfs2021@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

Hey @bfs2021! 🎉

Thanks for opening your first pull request! We appreciate your
contribution. Please ensure you have reviewed and understood the
contributing guidelines.

If AI is used for any portion of this PR, you must vet the content
for technical accuracy.

Finally, be sure the PR description includes the PR
checklist,
and complete the items you are able to.

Signed-off-by: bfs2021 <101915083+bfs2021@users.noreply.github.com>
@kandersolar kandersolar added this to the v0.16.2 milestone Oct 15, 2026

@kandersolar kandersolar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked the bug fix yet, but at least the what's new entry can be edited

Comment thread docs/sphinx/source/whatsnew/v0.16.2.rst Outdated
* :py:func:`pvlib.tracking.calc_surface_orientation` returns scalar when input
is scalar (:issue:`2881`, :pull:`2882`)
* :py:func:`pvlib.scaling.wvm` no longer returns all NaNs when ``positions``
contains a single site (:issue:`2895`, :pull:`2896`) by :ghuser:`bfs2021`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
contains a single site (:issue:`2895`, :pull:`2896`) by :ghuser:`bfs2021`
contains a single site (:issue:`2895`, :pull:`2896`)

Comment thread docs/sphinx/source/whatsnew/v0.16.2.rst Outdated
Move the contributor credit to the Contributors section per the
what's-new convention, and list bfs2021 there.

Signed-off-by: bfs2021 <101915083+bfs2021@users.noreply.github.com>
@bfs2021

bfs2021 commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Both whatsnew suggestions applied (fe02197) — the credit moved to the Contributors section and the bullet now ends with the issue/pull references.

On the test (windows-latest, 3.14, conda) failure: both attempts died while building the environment, not in the test suite — micromamba.exe exits with 0xC0000005 (access violation) and the activation step then can't find test_env. That's the same env-setup crash other PRs have hit on this runner; a rerun should clear it.

Note: the run also confirms the "If AI is used for any portion of this PR, you must vet the content" note — this PR was developed with AI assistance and I've vetted the change and its tests.

@cwhanse cwhanse left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use this PR to clarify the positions input. Although the current code parses a list (or array) of lists, e.g. positions=[[x1, y1], [x2, y2], ...], that ought not to be encouraged, since the downstream function scipy.distance.pdist` is looking for a 2d array of shape (N, ndim) where ndim is the number of coordinates for each point (2 in our case). The comment is also a little unclear regarding what a position means.

Comment thread pvlib/scaling.py Outdated
Comment thread pvlib/scaling.py Outdated
Comment thread pvlib/scaling.py Outdated
Comment on lines 96 to 100

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Match the docstring of wvm

- positions is documented as an (N, 2) array of (x, y) coordinates in
  both wvm and _compute_vr, which is the shape pdist consumes.
- The single-site guard is len(pos) == 1 now: an empty array is out of
  scope and falls through to produce an invalid result.

Signed-off-by: bfs2021 <101915083+bfs2021@users.noreply.github.com>
@bfs2021

bfs2021 commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review — applied in 8f64f86:

  • positions is now documented as an (N, 2) array of (x, y) coordinates in both wvm and _compute_vr, which is the shape pdist consumes, and the wording says what a position is (easting/northing of the PV systems in meters).
  • The guard is len(pos) == 1 now. An empty array falls through and produces an invalid result on its own, as you noted — left out of scope here.

tests/test_scaling.py passes (18/18).

Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com>
Comment thread docs/sphinx/source/whatsnew/v0.16.2.rst Outdated
@cwhanse

cwhanse commented Oct 20, 2026

Copy link
Copy Markdown
Member

thanks @bfs2021

@cwhanse
cwhanse merged commit 0c6ab77 into pvlib:main Oct 20, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: scaling.wvm returns all NaNs for a single-site layout

3 participants