Skip to content

Commit

Permalink
Implements closed-form solution for optimization problem in clearsky.…
Browse files Browse the repository at this point in the history
… Updates test.
  • Loading branch information
Andrew Godbehere committed Sep 19, 2024
1 parent 5d23f14 commit 94259c5
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 36 deletions.
5 changes: 5 additions & 0 deletions docs/sphinx/source/whatsnew/v0.11.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Enhancements
* Added function for calculating wind speed at different heights,
:py:func:`pvlib.atmosphere.windspeed_powerlaw`.
(:issue:`2118`, :pull:`2124`)
* Implemented closed-form solution for alpha in detect_clearsky, obviating
the call to scipy.optimize that was prone to runtime errors and minimizing
computation, :py:func:`pvlib.clearsky.detect_clearsky`. Resolves :issue:`2712`.


Bug fixes
~~~~~~~~~
Expand Down Expand Up @@ -74,3 +78,4 @@ Contributors
* Marcos R. Escudero (:ghuser:`marc-resc`)
* Bernat Nicolau (:ghuser:`BernatNicolau`)
* Eduardo Sarquis (:ghuser:`EduardoSarquis`)
* Andrew B Godbehere (:ghuser:`agodbehere`)
28 changes: 7 additions & 21 deletions pvlib/clearsky.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
to calculate clear sky GHI, DNI, and DHI.
"""

import calendar
import os
from collections import OrderedDict
import calendar

import h5py
import numpy as np
import pandas as pd
from scipy.optimize import minimize_scalar
from scipy.linalg import hankel
import h5py

from pvlib import atmosphere, tools
from pvlib.tools import _degrees_to_index
Expand Down Expand Up @@ -874,24 +873,11 @@ def detect_clearsky(measured, clearsky, times=None, infer_limits=False,
clear_meas = meas[clear_samples]
clear_clear = clear[clear_samples]

def rmse(alpha):
return np.sqrt(np.mean((clear_meas - alpha*clear_clear)**2))

optimize_result = minimize_scalar(rmse)
if not optimize_result.success:
try:
message = "Optimizer exited unsuccessfully: " \
+ optimize_result.message
except AttributeError:
message = "Optimizer exited unsuccessfully: \
No message explaining the failure was returned. \
If you would like to see this message, please \
update your scipy version (try version 1.8.0 \
or beyond)."
raise RuntimeError(message)

else:
alpha = optimize_result.x
# Compute arg min of MSE between model and observations
C = (clear_clear**2).sum()
if not (pd.isna(C) or C == 0): # safety check
# only update alpha if C is strictly positive
alpha = (clear_meas * clear_clear).sum() / C

if round(alpha*10000) == round(previous_alpha*10000):
break
Expand Down
29 changes: 14 additions & 15 deletions pvlib/tests/test_clearsky.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
from collections import OrderedDict

import numpy as np
from numpy import nan
import pandas as pd
import pytz
from scipy.linalg import hankel

import pytest
import pytz
from numpy import nan
from numpy.testing import assert_allclose
from .conftest import assert_frame_equal, assert_series_equal
from scipy.linalg import hankel

from pvlib import atmosphere, clearsky, irradiance, solarposition
from pvlib.location import Location
from pvlib import clearsky
from pvlib import solarposition
from pvlib import atmosphere
from pvlib import irradiance

from .conftest import DATA_DIR
from .conftest import DATA_DIR, assert_frame_equal, assert_series_equal


def test_ineichen_series():
Expand Down Expand Up @@ -677,12 +672,16 @@ def test_detect_clearsky_not_enough_data(detect_clearsky_data):
with pytest.raises(ValueError, match='have at least'):
clearsky.detect_clearsky(expected['GHI'], cs['ghi'], window_length=60)


def test_detect_clearsky_optimizer_failed(detect_clearsky_data):
@pytest.mark.parametrize("window_length", [5, 10, 15, 20, 25])
def test_detect_clearsky_optimizer_not_failed(
detect_clearsky_data,
window_length
):
expected, cs = detect_clearsky_data
with pytest.raises(RuntimeError, match='Optimizer exited unsuccessfully'):
clearsky.detect_clearsky(expected['GHI'], cs['ghi'], window_length=15)

clear_samples = clearsky.detect_clearsky(
expected["GHI"], cs["ghi"], window_length=window_length
)
assert isinstance(clear_samples, pd.Series)

@pytest.fixture
def detect_clearsky_helper_data():
Expand Down

0 comments on commit 94259c5

Please sign in to comment.