Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

return results instead of none when CODS soiling signal is small #400

Merged
merged 13 commits into from
Dec 1, 2023
Merged
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ rdtools.egg-info*
.\#*

*.pickle

# ignore vscode settings
.vscode/
73 changes: 39 additions & 34 deletions docs/TrendAnalysis_example_pvdaq4.ipynb

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions rdtools/soiling.py
Original file line number Diff line number Diff line change
Expand Up @@ -1762,11 +1762,11 @@ def run_bootstrap(self,
self.soiling_loss = [0, 0, (1 - result_df.soiling_ratio).mean()]
self.small_soiling_signal = True
self.errors = (
'Soiling signal is small relative to the noise.'
'Iterative decomposition not possible.\n'
'Degradation found by RdTools YoY')
print(self.errors)
return
'Soiling signal is small relative to the noise. '
'Iterative decomposition not possible. '
'Degradation found by RdTools YoY.')
warnings.warn(self.errors)
return self.result_df, self.degradation, self.soiling_loss
self.small_soiling_signal = False

# Aggregate all bootstrap samples
Expand Down Expand Up @@ -2507,7 +2507,8 @@ def _make_seasonal_samples(list_of_SCs, sample_nr=10, min_multiplier=0.5,
''' Generate seasonal samples by perturbing the amplitude and the phase of
a seasonal components found with the fitted CODS model '''
samples = pd.DataFrame(index=list_of_SCs[0].index,
columns=range(int(sample_nr*len(list_of_SCs))))
columns=range(int(sample_nr*len(list_of_SCs))),
dtype=float)
# From each fitted signal, we will generate new seaonal components
for i, signal in enumerate(list_of_SCs):
# Remove beginning and end of signal
Expand Down
2 changes: 1 addition & 1 deletion rdtools/test/bootstrap_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'''Bootstrap module tests.'''

from rdtools.bootstrap import _construct_confidence_intervals,\
from rdtools.bootstrap import _construct_confidence_intervals, \
_make_time_series_bootstrap_samples
from rdtools.degradation import degradation_year_on_year

Expand Down
10 changes: 10 additions & 0 deletions rdtools/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ def cods_normalized_daily(cods_normalized_daily_wo_noise):
return cods_normalized_daily


@pytest.fixture()
def cods_normalized_daily_small_soiling(cods_normalized_daily_wo_noise):
N = len(cods_normalized_daily_wo_noise)
np.random.seed(1977)
noise = 1 + 0.02 * (np.random.rand(N) - 0.5)
cods_normalized_daily_small_soiling = cods_normalized_daily_wo_noise.apply(
lambda row: 1-(1-row)*0.1) * noise
return cods_normalized_daily_small_soiling


# %% Availability fixtures

ENERGY_PARAMETER_SPACE = list(itertools.product(
Expand Down
57 changes: 35 additions & 22 deletions rdtools/test/soiling_cods_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ def test_iterative_signal_decomposition(cods_normalized_daily):
cods = soiling.CODSAnalysis(cods_normalized_daily)
df_out, results_dict = \
cods.iterative_signal_decomposition()
assert 0.080641 == pytest.approx(results_dict['degradation'], abs=1e-6),\
assert 0.080641 == pytest.approx(results_dict['degradation'], abs=1e-6), \
'Degradation rate different from expected value'
assert 3.305136 == pytest.approx(results_dict['soiling_loss'], abs=1e-6),\
assert 3.305136 == pytest.approx(results_dict['soiling_loss'], abs=1e-6), \
'Soiling loss different from expected value'
assert 0.999359 == pytest.approx(results_dict['residual_shift'], abs=1e-6),\
assert 0.999359 == pytest.approx(results_dict['residual_shift'], abs=1e-6), \
'Residual shift different from expected value'
assert 0.008144 == pytest.approx(results_dict['RMSE'], abs=1e-6),\
assert 0.008144 == pytest.approx(results_dict['RMSE'], abs=1e-6), \
'RMSE different from expected value'
assert not results_dict['small_soiling_signal'], \
'Small soiling signal assertion different from expected value'
assert 7.019626e-11 == pytest.approx(results_dict['adf_res'][1], abs=1e-6),\
assert 7.019626e-11 == pytest.approx(results_dict['adf_res'][1], abs=1e-6), \
'p-value of Augmented Dickey-Fuller test different from expected value'

# Check result dataframe
Expand All @@ -31,10 +31,10 @@ def test_iterative_signal_decomposition(cods_normalized_daily):
'seasonal_component', 'degradation_trend', 'total_model', 'residuals']
actual_columns = df_out.columns.values
for x in actual_columns:
assert x in expected_columns,\
assert x in expected_columns, \
"'{}' not an expected column in result_df]".format(x)
for x in expected_columns:
assert x in actual_columns,\
assert x in actual_columns, \
"'{}' was expected as a column, but not in result_df".format(x)
assert isinstance(df_out, pd.DataFrame), 'result_df not a dataframe'
expected_means = pd.Series({'soiling_ratio': 0.9669486267086722,
Expand All @@ -48,7 +48,7 @@ def test_iterative_signal_decomposition(cods_normalized_daily):
['soiling_ratio', 'soiling_rates', 'cleaning_events',
'seasonal_component', 'degradation_trend', 'total_model', 'residuals']]
pd.testing.assert_series_equal(expected_means, df_out.mean(),
check_exact=False, check_less_precise=True)
check_exact=False, rtol=1e-3)


def test_iterative_signal_decomposition_with_nan_interval(cods_normalized_daily):
Expand All @@ -59,17 +59,17 @@ def test_iterative_signal_decomposition_with_nan_interval(cods_normalized_daily)
cods = soiling.CODSAnalysis(normalized_corrupt)
df_out, results_dict = \
cods.iterative_signal_decomposition()
assert -0.004968 == pytest.approx(results_dict['degradation'], abs=1e-5),\
assert -0.004968 == pytest.approx(results_dict['degradation'], abs=1e-5), \
'Degradation rate different from expected value'
assert 3.232171 == pytest.approx(results_dict['soiling_loss'], abs=1e-5),\
assert 3.232171 == pytest.approx(results_dict['soiling_loss'], abs=1e-5), \
'Soiling loss different from expected value'
assert 1.000108 == pytest.approx(results_dict['residual_shift'], abs=1e-5),\
assert 1.000108 == pytest.approx(results_dict['residual_shift'], abs=1e-5), \
'Residual shift different from expected value'
assert 0.008184 == pytest.approx(results_dict['RMSE'], abs=1e-5),\
assert 0.008184 == pytest.approx(results_dict['RMSE'], abs=1e-5), \
'RMSE different from expected value'
assert not results_dict['small_soiling_signal'], \
'Small soiling signal assertion different from expected value'
assert 1.230754e-8 == pytest.approx(results_dict['adf_res'][1], abs=1e-6),\
assert 1.230754e-8 == pytest.approx(results_dict['adf_res'][1], abs=1e-6), \
'p-value of Augmented Dickey-Fuller test different from expected value'

# Check result dataframe
Expand All @@ -85,21 +85,21 @@ def test_iterative_signal_decomposition_with_nan_interval(cods_normalized_daily)
['soiling_ratio', 'soiling_rates', 'cleaning_events',
'seasonal_component', 'degradation_trend', 'total_model', 'residuals']]
pd.testing.assert_series_equal(expected_means, df_out.mean(),
check_exact=False, check_less_precise=True)
check_exact=False, rtol=1e-3)


def test_soiling_cods(cods_normalized_daily):
''' Test the CODS algorithm with fixed test case and 16 repetitions'''
reps = 16
np.random.seed(1977)
sr, sr_ci, deg, deg_ci, result_df = soiling.soiling_cods(cods_normalized_daily, reps=reps)
assert 0.962207 == pytest.approx(sr, abs=0.5),\
assert 0.962207 == pytest.approx(sr, abs=0.5), \
'Soiling ratio different from expected value'
assert np.array([0.96662419, 0.95692131]) == pytest.approx(sr_ci, abs=0.5),\
assert np.array([0.96662419, 0.95692131]) == pytest.approx(sr_ci, abs=0.5), \
'Confidence interval of SR different from expected value'
assert 0.09 == pytest.approx(deg, abs=0.5),\
assert 0.09 == pytest.approx(deg, abs=0.5), \
'Degradation rate different from expected value'
assert np.array([-0.17143952, 0.39313724]) == pytest.approx(deg_ci, abs=0.5),\
assert np.array([-0.17143952, 0.39313724]) == pytest.approx(deg_ci, abs=0.5), \
'Confidence interval of degradation rate different from expected value'

# Check result dataframe
Expand All @@ -111,13 +111,26 @@ def test_soiling_cods(cods_normalized_daily):
'model_high']
actual_summary_columns = result_df.columns.values
for x in actual_summary_columns:
assert x in expected_summary_columns,\
assert x in expected_summary_columns, \
"'{}' not an expected column in result_df]".format(x)
for x in expected_summary_columns:
assert x in actual_summary_columns,\
assert x in actual_summary_columns, \
"'{}' was expected as a column, but not in result_df".format(x)


def test_soiling_cods_small_signal(cods_normalized_daily_small_soiling):
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added the CODS small signal test

''' Test the CODS algorithm with small soiling signal'''
reps = 16
np.random.seed(1977)
warn_small_signal = (
'Soiling signal is small relative to the noise. '
'Iterative decomposition not possible. '
'Degradation found by RdTools YoY.')

with pytest.warns(UserWarning, match=warn_small_signal):
soiling.soiling_cods(cods_normalized_daily_small_soiling, reps=reps)


def test_Kalman_filter_for_SR(cods_normalized_daily):
'''Test the Kalman Filter method in CODS'''
cods = soiling.CODSAnalysis(cods_normalized_daily)
Expand All @@ -131,10 +144,10 @@ def test_Kalman_filter_for_SR(cods_normalized_daily):
'soiling_rates', 'cleaning_events', 'days_since_ce']
actual_columns = dfk.columns.values
for x in actual_columns:
assert x in expected_columns,\
assert x in expected_columns, \
"'{}' not an expected column in Kalman Filter results]".format(x)
for x in expected_columns:
assert x in actual_columns,\
assert x in actual_columns, \
"'{}' was expected as a column, but not in Kalman Filter results".format(x)
assert Ps.shape == (732, 2, 2), "Shape of array of covariance matrices (Ps) not as expected"

Expand Down
Loading