-
Notifications
You must be signed in to change notification settings - Fork 28
/
orientation.py
236 lines (211 loc) · 8.94 KB
/
orientation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
"""Functions for identifying system orientation."""
import pandas as pd
from pvanalytics.util import _fit, _group
def _conditional_fit(day, minutes, fitfunc, freq, default=0.0, min_hours=0.0,
peak_min=None):
# Return the :math:`r^2` of a curve fit to a single day of data if
# certain conditions are met.
#
# `fitfunc` does the curve fitting and is only applied if two
# conditions are met:
# - There must be more than `min_hours` of data in `day`
# (determined by the number of values in `day` times `freq`).
# - If `peak_min` is specified then no curve fitting will be
# performed unless the maximum value in `day` is at least
# `peak_min`.
#
# If either condition is not satisfied then `default` is returned.
#
# Parameters
# ----------
# day : Series
# y-values to which `fitfunc` will be applied.
# minutes : Series
# x-values for curve fitting. The index for `x` must be a
# superset of the index for `day`.
# fitfunc : function
# Function to perform curve fit. Must accept two parameters,
# the x-values and y-values, and return the :math:`r^2`
# of the curve fit.
# freq : str
# Timestamp spacing for data in `day`.
# default : float, default 0.0
# Value to be returned if the conditions above are not
# satisfied and `fitfunc` is not applied.
# min_hours : float, default 0.0
# Minimum hours in `day` with data for curve fitting to be performed.
# peak_min : float or None, default None
# Maximum value in `day` must be at least `peak_min` for curve
# fitting to be performed.
#
# Returns
# -------
# float
# The :math:`r^2` of the curve fit from `fitfunc` or `default`
# if fit was not performed.
high_enough = True
if peak_min is not None:
high_enough = day.max() > peak_min
if (_hours(day, freq) > min_hours) and high_enough:
return fitfunc(minutes[day.index], day)
return default
def _freqstr_to_hours(freq):
# Convert pandas freqstr to hours (as a float)
if freq.isalpha():
freq = '1' + freq
return pd.to_timedelta(freq).seconds / 60
def _hours(data, freq):
# Return the number of hours in `data` with timestamp
# spacing given by `freq`.
return data.count() * _freqstr_to_hours(freq)
def tracking_nrel(power_or_irradiance, daytime, r2_min=0.915,
r2_fixed_max=0.96, min_hours=5, peak_min=None,
quadratic_mask=None):
"""Flag days that match the profile of a single-axis tracking PV system
on a sunny day.
This algorithm relies on the observation that the power profile of
a single-axis tracking PV system tends to resemble a quartic
polynomial on a sunny day, I.e., two peaks are observed, one
before and one after the sun crosses the tracker azimuth. By
contrast, the power profile for a fixed tilt PV system often
resembles a quadratic polynomial on a sunny day, with a single
peak when the sun is near the system azimuth.
The algorithm fits both a quartic and a quadratic polynomial to
each day's data. A day is marked True if the quartic fit has a
sufficiently high :math:`r^2` and the quadratic fit has a
sufficiently low :math:`r^2`. Specifically, a day is marked True
when three conditions are met:
1. a restricted quartic [#]_ must fit the data with :math:`r^2`
greater than `r2_min`
2. the :math:`r^2` for the restricted quartic fit must be greater
than the :math:`r^2` for a quadratic fit
3. the :math:`r^2` for a quadratic fit must be less than
`r2_fixed_max`
Values on days where any one of these conditions is not met are
marked False.
.. [#] The specific quartic used for this fit is centered within
70 minutes of 12:00, the y-value at the center must be within
15% of the median for the day, and it must open downwards.
Parameters
----------
power_or_irradiance : Series
Timezone localized series of power or irradiance measurements.
daytime : Series
Boolean series with True for times that are during the
day. For best results this mask should exclude early morning
and late afternoon as well as night. Data at these times may have
problems with shadows that interfere with curve fitting.
r2_min : float, default 0.915
Minimum :math:`r^2` of a quartic fit for a day to be marked True.
r2_fixed_max : float, default 0.96
If the :math:`r^2` of a quadratic fit exceeds
`r2_fixed_max`, then tracking/fixed cannot be distinguished
and the day is marked False.
min_hours : float, default 5.0
Minimum number of hours with data to attempt a fit on a day.
peak_min : float, default None
The maximum `power_or_irradiance` value for a day must be
greater than `peak_min` for a fit to be attempted. If the
maximum for a day is less than `peak_min` then the day will be
marked False.
quadratic_mask : Series, default None
If None then `daytime` is used. This Series is used to remove
morning and afternoon times from the data before applying a
quadratic fit. The mask should
typically exclude more data than `daytime` in order to
eliminate long tails in the morning or afternoon that can
appear if a tracker is stuck in a West or East orientation.
Returns
-------
Series
Boolean series with True for every value on a day that has a
tracking profile (see criteria above).
Notes
-----
This algorithm is based on the PVFleets QA Analysis
project. Copyright (c) 2020 Alliance for Sustainable Energy, LLC.
"""
if quadratic_mask is None:
quadratic_mask = daytime
freq = pd.infer_freq(power_or_irradiance.index)
minutes = pd.Series(
power_or_irradiance.index.hour * 60 + power_or_irradiance.index.minute,
index=power_or_irradiance.index
)
daily_data = _group.by_day(power_or_irradiance[daytime])
tracking_days = daily_data.apply(
_conditional_fit,
fitfunc=_fit.quartic_restricted_r2,
minutes=minutes,
freq=freq,
min_hours=min_hours,
peak_min=peak_min
)
fixed_days = _group.by_day(power_or_irradiance[quadratic_mask]).apply(
_conditional_fit,
fitfunc=_fit.quadratic_r2,
minutes=minutes,
freq=freq,
min_hours=min_hours,
peak_min=peak_min
)
return (
(tracking_days > r2_min)
& (tracking_days > fixed_days)
& (fixed_days < r2_fixed_max)
).reindex(power_or_irradiance.index, method='pad', fill_value=False)
def fixed_nrel(power_or_irradiance, daytime, r2_min=0.94,
min_hours=5, peak_min=None):
"""Flag days that match the profile of a fixed PV system on a sunny day.
This algorithm relies on the observation that the power profile of a
fixed tilt PV system often resembles a quadratic polynomial on a
sunny day, with a single peak when the sun is near the system azimuth.
A day is marked True when the :math:`r^2` for a quadratic fit to the
power data is greater than `r2_min`.
Parameters
----------
power_or_irradiance : Series
Timezone localized series of power or irradiance measurements.
daytime : Series
Boolean series with True for times that are during the
day. For best results this mask should exclude early morning
and evening as well as night. Data at these times may have
problems with shadows that interfere with curve fitting.
r2_min : float, default 0.94
Minimum :math:`r^2` of a quadratic fit for a day to be marked True.
min_hours : float, default 5.0
Minimum number of hours with data to attempt a fit on a day.
peak_min : float, default None
The maximum `power_or_irradiance` value for a day must be
greater than `peak_min` for a fit to be attempted. If the
maximum for a day is less than `peak_min` then the day will be
marked False.
Returns
-------
Series
True for values on days where `power_or_irradiance` matches
the expected parabolic profile for a fixed PV system on a sunny day.
Notes
-----
This algorithm is based on the PVFleets QA Analysis
project. Copyright (c) 2020 Alliance for Sustainable Energy, LLC.
"""
freq = pd.infer_freq(power_or_irradiance.index)
daily_data = _group.by_day(
power_or_irradiance[daytime]
)
minutes = pd.Series(
power_or_irradiance.index.hour * 60 + power_or_irradiance.index.minute,
index=power_or_irradiance.index
)
fixed_days = daily_data.apply(
_conditional_fit,
fitfunc=_fit.quadratic_r2,
minutes=minutes,
freq=freq,
min_hours=min_hours,
peak_min=peak_min
)
return (
fixed_days > r2_min
).reindex(power_or_irradiance.index, method='pad', fill_value=False)