Skip to content

Commit

Permalink
implemented mode -1
Browse files Browse the repository at this point in the history
  • Loading branch information
eccoope committed Apr 29, 2024
1 parent 725240f commit 8b309cb
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 45 deletions.
50 changes: 22 additions & 28 deletions docs/examples/snow-detection/snow-mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import numpy as np
import re
import pvlib
from matplotlib import colormaps as cm
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter
import matplotlib.patches as mpatches
Expand Down Expand Up @@ -187,7 +188,7 @@

# Data where inverter is clipping based on AC power
mask1 = data[a] > max_ac_power
mask2 = clipping.geometric(ac_power=data[a], freq='15T')
mask2 = clipping.geometric(ac_power=data[a], freq='15min')
mask3 = np.logical_or(mask1.values, mask2.values)

data.loc[mask3, v] = np.nan
Expand Down Expand Up @@ -595,7 +596,11 @@ def wrapper(voltage, current, temp_cell, effective_irradiance,
loss = np.maximum(data[name_modeled_power] - data[i_col]*data[v_col], 0)
data[name_loss] = loss

# %%
# %% Plot measured and modeled power, color by mode

N = 6
alpha = 0.5
cmap = cm.get_cmap('plasma').resampled(N)

loss_cols = [c for c in data.columns if "Loss" in c]
mode_cols = [c for c in data.columns if "mode" in c and "modeled" not in c]
Expand All @@ -606,13 +611,6 @@ def wrapper(voltage, current, temp_cell, effective_irradiance,
mod = mode_cols[col]
pwr = modeled_power_cols[col]

# Color intervals by mode
cmap = {0: 'r',
1: 'b',
2: 'yellow',
3: 'cyan',
4: 'g'}

fig, ax = plt.subplots(figsize=(10, 10))
date_form = DateFormatter("%m/%d")
ax.xaxis.set_major_formatter(date_form)
Expand All @@ -628,15 +626,16 @@ def wrapper(voltage, current, temp_cell, effective_irradiance,
ax.plot(temp_grouped[pwr], c='k', ls='--')
ax.plot(temp_grouped[pwr] - temp_grouped[los], c='k')
ax.fill_between(temp_grouped.index, temp_grouped[pwr] - temp_grouped[los],
temp_grouped[pwr], color='k', alpha=0.2)
temp_grouped[pwr], color='k', alpha=alpha)

chng_pts = np.ravel(np.where(temp_grouped[mod].values[:-1]
- temp_grouped[mod].values[1:] != 0))

if len(chng_pts) == 0:
ax.axvspan(temp_grouped.index[0], temp_grouped.index[-1],
color=cmap[temp_grouped.at[temp_grouped.index[-1], mod]],
alpha=0.05)
color=cmap.colors[temp_grouped.at[temp_grouped.index[-1],
mod]],
alpha=alpha)
else:
set1 = np.append([0], chng_pts)
set2 = np.append(chng_pts, [-1])
Expand All @@ -645,29 +644,22 @@ def wrapper(voltage, current, temp_cell, effective_irradiance,
my_index = temp_grouped.index[start:end]
ax.axvspan(
temp_grouped.index[start], temp_grouped.index[end],
color=cmap[temp_grouped.at[temp_grouped.index[end], mod]],
alpha=0.05)
color=cmap.colors[temp_grouped.at[temp_grouped.index[end],
mod]],
alpha=alpha, ec=None)

# Add different colored intervals to legend
handles, labels = ax.get_legend_handles_labels()

modeled_line = Line2D([0], [0], label='Modeled', color='k', ls='--')
measured_line = Line2D([0], [0], label='Measured', color='k')

# TODO don't mark offline periods as Mode 0
red_patch = mpatches.Patch(color='r', alpha=0.05, label='Mode 0')
blue_patch = mpatches.Patch(color='b', alpha=0.05, label='Mode 1')
yellow_patch = mpatches.Patch(color='y', alpha=0.05, label='Mode 2')
purple_patch = mpatches.Patch(color='cyan', alpha=0.05, label='Mode 3')
green_patch = mpatches.Patch(color='g', alpha=0.05, label='Mode 4')

handles.append(measured_line)
handles.append(modeled_line)
handles.append(red_patch)
handles.append(green_patch)
handles.append(blue_patch)
handles.append(yellow_patch)
handles.append(purple_patch)

for i in range(-1, N-1):
my_patch = mpatches.Patch(color=cmap.colors[i], label=f'Mode {i}')
handles.append(my_patch)

# handles.append(gray_patch)

ax.set_xlabel('Date', fontsize='xx-large')
Expand Down Expand Up @@ -724,4 +716,6 @@ def wrapper(voltage, current, temp_cell, effective_irradiance,
ax.set_ylabel('[%]', fontsize='xx-large')
ax.set_xticks(xvals, days)
ax.xaxis.set_major_formatter(date_form)
ax.set_title('Losses incurred in modes 0 -3', fontsize='xx-large')
ax.set_title('Losses incurred in modes -1, 0, 1, 2, 3', fontsize='xx-large')

# %%
21 changes: 15 additions & 6 deletions pvanalytics/features/snow.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def categorize(vmp_ratio, transmission, measured_voltage, modeled_voltage,
* Mode 0: Indicates periods with enough opaque snow that the system is not
producing power. Specifically, Mode 0 is when the measured voltage is
below the inverter's turn-on voltage but the voltage modeled using
measured irradiance is below the inverter's turn-on voltage.
measured irradiance is above the inverter's turn-on voltage.
* Mode 1: Indicates periods when the system has non-uniform snow and
both operating voltage and current are decreased. Operating voltage is
reduced when bypass diodes activate and current is decreased due to
Expand All @@ -171,6 +171,12 @@ def categorize(vmp_ratio, transmission, measured_voltage, modeled_voltage,
* Mode is None when both measured and voltage modeled from measured
irradiance are below the inverter turn-on voltage.
An additional mode was added to cover a case that was not addressed in
[1]_:
* Mode -1: Indicates periods where voltage modeled with measured irradiance
is below the inverter's turn-on voltage. Under Mode -1, it is unknown if
and how snow impacts power output.
Parameters
----------
vmp_ratio : array-like
Expand Down Expand Up @@ -207,23 +213,26 @@ def categorize(vmp_ratio, transmission, measured_voltage, modeled_voltage,
umin_meas = measured_voltage >= min_dcv
umin_model = modeled_voltage >= min_dcv

# offline if both measurement and model say that voltage is too low.
# if either measured or modeled is above the minimum, then system is
# offline if model says that voltage is too low.
# if modeled is above the minimum, then system is
# possibly generating
offline = ~umin_meas & ~umin_model
# offline = ~umin_meas & ~umin_model

# vmp_ratio discrimates between states (1,2) and (3,4)
uvr = np.where(vmp_ratio >= threshold_vratio, 3, 1)

# transmission discrimates within (1,2) and (3,4)
utrans = np.where(transmission >= threshold_transmission, 1, 0)

# None if nan or system is offline
# None if nan
# if offline:
# - -1 if umin_model is 0
# if not offline:
# - 0 if umin_meas is 0, i.e., measurement indicate no power but
# it must be that umin_model is 1
# - state 1, 2, 3, 4 defined by uvr + utrans
mode = np.where(offline | np.isnan(vmp_ratio) | np.isnan(transmission),
mode = np.where(np.isnan(vmp_ratio) | np.isnan(transmission),
None, umin_meas * (uvr + utrans))
mode = np.where(~umin_model, -1, mode)

return mode
26 changes: 15 additions & 11 deletions pvanalytics/tests/features/test_snow.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,24 @@ def test_get_transmission():


def test_categorize():
vmp_ratio = np.array([np.nan, 0.9, 0.1, 0.6, 0.7, 0.9, 0.9])
measured_voltage = np.array([400., 450., 400., 420., 420., 495., 270.])
modeled_voltage = np.array([np.nan, 500, 4000, 700, 600, 550, 300])
transmission = np.array([0.5, np.nan, 0.5, 0.9, 0.5, 0.9, 0.9])
vmp_ratio = np.array([np.nan, 0.9, 0.1, 0.6, 0.7, 0.9, 0.9, 0.5])
measured_voltage = np.array([400., 450., 400., 420., 420., 495., 270.,
275.])
modeled_voltage = np.array([np.nan, 500, 4000, 700, 600, 550, 300, 550])
transmission = np.array([0.5, np.nan, 0.5, 0.9, 0.5, 0.9, 0.9, 0.9])
min_dcv = 300
threshold_vratio = 0.7
threshold_transmission = 0.6
# ratio: np.nan, >thres, >thres, >thres, >thres, >thres, <thres
# measured: np.nan, >thres, >thres, >thres, >thres, >thres, <thres
# modeled: np.nan, >thres, >thres, >thres, >thres, >thres, <thres
# vr=np.nan, vr<thres, vr<thres, vr=thres, vr>thres, vr>thres, vr>thres
# tr<thres, np.nan, tr<thres, tr<thres, tr<thres, tr<thres, tr>thres
# None (vr), None (vmo), 1, 3, 3, 3, None
expected = np.array([None, None, 1, 2, 3, 4, 0])
# vmp_ratio: np.nan, >thres, <thres, <thres, =thres, >thres, >thres, <thres
# measured_voltage: >thres, >thres, >thres, >thres, >thres, >thres, <thres,
# <thres
# modeled_voltage: np.nan, >thres, >thres, >thres, >thres, >thres, <thres,
# >thres
# transmission: <thres, np.nan, <thres, >thres, <thres, >thres, >thres,
# >thres, > thres
# None (vmp_ratio, modeled_voltage), None (transmission), 1, 2, 3, 4, -1,
# 0
expected = np.array([None, None, 1, 2, 3, 4, -1, 0])
result = snow.categorize(vmp_ratio, transmission, measured_voltage,
modeled_voltage, min_dcv,
threshold_vratio, threshold_transmission)
Expand Down

0 comments on commit 8b309cb

Please sign in to comment.