foxes 1.1.1__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.
- docs/source/conf.py +3 -1
- examples/abl_states/run.py +5 -5
- examples/dyn_wakes/run.py +2 -2
- 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/timelines/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/__init__.py +13 -2
- foxes/algorithms/downwind/downwind.py +21 -6
- foxes/algorithms/downwind/models/init_farm_data.py +5 -2
- foxes/algorithms/downwind/models/point_wakes_calc.py +0 -1
- foxes/algorithms/iterative/iterative.py +1 -1
- foxes/algorithms/sequential/sequential.py +5 -4
- foxes/config/__init__.py +1 -0
- foxes/config/config.py +134 -0
- foxes/constants.py +15 -6
- foxes/core/algorithm.py +46 -30
- foxes/core/axial_induction_model.py +18 -0
- foxes/core/data.py +2 -1
- foxes/core/engine.py +43 -49
- foxes/core/farm_controller.py +22 -3
- foxes/core/farm_data_model.py +6 -2
- foxes/core/ground_model.py +19 -0
- foxes/core/model.py +2 -1
- foxes/core/partial_wakes_model.py +9 -21
- foxes/core/point_data_model.py +22 -2
- foxes/core/rotor_model.py +9 -21
- 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 +9 -25
- foxes/core/wake_model.py +24 -20
- foxes/core/wake_superposition.py +19 -0
- foxes/data/__init__.py +1 -1
- foxes/data/static_data.py +0 -7
- foxes/engines/dask.py +4 -3
- foxes/engines/single.py +1 -1
- foxes/input/__init__.py +1 -1
- foxes/input/farm_layout/from_csv.py +3 -1
- foxes/input/farm_layout/from_file.py +10 -10
- foxes/input/farm_layout/from_json.py +4 -3
- foxes/input/farm_layout/grid.py +3 -3
- foxes/input/states/__init__.py +1 -1
- foxes/input/states/create/random_abl_states.py +5 -3
- foxes/input/states/field_data_nc.py +36 -15
- foxes/input/states/multi_height.py +26 -15
- foxes/input/states/one_point_flow.py +6 -5
- foxes/input/states/{scan_ws.py → scan.py} +42 -52
- foxes/input/states/single.py +15 -6
- foxes/input/states/slice_data_nc.py +18 -12
- foxes/input/states/states_table.py +17 -10
- foxes/input/yaml/__init__.py +3 -0
- foxes/input/yaml/dict.py +381 -0
- foxes/input/yaml/windio/__init__.py +4 -0
- foxes/input/{windio → yaml/windio}/get_states.py +7 -7
- foxes/input/{windio → yaml/windio}/read_attributes.py +61 -40
- foxes/input/{windio → yaml/windio}/read_farm.py +34 -43
- foxes/input/{windio → yaml/windio}/read_fields.py +11 -10
- foxes/input/yaml/windio/read_outputs.py +147 -0
- foxes/input/yaml/windio/windio.py +269 -0
- foxes/input/yaml/yaml.py +103 -0
- foxes/models/partial_wakes/axiwake.py +7 -6
- foxes/models/partial_wakes/centre.py +3 -2
- foxes/models/partial_wakes/segregated.py +5 -2
- foxes/models/point_models/set_uniform_data.py +5 -3
- foxes/models/rotor_models/centre.py +2 -2
- foxes/models/rotor_models/grid.py +5 -5
- foxes/models/rotor_models/levels.py +6 -6
- foxes/models/turbine_models/kTI_model.py +3 -1
- foxes/models/turbine_models/lookup_table.py +7 -4
- foxes/models/turbine_models/power_mask.py +14 -8
- foxes/models/turbine_models/sector_management.py +4 -2
- foxes/models/turbine_models/set_farm_vars.py +53 -23
- foxes/models/turbine_models/table_factors.py +8 -7
- foxes/models/turbine_models/yaw2yawm.py +0 -1
- foxes/models/turbine_models/yawm2yaw.py +0 -1
- foxes/models/turbine_types/CpCt_file.py +6 -3
- foxes/models/turbine_types/CpCt_from_two.py +6 -3
- foxes/models/turbine_types/PCt_file.py +7 -6
- foxes/models/turbine_types/PCt_from_two.py +11 -2
- foxes/models/turbine_types/TBL_file.py +3 -4
- foxes/models/turbine_types/wsrho2PCt_from_two.py +19 -11
- foxes/models/turbine_types/wsti2PCt_from_two.py +19 -11
- foxes/models/vertical_profiles/abl_log_neutral_ws.py +1 -1
- foxes/models/vertical_profiles/abl_log_stable_ws.py +1 -1
- foxes/models/vertical_profiles/abl_log_unstable_ws.py +1 -1
- foxes/models/vertical_profiles/abl_log_ws.py +1 -1
- foxes/models/wake_frames/dynamic_wakes.py +17 -9
- foxes/models/wake_frames/farm_order.py +4 -3
- foxes/models/wake_frames/rotor_wd.py +3 -1
- foxes/models/wake_frames/seq_dynamic_wakes.py +14 -7
- foxes/models/wake_frames/streamlines.py +9 -6
- foxes/models/wake_frames/timelines.py +21 -14
- foxes/models/wake_frames/yawed_wakes.py +3 -1
- foxes/models/wake_models/induction/vortex_sheet.py +0 -1
- foxes/models/wake_models/ti/crespo_hernandez.py +2 -1
- foxes/models/wake_models/wind/bastankhah14.py +3 -2
- foxes/models/wake_models/wind/bastankhah16.py +2 -1
- foxes/models/wake_models/wind/turbopark.py +9 -7
- foxes/models/wake_superpositions/ws_product.py +0 -1
- foxes/output/__init__.py +2 -1
- foxes/output/calc_points.py +7 -4
- foxes/output/farm_layout.py +30 -18
- foxes/output/farm_results_eval.py +61 -38
- foxes/output/grids.py +8 -7
- foxes/output/output.py +9 -20
- foxes/output/plt.py +19 -0
- foxes/output/results_writer.py +10 -11
- foxes/output/rose_plot.py +448 -224
- foxes/output/rotor_point_plots.py +7 -3
- foxes/output/slice_data.py +1 -1
- foxes/output/state_turbine_map.py +5 -1
- foxes/output/state_turbine_table.py +7 -3
- foxes/output/turbine_type_curves.py +7 -2
- foxes/utils/__init__.py +1 -2
- foxes/utils/dict.py +107 -3
- foxes/utils/geopandas_utils.py +3 -2
- foxes/utils/subclasses.py +69 -0
- {foxes-1.1.1.dist-info → foxes-1.2.1.dist-info}/METADATA +18 -18
- {foxes-1.1.1.dist-info → foxes-1.2.1.dist-info}/RECORD +145 -145
- {foxes-1.1.1.dist-info → foxes-1.2.1.dist-info}/WHEEL +1 -1
- foxes-1.2.1.dist-info/entry_points.txt +3 -0
- tests/0_consistency/iterative/test_iterative.py +65 -67
- tests/0_consistency/partial_wakes/test_partial_wakes.py +58 -61
- tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +56 -53
- tests/1_verification/flappy_0_6/abl_states/test_abl_states.py +41 -41
- tests/1_verification/flappy_0_6/partial_top_hat/test_partial_top_hat.py +34 -34
- tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +57 -52
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +58 -54
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +80 -76
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +80 -76
- tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +58 -51
- tests/1_verification/flappy_0_6_2/grid_rotors/test_grid_rotors.py +101 -103
- tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +67 -64
- tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +58 -54
- examples/windio/run.py +0 -29
- foxes/data/states/windio_timeseries_5000.nc +0 -0
- foxes/data/windio/DTU_10MW_turbine.yaml +0 -10
- foxes/data/windio/__init__.py +0 -0
- foxes/data/windio/windio_5turbines_timeseries.yaml +0 -79
- foxes/input/windio/__init__.py +0 -11
- foxes/input/windio/read_outputs.py +0 -172
- foxes/input/windio/runner.py +0 -183
- foxes/input/windio/windio.py +0 -193
- foxes/utils/windrose_plot.py +0 -152
- {foxes-1.1.1.dist-info → foxes-1.2.1.dist-info}/LICENSE +0 -0
- {foxes-1.1.1.dist-info → foxes-1.2.1.dist-info}/top_level.txt +0 -0
foxes/output/rose_plot.py
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
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
|
-
import foxes.variables as FV
|
|
5
|
-
import foxes.constants as FC
|
|
6
|
-
from foxes.utils import wd2uv, uv2wd, TabWindroseAxes
|
|
7
8
|
from foxes.algorithms import Downwind
|
|
8
9
|
from foxes.core import WindFarm, Turbine
|
|
9
10
|
from foxes.models import ModelBook
|
|
11
|
+
import foxes.variables as FV
|
|
12
|
+
import foxes.constants as FC
|
|
13
|
+
|
|
10
14
|
from .output import Output
|
|
11
15
|
|
|
12
16
|
|
|
@@ -23,27 +27,38 @@ class RosePlotOutput(Output):
|
|
|
23
27
|
|
|
24
28
|
"""
|
|
25
29
|
|
|
26
|
-
def __init__(
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
farm_results=None,
|
|
33
|
+
point_results=None,
|
|
34
|
+
use_points=False,
|
|
35
|
+
**kwargs,
|
|
36
|
+
):
|
|
27
37
|
"""
|
|
28
38
|
Constructor.
|
|
29
39
|
|
|
30
40
|
Parameters
|
|
31
41
|
----------
|
|
32
|
-
|
|
33
|
-
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
|
|
49
|
+
kwargs: dict, optional
|
|
50
|
+
Additional parameters for the base class
|
|
34
51
|
|
|
35
52
|
"""
|
|
36
|
-
|
|
37
|
-
if
|
|
38
|
-
self.
|
|
39
|
-
elif dims[1] == FC.POINT:
|
|
53
|
+
super().__init__(**kwargs)
|
|
54
|
+
if use_points or (farm_results is None and point_results is not None):
|
|
55
|
+
self.results = point_results
|
|
40
56
|
self._rtype = FC.POINT
|
|
57
|
+
elif farm_results is not None:
|
|
58
|
+
self.results = farm_results
|
|
59
|
+
self._rtype = FC.TURBINE
|
|
41
60
|
else:
|
|
42
|
-
raise KeyError(
|
|
43
|
-
f"Results dimension 1 is neither '{FC.TURBINE}' nor '{FC.POINT}': dims = {results.dims}"
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
self.results = results.to_dataframe()
|
|
61
|
+
raise KeyError(f"Require either farm_results or point_results")
|
|
47
62
|
|
|
48
63
|
@classmethod
|
|
49
64
|
def get_data_info(cls, dname):
|
|
@@ -100,241 +115,211 @@ class RosePlotOutput(Output):
|
|
|
100
115
|
|
|
101
116
|
def get_data(
|
|
102
117
|
self,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
wd_sectors,
|
|
119
|
+
ws_var,
|
|
120
|
+
ws_bins,
|
|
106
121
|
wd_var=FV.AMB_WD,
|
|
107
|
-
turbine=
|
|
108
|
-
point=
|
|
109
|
-
|
|
122
|
+
turbine=0,
|
|
123
|
+
point=0,
|
|
124
|
+
add_inf=False,
|
|
110
125
|
):
|
|
111
126
|
"""
|
|
112
|
-
|
|
127
|
+
Generates the plot data
|
|
113
128
|
|
|
114
129
|
Parameters
|
|
115
130
|
----------
|
|
116
|
-
|
|
117
|
-
The number of wind
|
|
118
|
-
|
|
119
|
-
The
|
|
120
|
-
|
|
121
|
-
The
|
|
122
|
-
wd_var: str
|
|
123
|
-
The wind direction variable
|
|
124
|
-
turbine: int
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
start0: bool
|
|
133
|
-
Flag for starting the first sector at
|
|
134
|
-
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
|
|
135
147
|
|
|
136
148
|
Returns
|
|
137
149
|
-------
|
|
138
|
-
|
|
139
|
-
The
|
|
150
|
+
data: xarray.Dataset
|
|
151
|
+
The plot data
|
|
140
152
|
|
|
141
153
|
"""
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
data =
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
grp = data[[wd_var, lgd, "frequency"]].groupby([wd_var, lgd], observed=False)
|
|
174
|
-
data = grp.sum().reset_index()
|
|
175
|
-
|
|
176
|
-
data[wd_var] = data[wd_var].astype(np.float64)
|
|
177
|
-
data[lgd] = list(data[lgd])
|
|
178
|
-
if start0:
|
|
179
|
-
data[wd_var] += dwd / 2
|
|
180
|
-
|
|
181
|
-
ii = pd.IntervalIndex(data[lgd])
|
|
182
|
-
data[var] = ii.mid
|
|
183
|
-
data[f"bin_min_{var}"] = ii.left
|
|
184
|
-
data[f"bin_max_{var}"] = ii.right
|
|
185
|
-
data[f"bin_min_{wd_var}"] = data[wd_var] - dwd / 2
|
|
186
|
-
data[f"bin_max_{wd_var}"] = data[wd_var] + dwd / 2
|
|
187
|
-
data["sector"] = (data[wd_var] / dwd).astype(int)
|
|
188
|
-
|
|
189
|
-
data = data[
|
|
190
|
-
[
|
|
191
|
-
wd_var,
|
|
192
|
-
var,
|
|
193
|
-
"sector",
|
|
194
|
-
f"bin_min_{wd_var}",
|
|
195
|
-
f"bin_max_{wd_var}",
|
|
196
|
-
f"bin_min_{var}",
|
|
197
|
-
f"bin_max_{var}",
|
|
198
|
-
lgd,
|
|
199
|
-
"frequency",
|
|
200
|
-
]
|
|
201
|
-
]
|
|
202
|
-
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
|
+
)
|
|
203
184
|
|
|
204
185
|
return data
|
|
205
186
|
|
|
206
187
|
def get_figure(
|
|
207
188
|
self,
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
189
|
+
wd_sectors,
|
|
190
|
+
ws_var,
|
|
191
|
+
ws_bins,
|
|
211
192
|
wd_var=FV.AMB_WD,
|
|
212
|
-
turbine=None,
|
|
213
|
-
point=None,
|
|
214
|
-
title=None,
|
|
215
|
-
legend=None,
|
|
216
|
-
design="bar",
|
|
217
|
-
start0=False,
|
|
218
193
|
fig=None,
|
|
194
|
+
ax=None,
|
|
219
195
|
figsize=None,
|
|
220
|
-
|
|
196
|
+
freq_delta=3,
|
|
197
|
+
cmap="summer",
|
|
198
|
+
title=None,
|
|
199
|
+
legend_pars=None,
|
|
221
200
|
ret_data=False,
|
|
222
201
|
**kwargs,
|
|
223
202
|
):
|
|
224
203
|
"""
|
|
225
|
-
Creates figure
|
|
204
|
+
Creates the figure
|
|
226
205
|
|
|
227
206
|
Parameters
|
|
228
207
|
----------
|
|
229
|
-
|
|
230
|
-
The number of wind
|
|
231
|
-
|
|
232
|
-
The
|
|
233
|
-
|
|
234
|
-
The
|
|
235
|
-
wd_var: str
|
|
236
|
-
The wind direction variable
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
point: int, optional
|
|
242
|
-
Only relevant in case of point results.
|
|
243
|
-
If None, mean over all points.
|
|
244
|
-
Else, data from a single point
|
|
245
|
-
title. str, optional
|
|
246
|
-
The title
|
|
247
|
-
legend: str, optional
|
|
248
|
-
The data legend string
|
|
249
|
-
design: str
|
|
250
|
-
The wind rose design: bar, contour, ...
|
|
251
|
-
start0: bool
|
|
252
|
-
Flag for starting the first sector at
|
|
253
|
-
zero degrees instead of minus half width
|
|
254
|
-
fig: matplotlib.Figure
|
|
255
|
-
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
|
|
256
220
|
figsize: tuple, optional
|
|
257
|
-
The figsize
|
|
258
|
-
|
|
259
|
-
The
|
|
260
|
-
|
|
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
|
|
261
231
|
ret_data: bool
|
|
262
232
|
Flag for returning wind rose data
|
|
263
233
|
kwargs: dict, optional
|
|
264
|
-
Additional
|
|
265
|
-
plot function
|
|
234
|
+
Additional parameters for get_data
|
|
266
235
|
|
|
267
236
|
Returns
|
|
268
237
|
-------
|
|
269
|
-
|
|
270
|
-
The
|
|
271
|
-
data:
|
|
272
|
-
The
|
|
238
|
+
ax: pyplot.Axes
|
|
239
|
+
The axes object
|
|
240
|
+
data: xarray.Dataset, optional
|
|
241
|
+
The plot data
|
|
273
242
|
|
|
274
243
|
"""
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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)
|
|
290
286
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
bin_min_dir=np.sort(wrdata[f"bin_min_{wd_var}"].unique()),
|
|
300
|
-
bin_min_var=np.sort(wrdata[f"bin_min_{var}"].unique()),
|
|
301
|
-
bin_max_var=np.sort(wrdata[f"bin_max_{var}"].unique()),
|
|
302
|
-
**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}",
|
|
303
295
|
)
|
|
304
|
-
|
|
305
|
-
|
|
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)
|
|
306
303
|
|
|
307
304
|
if ret_data:
|
|
308
|
-
return
|
|
305
|
+
return ax, data
|
|
309
306
|
else:
|
|
310
|
-
return
|
|
307
|
+
return ax
|
|
311
308
|
|
|
312
|
-
def write_figure(
|
|
313
|
-
self,
|
|
314
|
-
file_name,
|
|
315
|
-
sectors,
|
|
316
|
-
var,
|
|
317
|
-
var_bins,
|
|
318
|
-
ret_data=False,
|
|
319
|
-
**kwargs,
|
|
320
|
-
):
|
|
309
|
+
def write_figure(self, file_name, *args, ret_data=False, **kwargs):
|
|
321
310
|
"""
|
|
322
311
|
Write rose plot to file
|
|
323
312
|
|
|
324
313
|
Parameters
|
|
325
314
|
----------
|
|
326
315
|
file_name: str
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
var: str
|
|
331
|
-
The data variable name
|
|
332
|
-
var_bins: list of float
|
|
333
|
-
The variable bin separation values
|
|
316
|
+
Name of the output file
|
|
317
|
+
args: tuple, optional
|
|
318
|
+
Additional parameters for get_figure
|
|
334
319
|
ret_data: bool
|
|
335
320
|
Flag for returning wind rose data
|
|
336
321
|
kwargs: dict, optional
|
|
337
|
-
Additional parameters for get_figure
|
|
322
|
+
Additional parameters for get_figure
|
|
338
323
|
|
|
339
324
|
Returns
|
|
340
325
|
-------
|
|
@@ -343,38 +328,46 @@ class RosePlotOutput(Output):
|
|
|
343
328
|
|
|
344
329
|
"""
|
|
345
330
|
|
|
346
|
-
r = self.get_figure(
|
|
347
|
-
|
|
348
|
-
var=var,
|
|
349
|
-
var_bins=var_bins,
|
|
350
|
-
ret_data=ret_data,
|
|
351
|
-
**kwargs,
|
|
352
|
-
)
|
|
331
|
+
r = self.get_figure(*args, ret_data=ret_data, **kwargs)
|
|
332
|
+
fpath = self.get_fpath(file_name)
|
|
353
333
|
if ret_data:
|
|
354
|
-
r[0].
|
|
334
|
+
r[0].get_figure().savefig(fpath, bbox_inches="tight")
|
|
355
335
|
return r[1]
|
|
356
336
|
else:
|
|
357
|
-
r.
|
|
337
|
+
r.get_figure().savefig(fpath, bbox_inches="tight")
|
|
358
338
|
|
|
359
339
|
|
|
360
340
|
class StatesRosePlotOutput(RosePlotOutput):
|
|
361
341
|
"""
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
Parameters
|
|
365
|
-
----------
|
|
366
|
-
states: foxes.core.States
|
|
367
|
-
The states from which to compute the wind rose
|
|
368
|
-
point: numpy.ndarray
|
|
369
|
-
The evaluation point, shape: (3,)
|
|
370
|
-
mbook: foxes.models.ModelBook, optional
|
|
371
|
-
The model book
|
|
372
|
-
|
|
342
|
+
Class for rose plot creation directly from states
|
|
373
343
|
:group: output
|
|
374
|
-
|
|
375
344
|
"""
|
|
376
345
|
|
|
377
|
-
def __init__(
|
|
346
|
+
def __init__(
|
|
347
|
+
self,
|
|
348
|
+
states,
|
|
349
|
+
point,
|
|
350
|
+
mbook=None,
|
|
351
|
+
ws_var=FV.AMB_REWS,
|
|
352
|
+
**kwargs,
|
|
353
|
+
):
|
|
354
|
+
"""
|
|
355
|
+
Constructor.
|
|
356
|
+
|
|
357
|
+
Parameters
|
|
358
|
+
----------
|
|
359
|
+
states: foxes.core.States
|
|
360
|
+
The states from which to compute the wind rose
|
|
361
|
+
point: numpy.ndarray
|
|
362
|
+
The evaluation point, shape: (3,)
|
|
363
|
+
mbook: foxes.models.ModelBook, optional
|
|
364
|
+
The model book
|
|
365
|
+
ws_var: str
|
|
366
|
+
The wind speed variable name
|
|
367
|
+
kwargs: dict, optional
|
|
368
|
+
Additional parameters for the base class
|
|
369
|
+
|
|
370
|
+
"""
|
|
378
371
|
farm = WindFarm()
|
|
379
372
|
farm.add_turbine(
|
|
380
373
|
Turbine(
|
|
@@ -390,4 +383,235 @@ class StatesRosePlotOutput(RosePlotOutput):
|
|
|
390
383
|
|
|
391
384
|
results = algo.calc_farm(ambient=True).rename_vars({ws_var: FV.AMB_WS})
|
|
392
385
|
|
|
393
|
-
super().__init__(results)
|
|
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")
|