foxes 1.2__py3-none-any.whl → 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of foxes might be problematic. Click here for more details.
- examples/abl_states/run.py +5 -5
- examples/induction/run.py +5 -5
- examples/random_timeseries/run.py +13 -13
- examples/scan_row/run.py +12 -7
- examples/sector_management/run.py +11 -7
- examples/single_state/run.py +5 -5
- examples/tab_file/run.py +1 -1
- examples/timeseries/run.py +5 -5
- examples/timeseries_slurm/run.py +5 -5
- examples/wind_rose/run.py +1 -1
- examples/yawed_wake/run.py +5 -5
- foxes/algorithms/downwind/downwind.py +15 -5
- foxes/algorithms/sequential/sequential.py +1 -1
- foxes/core/algorithm.py +24 -20
- foxes/core/axial_induction_model.py +18 -0
- foxes/core/engine.py +2 -14
- foxes/core/farm_controller.py +18 -0
- foxes/core/ground_model.py +19 -0
- foxes/core/partial_wakes_model.py +9 -21
- foxes/core/point_data_model.py +18 -0
- foxes/core/rotor_model.py +2 -18
- foxes/core/states.py +2 -17
- foxes/core/turbine_model.py +2 -18
- foxes/core/turbine_type.py +2 -18
- foxes/core/vertical_profile.py +8 -20
- foxes/core/wake_frame.py +2 -20
- foxes/core/wake_model.py +19 -20
- foxes/core/wake_superposition.py +19 -0
- foxes/input/states/__init__.py +1 -1
- foxes/input/states/field_data_nc.py +14 -1
- foxes/input/states/{scan_ws.py → scan.py} +39 -52
- foxes/input/yaml/__init__.py +1 -1
- foxes/input/yaml/dict.py +221 -50
- foxes/input/yaml/yaml.py +5 -5
- foxes/output/__init__.py +2 -1
- foxes/output/farm_results_eval.py +57 -35
- foxes/output/output.py +2 -18
- foxes/output/plt.py +19 -0
- foxes/output/rose_plot.py +413 -207
- foxes/utils/__init__.py +1 -2
- foxes/utils/subclasses.py +69 -0
- {foxes-1.2.dist-info → foxes-1.2.1.dist-info}/METADATA +1 -2
- {foxes-1.2.dist-info → foxes-1.2.1.dist-info}/RECORD +56 -56
- tests/0_consistency/iterative/test_iterative.py +1 -1
- tests/0_consistency/partial_wakes/test_partial_wakes.py +1 -1
- tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +7 -2
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +7 -2
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +7 -2
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +7 -2
- tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +7 -2
- tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +7 -3
- tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +7 -2
- foxes/utils/windrose_plot.py +0 -152
- {foxes-1.2.dist-info → foxes-1.2.1.dist-info}/LICENSE +0 -0
- {foxes-1.2.dist-info → foxes-1.2.1.dist-info}/WHEEL +0 -0
- {foxes-1.2.dist-info → foxes-1.2.1.dist-info}/entry_points.txt +0 -0
- {foxes-1.2.dist-info → foxes-1.2.1.dist-info}/top_level.txt +0 -0
foxes/output/rose_plot.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
import
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
from xarray import Dataset
|
|
4
|
+
from matplotlib.cm import ScalarMappable
|
|
5
|
+
from matplotlib.projections.polar import PolarAxes
|
|
6
|
+
from matplotlib.lines import Line2D
|
|
3
7
|
|
|
4
|
-
from foxes.utils import wd2uv, uv2wd, TabWindroseAxes
|
|
5
8
|
from foxes.algorithms import Downwind
|
|
6
9
|
from foxes.core import WindFarm, Turbine
|
|
7
10
|
from foxes.models import ModelBook
|
|
@@ -24,30 +27,38 @@ class RosePlotOutput(Output):
|
|
|
24
27
|
|
|
25
28
|
"""
|
|
26
29
|
|
|
27
|
-
def __init__(
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
farm_results=None,
|
|
33
|
+
point_results=None,
|
|
34
|
+
use_points=False,
|
|
35
|
+
**kwargs,
|
|
36
|
+
):
|
|
28
37
|
"""
|
|
29
38
|
Constructor.
|
|
30
39
|
|
|
31
40
|
Parameters
|
|
32
41
|
----------
|
|
33
|
-
|
|
34
|
-
The
|
|
42
|
+
farm_results: xarray.Dataset, optional
|
|
43
|
+
The farm results
|
|
44
|
+
point_results: xarray.Dataset, optional
|
|
45
|
+
The point results
|
|
46
|
+
use_points: bool
|
|
47
|
+
Flag for using points in cases where both
|
|
48
|
+
farm and point results are given
|
|
35
49
|
kwargs: dict, optional
|
|
36
50
|
Additional parameters for the base class
|
|
37
51
|
|
|
38
52
|
"""
|
|
39
53
|
super().__init__(**kwargs)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
self._rtype = FC.TURBINE
|
|
43
|
-
elif dims[1] == FC.POINT:
|
|
54
|
+
if use_points or (farm_results is None and point_results is not None):
|
|
55
|
+
self.results = point_results
|
|
44
56
|
self._rtype = FC.POINT
|
|
57
|
+
elif farm_results is not None:
|
|
58
|
+
self.results = farm_results
|
|
59
|
+
self._rtype = FC.TURBINE
|
|
45
60
|
else:
|
|
46
|
-
raise KeyError(
|
|
47
|
-
f"Results dimension 1 is neither '{FC.TURBINE}' nor '{FC.POINT}': dims = {results.dims}"
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
self.results = results.to_dataframe()
|
|
61
|
+
raise KeyError(f"Require either farm_results or point_results")
|
|
51
62
|
|
|
52
63
|
@classmethod
|
|
53
64
|
def get_data_info(cls, dname):
|
|
@@ -104,224 +115,198 @@ class RosePlotOutput(Output):
|
|
|
104
115
|
|
|
105
116
|
def get_data(
|
|
106
117
|
self,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
wd_sectors,
|
|
119
|
+
ws_var,
|
|
120
|
+
ws_bins,
|
|
110
121
|
wd_var=FV.AMB_WD,
|
|
111
|
-
turbine=
|
|
112
|
-
point=
|
|
113
|
-
|
|
122
|
+
turbine=0,
|
|
123
|
+
point=0,
|
|
124
|
+
add_inf=False,
|
|
114
125
|
):
|
|
115
126
|
"""
|
|
116
|
-
|
|
127
|
+
Generates the plot data
|
|
117
128
|
|
|
118
129
|
Parameters
|
|
119
130
|
----------
|
|
120
|
-
|
|
121
|
-
The number of wind
|
|
122
|
-
|
|
123
|
-
The
|
|
124
|
-
|
|
125
|
-
The
|
|
126
|
-
wd_var: str
|
|
127
|
-
The wind direction variable
|
|
128
|
-
turbine: int
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
start0: bool
|
|
137
|
-
Flag for starting the first sector at
|
|
138
|
-
zero degrees instead of minus half width
|
|
131
|
+
wd_sectors: int
|
|
132
|
+
The number of wind rose sectors
|
|
133
|
+
ws_var: str
|
|
134
|
+
The wind speed variable
|
|
135
|
+
ws_bins: list of float
|
|
136
|
+
The wind speed bins
|
|
137
|
+
wd_var: str
|
|
138
|
+
The wind direction variable
|
|
139
|
+
turbine: int
|
|
140
|
+
The turbine index, for weights and for
|
|
141
|
+
data if farm_results are given
|
|
142
|
+
point: int
|
|
143
|
+
The point index, for data if point_results
|
|
144
|
+
are given
|
|
145
|
+
add_inf: bool
|
|
146
|
+
Add an upper bin up to infinity
|
|
139
147
|
|
|
140
148
|
Returns
|
|
141
149
|
-------
|
|
142
|
-
|
|
143
|
-
The
|
|
150
|
+
data: xarray.Dataset
|
|
151
|
+
The plot data
|
|
144
152
|
|
|
145
153
|
"""
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
data =
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
grp = data[[wd_var, lgd, "frequency"]].groupby([wd_var, lgd], observed=False)
|
|
178
|
-
data = grp.sum().reset_index()
|
|
179
|
-
|
|
180
|
-
data[wd_var] = data[wd_var].astype(np.float64)
|
|
181
|
-
data[lgd] = list(data[lgd])
|
|
182
|
-
if start0:
|
|
183
|
-
data[wd_var] += dwd / 2
|
|
184
|
-
|
|
185
|
-
ii = pd.IntervalIndex(data[lgd])
|
|
186
|
-
data[var] = ii.mid
|
|
187
|
-
data[f"bin_min_{var}"] = ii.left
|
|
188
|
-
data[f"bin_max_{var}"] = ii.right
|
|
189
|
-
data[f"bin_min_{wd_var}"] = data[wd_var] - dwd / 2
|
|
190
|
-
data[f"bin_max_{wd_var}"] = data[wd_var] + dwd / 2
|
|
191
|
-
data["sector"] = (data[wd_var] / dwd).astype(int)
|
|
192
|
-
|
|
193
|
-
data = data[
|
|
194
|
-
[
|
|
195
|
-
wd_var,
|
|
196
|
-
var,
|
|
197
|
-
"sector",
|
|
198
|
-
f"bin_min_{wd_var}",
|
|
199
|
-
f"bin_max_{wd_var}",
|
|
200
|
-
f"bin_min_{var}",
|
|
201
|
-
f"bin_max_{var}",
|
|
202
|
-
lgd,
|
|
203
|
-
"frequency",
|
|
204
|
-
]
|
|
205
|
-
]
|
|
206
|
-
data.index.name = "bin"
|
|
154
|
+
if add_inf:
|
|
155
|
+
ws_bins = list(ws_bins) + [np.inf]
|
|
156
|
+
w = self.results[FV.WEIGHT].to_numpy()[:, turbine]
|
|
157
|
+
t = turbine if self._rtype == FC.TURBINE else point
|
|
158
|
+
ws = self.results[ws_var].to_numpy()[:, t]
|
|
159
|
+
wd = self.results[wd_var].to_numpy()[:, t].copy()
|
|
160
|
+
wd_delta = 360 / wd_sectors
|
|
161
|
+
wd[wd >= 360 - wd_delta / 2] -= 360
|
|
162
|
+
wd_bins = np.arange(-wd_delta / 2, 360, wd_delta)
|
|
163
|
+
ws_bins = np.asarray(ws_bins, dtype=ws.dtype)
|
|
164
|
+
|
|
165
|
+
freq = 100 * np.histogram2d(wd, ws, (wd_bins, ws_bins), weights=w)[0]
|
|
166
|
+
|
|
167
|
+
data = Dataset(
|
|
168
|
+
coords={
|
|
169
|
+
wd_var: np.arange(0, 360, wd_delta),
|
|
170
|
+
ws_var: 0.5 * (ws_bins[:-1] + ws_bins[1:]),
|
|
171
|
+
},
|
|
172
|
+
data_vars={
|
|
173
|
+
f"bin_min_{wd_var}": (wd_var, wd_bins[:-1]),
|
|
174
|
+
f"bin_max_{wd_var}": (wd_var, wd_bins[1:]),
|
|
175
|
+
f"bin_min_{ws_var}": (ws_var, ws_bins[:-1]),
|
|
176
|
+
f"bin_max_{ws_var}": (ws_var, ws_bins[1:]),
|
|
177
|
+
"frequency": ((wd_var, ws_var), freq),
|
|
178
|
+
},
|
|
179
|
+
attrs={
|
|
180
|
+
f"{wd_var}_bounds": wd_bins,
|
|
181
|
+
f"{ws_var}_bounds": ws_bins,
|
|
182
|
+
},
|
|
183
|
+
)
|
|
207
184
|
|
|
208
185
|
return data
|
|
209
186
|
|
|
210
187
|
def get_figure(
|
|
211
188
|
self,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
189
|
+
wd_sectors,
|
|
190
|
+
ws_var,
|
|
191
|
+
ws_bins,
|
|
215
192
|
wd_var=FV.AMB_WD,
|
|
216
|
-
turbine=None,
|
|
217
|
-
point=None,
|
|
218
|
-
title=None,
|
|
219
|
-
legend=None,
|
|
220
|
-
design="bar",
|
|
221
|
-
start0=False,
|
|
222
193
|
fig=None,
|
|
194
|
+
ax=None,
|
|
223
195
|
figsize=None,
|
|
224
|
-
|
|
196
|
+
freq_delta=3,
|
|
197
|
+
cmap="summer",
|
|
198
|
+
title=None,
|
|
199
|
+
legend_pars=None,
|
|
225
200
|
ret_data=False,
|
|
226
201
|
**kwargs,
|
|
227
202
|
):
|
|
228
203
|
"""
|
|
229
|
-
Creates figure
|
|
204
|
+
Creates the figure
|
|
230
205
|
|
|
231
206
|
Parameters
|
|
232
207
|
----------
|
|
233
|
-
|
|
234
|
-
The number of wind
|
|
235
|
-
|
|
236
|
-
The
|
|
237
|
-
|
|
238
|
-
The
|
|
239
|
-
wd_var: str
|
|
240
|
-
The wind direction variable
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
point: int, optional
|
|
246
|
-
Only relevant in case of point results.
|
|
247
|
-
If None, mean over all points.
|
|
248
|
-
Else, data from a single point
|
|
249
|
-
title. str, optional
|
|
250
|
-
The title
|
|
251
|
-
legend: str, optional
|
|
252
|
-
The data legend string
|
|
253
|
-
design: str
|
|
254
|
-
The wind rose design: bar, contour, ...
|
|
255
|
-
start0: bool
|
|
256
|
-
Flag for starting the first sector at
|
|
257
|
-
zero degrees instead of minus half width
|
|
258
|
-
fig: matplotlib.Figure
|
|
259
|
-
The figure to which to add an axis
|
|
208
|
+
wd_sectors: int
|
|
209
|
+
The number of wind rose sectors
|
|
210
|
+
ws_var: str
|
|
211
|
+
The wind speed variable
|
|
212
|
+
ws_bins: list of float
|
|
213
|
+
The wind speed bins
|
|
214
|
+
wd_var: str
|
|
215
|
+
The wind direction variable
|
|
216
|
+
fig: pyplot.Figure, optional
|
|
217
|
+
The figure object
|
|
218
|
+
ax: pyplot.Axes, optional
|
|
219
|
+
The axes object
|
|
260
220
|
figsize: tuple, optional
|
|
261
|
-
The figsize
|
|
262
|
-
|
|
263
|
-
The
|
|
264
|
-
|
|
221
|
+
The figsize argument for plt.subplots
|
|
222
|
+
freq_delta: int
|
|
223
|
+
The frequency delta for the label
|
|
224
|
+
in percent
|
|
225
|
+
cmap: str
|
|
226
|
+
The color map
|
|
227
|
+
title: str, optional
|
|
228
|
+
The title
|
|
229
|
+
legend_pars: dict, optional
|
|
230
|
+
Parameters for the legend
|
|
265
231
|
ret_data: bool
|
|
266
232
|
Flag for returning wind rose data
|
|
267
233
|
kwargs: dict, optional
|
|
268
|
-
Additional
|
|
269
|
-
plot function
|
|
234
|
+
Additional parameters for get_data
|
|
270
235
|
|
|
271
236
|
Returns
|
|
272
237
|
-------
|
|
273
|
-
|
|
274
|
-
The
|
|
275
|
-
data:
|
|
276
|
-
The
|
|
238
|
+
ax: pyplot.Axes
|
|
239
|
+
The axes object
|
|
240
|
+
data: xarray.Dataset, optional
|
|
241
|
+
The plot data
|
|
277
242
|
|
|
278
243
|
"""
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
244
|
+
data = self.get_data(wd_sectors, ws_var, ws_bins, wd_var, **kwargs)
|
|
245
|
+
|
|
246
|
+
n_wsb = data.sizes[ws_var]
|
|
247
|
+
n_wdb = data.sizes[wd_var]
|
|
248
|
+
ws_bins = np.asarray(data.attrs[f"{ws_var}_bounds"])
|
|
249
|
+
wd_cent = np.mod(90 - data[wd_var].to_numpy(), 360)
|
|
250
|
+
wd_cent = np.radians(wd_cent)
|
|
251
|
+
wd_delta = 360 / n_wdb
|
|
252
|
+
wd_width = np.radians(0.9 * wd_delta)
|
|
253
|
+
freq = data["frequency"].to_numpy()
|
|
254
|
+
|
|
255
|
+
if ax is not None:
|
|
256
|
+
if not isinstance(ax, PolarAxes):
|
|
257
|
+
raise TypeError(
|
|
258
|
+
f"Require axes of type '{PolarAxes.__name__}' for '{type(self).__name__}', got '{type(ax).__name__}'"
|
|
259
|
+
)
|
|
260
|
+
else:
|
|
261
|
+
fig, ax = plt.subplots(figsize=figsize, subplot_kw={"projection": "polar"})
|
|
262
|
+
|
|
263
|
+
bcmap = plt.get_cmap(cmap, n_wsb)
|
|
264
|
+
color_list = bcmap(np.linspace(0, 1, n_wsb))
|
|
265
|
+
|
|
266
|
+
bottom = np.zeros(n_wdb)
|
|
267
|
+
for wsi in range(n_wsb):
|
|
268
|
+
ax.bar(
|
|
269
|
+
wd_cent,
|
|
270
|
+
freq[:, wsi],
|
|
271
|
+
bottom=bottom,
|
|
272
|
+
width=wd_width,
|
|
273
|
+
color=color_list[wsi],
|
|
274
|
+
)
|
|
275
|
+
bottom += freq[:, wsi]
|
|
276
|
+
|
|
277
|
+
fmax = np.max(np.sum(freq, axis=1))
|
|
278
|
+
freq_delta = int(freq_delta)
|
|
279
|
+
freq_ticks = np.arange(0, fmax + freq_delta / 2, freq_delta, dtype=np.int32)[1:]
|
|
280
|
+
|
|
281
|
+
tksl = np.arange(0, 360, max(wd_delta, 30))
|
|
282
|
+
tks = np.radians(np.mod(90 - tksl, 360))
|
|
283
|
+
ax.set_xticks(tks, [f"{int(d)}°" for d in tksl])
|
|
284
|
+
ax.set_yticks(freq_ticks, [f"{f}%" for f in freq_ticks])
|
|
285
|
+
ax.set_title(title)
|
|
294
286
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
bin_min_dir=np.sort(wrdata[f"bin_min_{wd_var}"].unique()),
|
|
304
|
-
bin_min_var=np.sort(wrdata[f"bin_min_{var}"].unique()),
|
|
305
|
-
bin_max_var=np.sort(wrdata[f"bin_max_{var}"].unique()),
|
|
306
|
-
**kwargs,
|
|
287
|
+
llines = [Line2D([0], [0], color=c, lw=10) for c in np.flip(color_list, axis=0)]
|
|
288
|
+
lleg = [
|
|
289
|
+
f"[{ws_bins[i]:.1f}, {ws_bins[i+1]:.1f})" for i in range(n_wsb - 1, -1, -1)
|
|
290
|
+
]
|
|
291
|
+
lpars = dict(
|
|
292
|
+
loc="upper left",
|
|
293
|
+
bbox_to_anchor=(0.8, 0.5),
|
|
294
|
+
title=f"{ws_var}",
|
|
307
295
|
)
|
|
308
|
-
|
|
309
|
-
|
|
296
|
+
wsl = [FV.WS, FV.REWS, FV.REWS2, FV.REWS3]
|
|
297
|
+
wsl += [FV.var2amb[v] for v in wsl]
|
|
298
|
+
if ws_var in wsl:
|
|
299
|
+
lpars["title"] += " [m/s]"
|
|
300
|
+
if legend_pars is not None:
|
|
301
|
+
lpars.update(legend_pars)
|
|
302
|
+
ax.legend(llines, lleg, **lpars)
|
|
310
303
|
|
|
311
304
|
if ret_data:
|
|
312
|
-
return
|
|
305
|
+
return ax, data
|
|
313
306
|
else:
|
|
314
|
-
return
|
|
307
|
+
return ax
|
|
315
308
|
|
|
316
|
-
def write_figure(
|
|
317
|
-
self,
|
|
318
|
-
file_name,
|
|
319
|
-
sectors,
|
|
320
|
-
var,
|
|
321
|
-
var_bins,
|
|
322
|
-
ret_data=False,
|
|
323
|
-
**kwargs,
|
|
324
|
-
):
|
|
309
|
+
def write_figure(self, file_name, *args, ret_data=False, **kwargs):
|
|
325
310
|
"""
|
|
326
311
|
Write rose plot to file
|
|
327
312
|
|
|
@@ -329,16 +314,12 @@ class RosePlotOutput(Output):
|
|
|
329
314
|
----------
|
|
330
315
|
file_name: str
|
|
331
316
|
Name of the output file
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
var: str
|
|
335
|
-
The data variable name
|
|
336
|
-
var_bins: list of float
|
|
337
|
-
The variable bin separation values
|
|
317
|
+
args: tuple, optional
|
|
318
|
+
Additional parameters for get_figure
|
|
338
319
|
ret_data: bool
|
|
339
320
|
Flag for returning wind rose data
|
|
340
321
|
kwargs: dict, optional
|
|
341
|
-
Additional parameters for get_figure
|
|
322
|
+
Additional parameters for get_figure
|
|
342
323
|
|
|
343
324
|
Returns
|
|
344
325
|
-------
|
|
@@ -347,19 +328,13 @@ class RosePlotOutput(Output):
|
|
|
347
328
|
|
|
348
329
|
"""
|
|
349
330
|
|
|
350
|
-
r = self.get_figure(
|
|
351
|
-
sectors=sectors,
|
|
352
|
-
var=var,
|
|
353
|
-
var_bins=var_bins,
|
|
354
|
-
ret_data=ret_data,
|
|
355
|
-
**kwargs,
|
|
356
|
-
)
|
|
331
|
+
r = self.get_figure(*args, ret_data=ret_data, **kwargs)
|
|
357
332
|
fpath = self.get_fpath(file_name)
|
|
358
333
|
if ret_data:
|
|
359
|
-
r[0].
|
|
334
|
+
r[0].get_figure().savefig(fpath, bbox_inches="tight")
|
|
360
335
|
return r[1]
|
|
361
336
|
else:
|
|
362
|
-
r.
|
|
337
|
+
r.get_figure().savefig(fpath, bbox_inches="tight")
|
|
363
338
|
|
|
364
339
|
|
|
365
340
|
class StatesRosePlotOutput(RosePlotOutput):
|
|
@@ -409,3 +384,234 @@ class StatesRosePlotOutput(RosePlotOutput):
|
|
|
409
384
|
results = algo.calc_farm(ambient=True).rename_vars({ws_var: FV.AMB_WS})
|
|
410
385
|
|
|
411
386
|
super().__init__(results, **kwargs)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class WindRoseBinPlot(Output):
|
|
390
|
+
"""
|
|
391
|
+
Plots mean data in wind rose bins
|
|
392
|
+
|
|
393
|
+
Attributes
|
|
394
|
+
----------
|
|
395
|
+
farm_results: xarray.Dataset
|
|
396
|
+
The wind farm results
|
|
397
|
+
|
|
398
|
+
:group: output
|
|
399
|
+
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
def __init__(self, farm_results, **kwargs):
|
|
403
|
+
"""
|
|
404
|
+
Constructor
|
|
405
|
+
|
|
406
|
+
Parameters
|
|
407
|
+
----------
|
|
408
|
+
farm_results: xarray.Dataset
|
|
409
|
+
The wind farm results
|
|
410
|
+
kwargs: dict, optional
|
|
411
|
+
Parameters for the base class
|
|
412
|
+
|
|
413
|
+
"""
|
|
414
|
+
super().__init__(**kwargs)
|
|
415
|
+
self.farm_results = farm_results
|
|
416
|
+
|
|
417
|
+
def get_data(
|
|
418
|
+
self,
|
|
419
|
+
variable,
|
|
420
|
+
ws_bins,
|
|
421
|
+
wd_sectors=12,
|
|
422
|
+
wd_var=FV.AMB_WD,
|
|
423
|
+
ws_var=FV.AMB_REWS,
|
|
424
|
+
turbine=0,
|
|
425
|
+
contraction="weights",
|
|
426
|
+
):
|
|
427
|
+
"""
|
|
428
|
+
Generates the plot data
|
|
429
|
+
|
|
430
|
+
Parameters
|
|
431
|
+
----------
|
|
432
|
+
variable: str
|
|
433
|
+
The variable name
|
|
434
|
+
ws_bins: list of float
|
|
435
|
+
The wind speed bins
|
|
436
|
+
wd_var: str
|
|
437
|
+
The wind direction variable
|
|
438
|
+
ws_var: str
|
|
439
|
+
The wind speed variable
|
|
440
|
+
turbine: int
|
|
441
|
+
The turbine index
|
|
442
|
+
contraction: str
|
|
443
|
+
The contraction method for states:
|
|
444
|
+
weights, mean_no_weights, sum_no_weights
|
|
445
|
+
|
|
446
|
+
Returns
|
|
447
|
+
-------
|
|
448
|
+
data: xarray.Dataset
|
|
449
|
+
The plot data
|
|
450
|
+
|
|
451
|
+
"""
|
|
452
|
+
var = self.farm_results[variable].to_numpy()[:, turbine]
|
|
453
|
+
w = self.farm_results[FV.WEIGHT].to_numpy()[:, turbine]
|
|
454
|
+
ws = self.farm_results[ws_var].to_numpy()[:, turbine]
|
|
455
|
+
wd = self.farm_results[wd_var].to_numpy()[:, turbine].copy()
|
|
456
|
+
wd_delta = 360 / wd_sectors
|
|
457
|
+
wd[wd >= 360 - wd_delta / 2] -= 360
|
|
458
|
+
wd_bins = np.arange(-wd_delta / 2, 360, wd_delta)
|
|
459
|
+
ws_bins = np.asarray(ws_bins)
|
|
460
|
+
|
|
461
|
+
if contraction == "weights":
|
|
462
|
+
z = np.histogram2d(wd, ws, (wd_bins, ws_bins), weights=w)[0]
|
|
463
|
+
z[z < 1e-13] = np.nan
|
|
464
|
+
z = np.histogram2d(wd, ws, (wd_bins, ws_bins), weights=w * var)[0] / z
|
|
465
|
+
elif contraction == "mean_no_weights":
|
|
466
|
+
z = np.histogram2d(wd, ws, (wd_bins, ws_bins))[0].astype(w.dtype)
|
|
467
|
+
z[z < 1] = np.nan
|
|
468
|
+
z = np.histogram2d(wd, ws, (wd_bins, ws_bins), weights=var)[0] / z
|
|
469
|
+
elif contraction == "sum_no_weights":
|
|
470
|
+
z = np.histogram2d(wd, ws, (wd_bins, ws_bins), weights=var)[0]
|
|
471
|
+
else:
|
|
472
|
+
raise KeyError(
|
|
473
|
+
f"Contraction '{contraction}' not supported. Choices: weights, mean_no_weights, sum_no_weights"
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
data = Dataset(
|
|
477
|
+
coords={
|
|
478
|
+
wd_var: 0.5 * (wd_bins[:-1] + wd_bins[1:]),
|
|
479
|
+
ws_var: 0.5 * (ws_bins[:-1] + ws_bins[1:]),
|
|
480
|
+
},
|
|
481
|
+
data_vars={
|
|
482
|
+
variable: ((wd_var, ws_var), z),
|
|
483
|
+
},
|
|
484
|
+
attrs={
|
|
485
|
+
f"{wd_var}_bounds": wd_bins,
|
|
486
|
+
f"{ws_var}_bounds": ws_bins,
|
|
487
|
+
},
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
return data
|
|
491
|
+
|
|
492
|
+
def get_figure(
|
|
493
|
+
self,
|
|
494
|
+
variable,
|
|
495
|
+
ws_bins,
|
|
496
|
+
wd_sectors=12,
|
|
497
|
+
wd_var=FV.AMB_WD,
|
|
498
|
+
ws_var=FV.AMB_REWS,
|
|
499
|
+
turbine=0,
|
|
500
|
+
contraction="weights",
|
|
501
|
+
fig=None,
|
|
502
|
+
ax=None,
|
|
503
|
+
title=None,
|
|
504
|
+
figsize=None,
|
|
505
|
+
ret_data=False,
|
|
506
|
+
**kwargs,
|
|
507
|
+
):
|
|
508
|
+
"""
|
|
509
|
+
Creates the figure
|
|
510
|
+
|
|
511
|
+
Parameters
|
|
512
|
+
----------
|
|
513
|
+
variable: str
|
|
514
|
+
The variable name
|
|
515
|
+
ws_bins: list of float
|
|
516
|
+
The wind speed bins
|
|
517
|
+
wd_var: str
|
|
518
|
+
The wind direction variable
|
|
519
|
+
ws_var: str
|
|
520
|
+
The wind speed variable
|
|
521
|
+
turbine: int
|
|
522
|
+
The turbine index
|
|
523
|
+
contraction: str
|
|
524
|
+
The contraction method for states:
|
|
525
|
+
weights, mean_no_weights, sum_no_weights
|
|
526
|
+
fig: pyplot.Figure, optional
|
|
527
|
+
The figure object
|
|
528
|
+
ax: pyplot.Axes, optional
|
|
529
|
+
The axes object
|
|
530
|
+
title: str, optional
|
|
531
|
+
The title
|
|
532
|
+
figsize: tuple, optional
|
|
533
|
+
The figsize argument for plt.subplots
|
|
534
|
+
ret_data: bool
|
|
535
|
+
Flag for returning wind rose data
|
|
536
|
+
kwargs: dict, optional
|
|
537
|
+
Additional parameters for plt.pcolormesh
|
|
538
|
+
|
|
539
|
+
Returns
|
|
540
|
+
-------
|
|
541
|
+
ax: pyplot.Axes
|
|
542
|
+
The axes object
|
|
543
|
+
|
|
544
|
+
"""
|
|
545
|
+
data = self.get_data(
|
|
546
|
+
variable=variable,
|
|
547
|
+
ws_bins=ws_bins,
|
|
548
|
+
wd_sectors=wd_sectors,
|
|
549
|
+
wd_var=wd_var,
|
|
550
|
+
ws_var=ws_var,
|
|
551
|
+
turbine=turbine,
|
|
552
|
+
contraction=contraction,
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
wd_delta = 360 / data.sizes[wd_var]
|
|
556
|
+
wd_bins = np.mod(90 - data.attrs[f"{wd_var}_bounds"], 360)
|
|
557
|
+
wd_bins = np.radians(wd_bins)
|
|
558
|
+
ws_bins = data.attrs[f"{ws_var}_bounds"]
|
|
559
|
+
|
|
560
|
+
if ax is not None:
|
|
561
|
+
if not isinstance(ax, PolarAxes):
|
|
562
|
+
raise TypeError(
|
|
563
|
+
f"Require axes of type '{PolarAxes.__name__}' for '{type(self).__name__}', got '{type(ax).__name__}'"
|
|
564
|
+
)
|
|
565
|
+
else:
|
|
566
|
+
fig, ax = plt.subplots(figsize=figsize, subplot_kw={"projection": "polar"})
|
|
567
|
+
|
|
568
|
+
y, x = np.meshgrid(ws_bins, wd_bins)
|
|
569
|
+
z = data[variable].to_numpy()
|
|
570
|
+
|
|
571
|
+
prgs = {"shading": "flat"}
|
|
572
|
+
prgs.update(kwargs)
|
|
573
|
+
|
|
574
|
+
img = ax.pcolormesh(x, y, z, **prgs)
|
|
575
|
+
|
|
576
|
+
tksl = np.arange(0, 360, max(wd_delta, 30))
|
|
577
|
+
tks = np.radians(np.mod(90 - tksl, 360))
|
|
578
|
+
ax.set_xticks(tks, [f"{d}°" for d in tksl])
|
|
579
|
+
ax.set_yticks(ws_bins)
|
|
580
|
+
ax.set_title(title)
|
|
581
|
+
cbar = fig.colorbar(img, ax=ax, pad=0.12)
|
|
582
|
+
cbar.ax.set_title(variable)
|
|
583
|
+
|
|
584
|
+
if ret_data:
|
|
585
|
+
return ax, data
|
|
586
|
+
else:
|
|
587
|
+
return ax
|
|
588
|
+
|
|
589
|
+
def write_figure(self, file_name, *args, ret_data=False, **kwargs):
|
|
590
|
+
"""
|
|
591
|
+
Write rose plot to file
|
|
592
|
+
|
|
593
|
+
Parameters
|
|
594
|
+
----------
|
|
595
|
+
file_name: str
|
|
596
|
+
Name of the output file
|
|
597
|
+
args: tuple, optional
|
|
598
|
+
Additional parameters for get_figure
|
|
599
|
+
ret_data: bool
|
|
600
|
+
Flag for returning wind rose data
|
|
601
|
+
kwargs: dict, optional
|
|
602
|
+
Additional parameters for get_figure
|
|
603
|
+
|
|
604
|
+
Returns
|
|
605
|
+
-------
|
|
606
|
+
data: pd.DataFrame, optional
|
|
607
|
+
The wind rose data
|
|
608
|
+
|
|
609
|
+
"""
|
|
610
|
+
|
|
611
|
+
r = self.get_figure(*args, ret_data=ret_data, **kwargs)
|
|
612
|
+
fpath = self.get_fpath(file_name)
|
|
613
|
+
if ret_data:
|
|
614
|
+
r[0].get_figure().savefig(fpath, bbox_inches="tight")
|
|
615
|
+
return r[1]
|
|
616
|
+
else:
|
|
617
|
+
r.get_figure().savefig(fpath, bbox_inches="tight")
|