foxes 0.8.1__py3-none-any.whl → 1.0__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.

Files changed (175) hide show
  1. docs/source/conf.py +353 -0
  2. examples/abl_states/run.py +160 -0
  3. examples/compare_rotors_pwakes/run.py +217 -0
  4. examples/compare_wakes/run.py +241 -0
  5. examples/dyn_wakes/run.py +311 -0
  6. examples/field_data_nc/run.py +121 -0
  7. examples/induction_RHB/run.py +201 -0
  8. examples/multi_height/run.py +113 -0
  9. examples/power_mask/run.py +249 -0
  10. examples/random_timeseries/run.py +210 -0
  11. examples/scan_row/run.py +193 -0
  12. examples/sector_management/run.py +162 -0
  13. examples/sequential/run.py +209 -0
  14. examples/single_state/run.py +201 -0
  15. examples/states_lookup_table/run.py +137 -0
  16. examples/streamline_wakes/run.py +138 -0
  17. examples/tab_file/run.py +142 -0
  18. examples/timelines/run.py +267 -0
  19. examples/timeseries/run.py +183 -0
  20. examples/timeseries_slurm/run.py +185 -0
  21. examples/wind_rose/run.py +141 -0
  22. examples/windio/run.py +29 -0
  23. examples/yawed_wake/run.py +196 -0
  24. foxes/__init__.py +4 -8
  25. foxes/algorithms/__init__.py +1 -1
  26. foxes/algorithms/downwind/downwind.py +232 -101
  27. foxes/algorithms/downwind/models/farm_wakes_calc.py +11 -6
  28. foxes/algorithms/downwind/models/init_farm_data.py +1 -1
  29. foxes/algorithms/downwind/models/point_wakes_calc.py +5 -6
  30. foxes/algorithms/downwind/models/reorder_farm_output.py +0 -1
  31. foxes/algorithms/downwind/models/set_amb_point_results.py +4 -2
  32. foxes/algorithms/iterative/iterative.py +73 -33
  33. foxes/algorithms/iterative/models/farm_wakes_calc.py +11 -6
  34. foxes/algorithms/sequential/models/plugin.py +1 -1
  35. foxes/algorithms/sequential/sequential.py +126 -255
  36. foxes/constants.py +17 -2
  37. foxes/core/__init__.py +1 -0
  38. foxes/core/algorithm.py +631 -146
  39. foxes/core/data.py +252 -20
  40. foxes/core/data_calc_model.py +13 -289
  41. foxes/core/engine.py +630 -0
  42. foxes/core/farm_controller.py +37 -9
  43. foxes/core/farm_data_model.py +15 -0
  44. foxes/core/model.py +133 -80
  45. foxes/core/point_data_model.py +15 -0
  46. foxes/core/rotor_model.py +27 -21
  47. foxes/core/states.py +16 -0
  48. foxes/core/turbine_type.py +28 -0
  49. foxes/core/wake_frame.py +22 -4
  50. foxes/core/wake_model.py +2 -3
  51. foxes/data/windio/windio_5turbines_timeseries.yaml +23 -1
  52. foxes/engines/__init__.py +16 -0
  53. foxes/engines/dask.py +975 -0
  54. foxes/engines/default.py +75 -0
  55. foxes/engines/futures.py +72 -0
  56. foxes/engines/mpi.py +38 -0
  57. foxes/engines/multiprocess.py +74 -0
  58. foxes/engines/numpy.py +185 -0
  59. foxes/engines/pool.py +263 -0
  60. foxes/engines/single.py +139 -0
  61. foxes/input/farm_layout/__init__.py +1 -0
  62. foxes/input/farm_layout/from_csv.py +4 -0
  63. foxes/input/farm_layout/from_json.py +1 -1
  64. foxes/input/farm_layout/grid.py +2 -2
  65. foxes/input/farm_layout/ring.py +65 -0
  66. foxes/input/farm_layout/row.py +2 -2
  67. foxes/input/states/__init__.py +6 -0
  68. foxes/input/states/create/random_abl_states.py +1 -1
  69. foxes/input/states/field_data_nc.py +157 -32
  70. foxes/input/states/multi_height.py +127 -13
  71. foxes/input/states/one_point_flow.py +577 -0
  72. foxes/input/states/scan_ws.py +73 -2
  73. foxes/input/states/states_table.py +204 -35
  74. foxes/input/windio/__init__.py +1 -1
  75. foxes/input/windio/get_states.py +44 -23
  76. foxes/input/windio/read_attributes.py +41 -16
  77. foxes/input/windio/read_farm.py +116 -102
  78. foxes/input/windio/read_fields.py +13 -6
  79. foxes/input/windio/read_outputs.py +63 -22
  80. foxes/input/windio/runner.py +31 -17
  81. foxes/input/windio/windio.py +36 -22
  82. foxes/models/ground_models/wake_mirror.py +8 -4
  83. foxes/models/model_book.py +29 -18
  84. foxes/models/partial_wakes/rotor_points.py +3 -3
  85. foxes/models/rotor_models/centre.py +4 -0
  86. foxes/models/rotor_models/grid.py +22 -23
  87. foxes/models/rotor_models/levels.py +4 -5
  88. foxes/models/turbine_models/calculator.py +0 -2
  89. foxes/models/turbine_models/lookup_table.py +27 -2
  90. foxes/models/turbine_models/rotor_centre_calc.py +4 -3
  91. foxes/models/turbine_models/set_farm_vars.py +103 -34
  92. foxes/models/turbine_types/PCt_file.py +24 -0
  93. foxes/models/turbine_types/PCt_from_two.py +24 -0
  94. foxes/models/turbine_types/__init__.py +1 -0
  95. foxes/models/turbine_types/lookup.py +316 -0
  96. foxes/models/turbine_types/null_type.py +50 -0
  97. foxes/models/turbine_types/wsrho2PCt_from_two.py +24 -0
  98. foxes/models/turbine_types/wsti2PCt_from_two.py +24 -0
  99. foxes/models/vertical_profiles/data_profile.py +1 -1
  100. foxes/models/wake_frames/__init__.py +1 -0
  101. foxes/models/wake_frames/dynamic_wakes.py +424 -0
  102. foxes/models/wake_frames/farm_order.py +23 -3
  103. foxes/models/wake_frames/rotor_wd.py +4 -2
  104. foxes/models/wake_frames/seq_dynamic_wakes.py +56 -63
  105. foxes/models/wake_frames/streamlines.py +19 -20
  106. foxes/models/wake_frames/timelines.py +328 -127
  107. foxes/models/wake_frames/yawed_wakes.py +4 -1
  108. foxes/models/wake_models/dist_sliced.py +1 -3
  109. foxes/models/wake_models/induction/rankine_half_body.py +4 -4
  110. foxes/models/wake_models/induction/rathmann.py +2 -2
  111. foxes/models/wake_models/induction/self_similar.py +2 -2
  112. foxes/models/wake_models/induction/vortex_sheet.py +2 -2
  113. foxes/models/wake_models/ti/iec_ti.py +34 -17
  114. foxes/models/wake_models/top_hat.py +1 -1
  115. foxes/models/wake_models/wind/bastankhah14.py +2 -2
  116. foxes/models/wake_models/wind/bastankhah16.py +8 -7
  117. foxes/models/wake_models/wind/jensen.py +1 -1
  118. foxes/models/wake_models/wind/turbopark.py +2 -2
  119. foxes/output/__init__.py +4 -1
  120. foxes/output/farm_layout.py +2 -2
  121. foxes/output/flow_plots_2d/__init__.py +0 -1
  122. foxes/output/flow_plots_2d/flow_plots.py +70 -30
  123. foxes/output/grids.py +91 -21
  124. foxes/output/seq_plugins/__init__.py +2 -0
  125. foxes/output/{flow_plots_2d → seq_plugins}/seq_flow_ani_plugin.py +62 -20
  126. foxes/output/seq_plugins/seq_wake_debug_plugin.py +145 -0
  127. foxes/output/slice_data.py +131 -111
  128. foxes/output/state_turbine_map.py +18 -13
  129. foxes/output/state_turbine_table.py +19 -19
  130. foxes/utils/__init__.py +1 -1
  131. foxes/utils/dev_utils.py +42 -0
  132. foxes/utils/dict.py +1 -1
  133. foxes/utils/factory.py +147 -52
  134. foxes/utils/pandas_helpers.py +4 -3
  135. foxes/utils/wind_dir.py +0 -2
  136. foxes/utils/xarray_utils.py +25 -13
  137. foxes/variables.py +37 -0
  138. {foxes-0.8.1.dist-info → foxes-1.0.dist-info}/METADATA +72 -34
  139. foxes-1.0.dist-info/RECORD +307 -0
  140. {foxes-0.8.1.dist-info → foxes-1.0.dist-info}/WHEEL +1 -1
  141. foxes-1.0.dist-info/top_level.txt +4 -0
  142. tests/0_consistency/iterative/test_iterative.py +92 -0
  143. tests/0_consistency/partial_wakes/test_partial_wakes.py +90 -0
  144. tests/1_verification/flappy_0_6/PCt_files/flappy/run.py +85 -0
  145. tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +103 -0
  146. tests/1_verification/flappy_0_6/abl_states/flappy/run.py +85 -0
  147. tests/1_verification/flappy_0_6/abl_states/test_abl_states.py +87 -0
  148. tests/1_verification/flappy_0_6/partial_top_hat/flappy/run.py +82 -0
  149. tests/1_verification/flappy_0_6/partial_top_hat/test_partial_top_hat.py +82 -0
  150. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/flappy/run.py +92 -0
  151. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +93 -0
  152. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/flappy/run.py +92 -0
  153. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +96 -0
  154. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/flappy/run.py +94 -0
  155. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +122 -0
  156. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/flappy/run.py +94 -0
  157. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +122 -0
  158. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/flappy/run.py +92 -0
  159. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +93 -0
  160. tests/1_verification/flappy_0_6_2/grid_rotors/flappy/run.py +85 -0
  161. tests/1_verification/flappy_0_6_2/grid_rotors/test_grid_rotors.py +130 -0
  162. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/flappy/run.py +96 -0
  163. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +116 -0
  164. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/flappy/run.py +93 -0
  165. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +99 -0
  166. tests/3_examples/test_examples.py +34 -0
  167. foxes/VERSION +0 -1
  168. foxes/output/flow_plots_2d.py +0 -0
  169. foxes/utils/plotly_helpers.py +0 -19
  170. foxes/utils/runners/__init__.py +0 -1
  171. foxes/utils/runners/runners.py +0 -280
  172. foxes-0.8.1.dist-info/RECORD +0 -248
  173. foxes-0.8.1.dist-info/top_level.txt +0 -1
  174. foxes-0.8.1.dist-info/zip-safe +0 -1
  175. {foxes-0.8.1.dist-info → foxes-1.0.dist-info}/LICENSE +0 -0
foxes/core/engine.py ADDED
@@ -0,0 +1,630 @@
1
+ import numpy as np
2
+ from abc import ABC, abstractmethod
3
+ from os import cpu_count
4
+ from tqdm import tqdm
5
+ from xarray import Dataset
6
+
7
+ from foxes.core import MData, FData, TData
8
+ from foxes.utils import all_subclasses
9
+ import foxes.constants as FC
10
+
11
+ __global_engine_data__ = dict(
12
+ engine=None,
13
+ )
14
+
15
+
16
+ class Engine(ABC):
17
+ """
18
+ Abstract base clas for foxes calculation engines
19
+
20
+ Attributes
21
+ ----------
22
+ chunk_size_states: int
23
+ The size of a states chunk
24
+ chunk_size_points: int
25
+ The size of a points chunk
26
+ n_procs: int, optional
27
+ The number of processes to be used,
28
+ or None for automatic
29
+ verbosity: int
30
+ The verbosity level, 0 = silent
31
+
32
+ :group: core
33
+
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ chunk_size_states=None,
39
+ chunk_size_points=None,
40
+ n_procs=None,
41
+ verbosity=1,
42
+ ):
43
+ """
44
+ Constructor.
45
+
46
+ Parameters
47
+ ----------
48
+ chunk_size_states: int, optional
49
+ The size of a states chunk
50
+ chunk_size_points: int, optional
51
+ The size of a points chunk
52
+ n_procs: int, optional
53
+ The number of processes to be used,
54
+ or None for automatic
55
+ verbosity: int
56
+ The verbosity level, 0 = silent
57
+
58
+ """
59
+ self.chunk_size_states = chunk_size_states
60
+ self.chunk_size_points = chunk_size_points
61
+ self.n_procs = n_procs if n_procs is not None else max(cpu_count() - 1, 1)
62
+ self.verbosity = verbosity
63
+ self.__initialized = False
64
+ self.__entered = False
65
+
66
+ def __repr__(self):
67
+ s = f"n_procs={self.n_procs}, chunk_size_states={self.chunk_size_states}, chunk_size_points={self.chunk_size_points}"
68
+ return f"{type(self).__name__}({s})"
69
+
70
+ def __enter__(self):
71
+ if self.__entered:
72
+ raise ValueError(f"Enter called for already entered engine")
73
+ self.__entered = True
74
+ if not self.initialized:
75
+ self.initialize()
76
+ return self
77
+
78
+ def __exit__(self, *exit_args):
79
+ if not self.__entered:
80
+ raise ValueError(f"Exit called for not entered engine")
81
+ self.__entered = False
82
+ if self.initialized:
83
+ self.finalize(*exit_args)
84
+
85
+ def __del__(self):
86
+ if self.initialized:
87
+ self.finalize()
88
+
89
+ @property
90
+ def entered(self):
91
+ """
92
+ Flag that this model has been entered.
93
+
94
+ Returns
95
+ -------
96
+ flag: bool
97
+ True if the model has been entered.
98
+
99
+ """
100
+ return self.__entered
101
+
102
+ @property
103
+ def initialized(self):
104
+ """
105
+ Initialization flag.
106
+
107
+ Returns
108
+ -------
109
+ flag: bool
110
+ True if the model has been initialized.
111
+
112
+ """
113
+ return self.__initialized
114
+
115
+ def initialize(self):
116
+ """
117
+ Initializes the engine.
118
+ """
119
+ if not self.entered:
120
+ self.__enter__()
121
+ elif not self.initialized:
122
+ if get_engine(error=False, default=False) is not None:
123
+ raise ValueError(
124
+ f"Cannot initialize engine '{type(self).__name__}', since engine already set to '{type(get_engine()).__name__}'"
125
+ )
126
+ global __global_engine_data__
127
+ __global_engine_data__["engine"] = self
128
+ self.__initialized = True
129
+
130
+ def finalize(self, type=None, value=None, traceback=None):
131
+ """
132
+ Finalizes the engine.
133
+
134
+ Parameters
135
+ ----------
136
+ type: object, optional
137
+ Dummy argument for the exit function
138
+ value: object, optional
139
+ Dummy argument for the exit function
140
+ traceback: object, optional
141
+ Dummy argument for the exit function
142
+
143
+ """
144
+ if self.entered:
145
+ self.__exit__(type, value, traceback)
146
+ elif self.initialized:
147
+ global __global_engine_data__
148
+ __global_engine_data__["engine"] = None
149
+ self.__initialized = False
150
+
151
+ def print(self, *args, level=1, **kwargs):
152
+ """Prints based on verbosity"""
153
+ if self.verbosity >= level:
154
+ print(*args, **kwargs)
155
+
156
+ @property
157
+ def loop_dims(self):
158
+ """
159
+ Gets the loop dimensions (possibly chunked)
160
+
161
+ Returns
162
+ -------
163
+ dims: list of str
164
+ The loop dimensions (possibly chunked)
165
+
166
+ """
167
+ if self.chunk_size_states is None and self.chunk_size_states is None:
168
+ return []
169
+ elif self.chunk_size_states is None:
170
+ return [FC.TARGET]
171
+ elif self.chunk_size_points is None:
172
+ return [FC.STATE]
173
+ else:
174
+ return [FC.STATE, FC.TARGET]
175
+
176
+ def select_subsets(self, *datasets, sel=None, isel=None):
177
+ """
178
+ Takes subsets of datasets
179
+
180
+ Parameters
181
+ ----------
182
+ datasets: tuple
183
+ The xarray.Dataset or xarray.Dataarray objects
184
+ sel: dict, optional
185
+ The selection dictionary
186
+ isel: dict, optional
187
+ The index selection dictionary
188
+
189
+ Returns
190
+ -------
191
+ subsets: list
192
+ The subsets of the input data
193
+
194
+ """
195
+ if sel is not None:
196
+ new_datasets = []
197
+ for data in datasets:
198
+ if data is not None:
199
+ s = {c: u for c, u in sel.items() if c in data.coords}
200
+ new_datasets.append(data.sel(s) if len(s) else data)
201
+ else:
202
+ new_datasets.append(data)
203
+ datasets = new_datasets
204
+
205
+ if isel is not None:
206
+ new_datasets = []
207
+ for data in datasets:
208
+ if data is not None:
209
+ s = {c: u for c, u in isel.items() if c in data.coords}
210
+ new_datasets.append(data.isel(s) if len(s) else data)
211
+ else:
212
+ new_datasets.append(data)
213
+ datasets = new_datasets
214
+
215
+ return datasets
216
+
217
+ def calc_chunk_sizes(self, n_states, n_targets=1):
218
+ """
219
+ Computes the sizes of states and points chunks
220
+
221
+ Parameters
222
+ ----------
223
+ n_states: int
224
+ The number of states
225
+ n_targets: int
226
+ The number of point targets
227
+
228
+ Returns
229
+ -------
230
+ chunk_sizes_states: numpy.ndarray
231
+ The sizes of all states chunks, shape: (n_chunks_states,)
232
+ chunk_sizes_targets: numpy.ndarray
233
+ The sizes of all targets chunks, shape: (n_chunks_targets,)
234
+
235
+ """
236
+ # determine states chunks:
237
+ if self.chunk_size_states is None:
238
+ n_chunks_states = min(self.n_procs, n_states)
239
+ chunk_size_states = max(int(n_states / self.n_procs), 1)
240
+ else:
241
+ chunk_size_states = min(n_states, self.chunk_size_states)
242
+ n_chunks_states = max(int(n_states / chunk_size_states), 1)
243
+ if int(n_states / n_chunks_states) > chunk_size_states:
244
+ n_chunks_states += 1
245
+ chunk_size_states = int(n_states / n_chunks_states)
246
+ chunk_sizes_states = np.full(n_chunks_states, chunk_size_states)
247
+ extra = n_states - n_chunks_states * chunk_size_states
248
+ if extra > 0:
249
+ chunk_sizes_states[-extra:] += 1
250
+
251
+ s = np.sum(chunk_sizes_states)
252
+ assert (
253
+ s == n_states
254
+ ), f"States count mismatch: Expecting {n_states}, chunks sum is {s}. Chunks: {[int(c) for c in chunk_sizes_states]}"
255
+
256
+ # determine points chunks:
257
+ chunk_sizes_targets = [n_targets]
258
+ if n_targets > 1:
259
+ if self.chunk_size_points is None:
260
+ chunk_size_targets = n_targets
261
+ n_chunks_targets = 1
262
+ else:
263
+ chunk_size_targets = min(n_targets, self.chunk_size_points)
264
+ n_chunks_targets = max(int(n_targets / chunk_size_targets), 1)
265
+ if int(n_targets / n_chunks_targets) > chunk_size_targets:
266
+ n_chunks_targets += 1
267
+ chunk_size_targets = int(n_targets / n_chunks_targets)
268
+ chunk_sizes_targets = np.full(n_chunks_targets, chunk_size_targets)
269
+ extra = n_targets - n_chunks_targets * chunk_size_targets
270
+ if extra > 0:
271
+ chunk_sizes_targets[-extra:] += 1
272
+
273
+ s = np.sum(chunk_sizes_targets)
274
+ assert (
275
+ s == n_targets
276
+ ), f"Targets count mismatch: Expecting {n_targets}, chunks sum is {s}. Chunks: {[int(c) for c in chunk_sizes_targets]}"
277
+
278
+ return chunk_sizes_states, chunk_sizes_targets
279
+
280
+ def get_chunk_input_data(
281
+ self,
282
+ algo,
283
+ model_data,
284
+ farm_data,
285
+ point_data,
286
+ states_i0_i1,
287
+ targets_i0_i1,
288
+ out_vars,
289
+ ):
290
+ """
291
+ Extracts the data for a single chunk calculation
292
+
293
+ Parameters
294
+ ----------
295
+ algo: foxes.core.Algorithm
296
+ The algorithm object
297
+ model_data: xarray.Dataset
298
+ The initial model data
299
+ farm_data: xarray.Dataset
300
+ The initial farm data
301
+ point_data: xarray.Dataset
302
+ The initial point data
303
+ states_i0_i1: tuple
304
+ The (start, end) values of the states
305
+ targets_i0_i1: tuple
306
+ The (start, end) values of the targets
307
+ out_vars: list of str
308
+ Names of the output variables
309
+
310
+ Returns
311
+ -------
312
+ data: list of foxes.core.Data
313
+ Either [mdata, fdata] or [mdata, fdata, tdata]
314
+
315
+ """
316
+ # prepare:
317
+ i0_states, i1_states = states_i0_i1
318
+ i0_targets, i1_targets = targets_i0_i1
319
+ s_states = np.s_[i0_states:i1_states]
320
+ s_targets = np.s_[i0_targets:i1_targets]
321
+
322
+ # create mdata:
323
+ mdata = MData.from_dataset(
324
+ model_data,
325
+ s_states=s_states,
326
+ loop_dims=[FC.STATE],
327
+ states_i0=i0_states,
328
+ copy=True,
329
+ )
330
+
331
+ # create fdata:
332
+ if point_data is None:
333
+
334
+ def cb(data, dims):
335
+ n_states = i1_states - i0_states
336
+ for o in set(out_vars).difference(data.keys()):
337
+ data[o] = np.full(
338
+ (n_states, algo.n_turbines), np.nan, dtype=FC.DTYPE
339
+ )
340
+ dims[o] = (FC.STATE, FC.TURBINE)
341
+
342
+ else:
343
+ cb = None
344
+ fdata = FData.from_dataset(
345
+ farm_data,
346
+ mdata=mdata,
347
+ s_states=s_states,
348
+ callback=cb,
349
+ loop_dims=[FC.STATE],
350
+ states_i0=i0_states,
351
+ copy=True,
352
+ )
353
+
354
+ # create tdata:
355
+ tdata = None
356
+ if point_data is not None:
357
+
358
+ def cb(data, dims):
359
+ n_states = i1_states - i0_states
360
+ n_targets = i1_targets - i0_targets
361
+ for o in set(out_vars).difference(data.keys()):
362
+ data[o] = np.full((n_states, n_targets, 1), np.nan, dtype=FC.DTYPE)
363
+ dims[o] = (FC.STATE, FC.TARGET, FC.TPOINT)
364
+
365
+ tdata = TData.from_dataset(
366
+ point_data,
367
+ mdata=mdata,
368
+ s_states=s_states,
369
+ s_targets=s_targets,
370
+ callback=cb,
371
+ loop_dims=[FC.STATE, FC.TARGET],
372
+ states_i0=i0_states,
373
+ copy=True,
374
+ )
375
+
376
+ return [d for d in [mdata, fdata, tdata] if d is not None]
377
+
378
+ def combine_results(
379
+ self,
380
+ algo,
381
+ results,
382
+ model_data,
383
+ out_vars,
384
+ out_coords,
385
+ n_chunks_states,
386
+ n_chunks_targets,
387
+ goal_data,
388
+ iterative,
389
+ ):
390
+ """
391
+ Combines chunk results into final Dataset
392
+
393
+ Parameters
394
+ ----------
395
+ algo: foxes.core.Algorithm
396
+ The algorithm object
397
+ results: dict
398
+ The results from the chunk calculations,
399
+ key: (chunki_states, chunki_targets),
400
+ value: dict with numpy.ndarray values
401
+ model_data: xarray.Dataset
402
+ The initial model data
403
+ out_vars: list of str
404
+ Names of the output variables
405
+ out_coords: list of str
406
+ Names of the output coordinates
407
+ n_chunks_states: int
408
+ The number of states chunks
409
+ n_chunks_targets: int
410
+ The number of targets chunks
411
+ goal_data: foxes.core.Data
412
+ Either fdata or tdata
413
+ iterative: bool
414
+ Flag for use within the iterative algorithm
415
+
416
+ Returns
417
+ -------
418
+ ds: xarray.Dataset
419
+ The final results dataset
420
+
421
+ """
422
+ self.print("Combining results", level=2)
423
+ pbar = tqdm(total=len(out_vars)) if self.verbosity > 1 else None
424
+ data_vars = {}
425
+ for v in out_vars:
426
+ if v in results[(0, 0)][0]:
427
+ data_vars[v] = [out_coords, []]
428
+
429
+ if n_chunks_targets == 1:
430
+ alls = 0
431
+ for chunki_states in range(n_chunks_states):
432
+ r, cstore = results[(chunki_states, 0)]
433
+ data_vars[v][1].append(r[v])
434
+ alls += data_vars[v][1][-1].shape[0]
435
+ if iterative:
436
+ for k, c in cstore.items():
437
+ if k in algo.chunk_store:
438
+ algo.chunk_store[k].update(c)
439
+ else:
440
+ algo.chunk_store[k] = c
441
+ else:
442
+ for chunki_states in range(n_chunks_states):
443
+ tres = []
444
+ for chunki_points in range(n_chunks_targets):
445
+ r, cstore = results[(chunki_states, chunki_points)]
446
+ tres.append(r[v])
447
+ if iterative:
448
+ for k, c in cstore.items():
449
+ if k in algo.chunk_store:
450
+ algo.chunk_store[k].update(c)
451
+ else:
452
+ algo.chunk_store[k] = c
453
+ data_vars[v][1].append(np.concatenate(tres, axis=1))
454
+ del tres
455
+ del r, cstore
456
+ data_vars[v][1] = np.concatenate(data_vars[v][1], axis=0)
457
+ else:
458
+ data_vars[v] = (goal_data[v].dims, goal_data[v].to_numpy())
459
+
460
+ if pbar is not None:
461
+ pbar.update()
462
+ del results
463
+ if pbar is not None:
464
+ pbar.close()
465
+
466
+ # if not iterative or algo.final_iteration:
467
+ # algo.reset_chunk_store()
468
+
469
+ coords = {}
470
+ if FC.STATE in out_coords and FC.STATE in model_data.coords:
471
+ coords[FC.STATE] = model_data[FC.STATE].to_numpy()
472
+
473
+ return Dataset(
474
+ coords=coords,
475
+ data_vars={v: tuple(d) for v, d in data_vars.items()},
476
+ )
477
+
478
+ @abstractmethod
479
+ def run_calculation(self, algo, model, model_data, farm_data, point_data=None):
480
+ """
481
+ Runs the model calculation
482
+
483
+ Parameters
484
+ ----------
485
+ algo: foxes.core.Algorithm
486
+ The algorithm object
487
+ model: foxes.core.DataCalcModel
488
+ The model that whose calculate function
489
+ should be run
490
+ model_data: xarray.Dataset
491
+ The initial model data
492
+ farm_data: xarray.Dataset
493
+ The initial farm data
494
+ point_data: xarray.Dataset, optional
495
+ The initial point data
496
+
497
+ Returns
498
+ -------
499
+ results: xarray.Dataset
500
+ The model results
501
+
502
+ """
503
+ n_states = model_data.sizes[FC.STATE]
504
+ if point_data is None:
505
+ self.print(f"Calculating {n_states} states for {algo.n_turbines} turbines")
506
+ else:
507
+ self.print(
508
+ f"Calculating data at {point_data.sizes[FC.TARGET]} points for {n_states} states"
509
+ )
510
+ if not self.initialized:
511
+ raise ValueError(f"Engine '{type(self).__name__}' not initialized")
512
+ if not model.initialized:
513
+ raise ValueError(f"Model '{model.name}' not initialized")
514
+
515
+ @classmethod
516
+ def new(cls, engine_type, *args, **kwargs):
517
+ """
518
+ Run-time engine factory.
519
+
520
+ Parameters
521
+ ----------
522
+ engine_type: str
523
+ The selected derived class name
524
+ args: tuple, optional
525
+ Additional parameters for constructor
526
+ kwargs: dict, optional
527
+ Additional parameters for constructor
528
+
529
+ """
530
+
531
+ if engine_type is None:
532
+ return None
533
+ else:
534
+ engine_type = dict(
535
+ default="DefaultEngine",
536
+ threads="ThreadsEngine",
537
+ process="ProcessEngine",
538
+ xarray="XArrayEngine",
539
+ dask="DaskEngine",
540
+ multiprocess="MultiprocessEngine",
541
+ local_cluster="LocalClusterEngine",
542
+ slurm_cluster="SlurmClusterEngine",
543
+ mpi="MPIEngine",
544
+ numpy="NumpyEngine",
545
+ single="SingleChunkEngine",
546
+ ).get(engine_type, engine_type)
547
+
548
+ allc = all_subclasses(cls)
549
+ found = engine_type in [scls.__name__ for scls in allc]
550
+
551
+ if found:
552
+ for scls in allc:
553
+ if scls.__name__ == engine_type:
554
+ return scls(*args, **kwargs)
555
+
556
+ else:
557
+ estr = "engine type '{}' is not defined, available types are \n {}".format(
558
+ engine_type, sorted([i.__name__ for i in allc])
559
+ )
560
+ raise KeyError(estr)
561
+
562
+
563
+ def get_engine(error=True, default=True):
564
+ """
565
+ Gets the global calculation engine
566
+
567
+ Parameters
568
+ ----------
569
+ error: bool
570
+ Flag for raising ValueError if no
571
+ engine is found
572
+ default: bool or dict or Engine
573
+ Set default engine if not set yet
574
+
575
+ Returns
576
+ -------
577
+ engine: foxes.core.Engine
578
+ The foxes calculation engine
579
+
580
+ :group: core
581
+
582
+ """
583
+ engine = __global_engine_data__["engine"]
584
+ if engine is None:
585
+ if isinstance(default, dict):
586
+ engine = Engine.new(**default)
587
+ print(f"Selecting default engine '{engine}'")
588
+ engine.initialize()
589
+ return engine
590
+ elif isinstance(default, Engine):
591
+ print(f"Selecting default engine '{default}'")
592
+ default.initialize()
593
+ return default
594
+ elif isinstance(default, bool) and default:
595
+ engine = Engine.new(
596
+ engine_type="DefaultEngine", chunk_size_points=20000, verbosity=0
597
+ )
598
+ print(f"Selecting '{engine}'")
599
+ engine.initialize()
600
+ return engine
601
+ elif error:
602
+ raise ValueError("Engine not found.")
603
+ return engine
604
+
605
+
606
+ def has_engine():
607
+ """
608
+ Flag that checks if engine has been set
609
+
610
+ Returns
611
+ -------
612
+ flag: bool
613
+ True if engine has been set
614
+
615
+ :group: core
616
+
617
+ """
618
+ return __global_engine_data__["engine"] is not None
619
+
620
+
621
+ def reset_engine():
622
+ """
623
+ Resets the global calculation engine
624
+
625
+ :group: core
626
+
627
+ """
628
+ engine = get_engine(error=False, default=False)
629
+ if engine is not None:
630
+ engine.finalize(type=None, value=None, traceback=None)
@@ -4,6 +4,7 @@ from .farm_data_model import FarmDataModelList, FarmDataModel
4
4
  from .turbine_model import TurbineModel
5
5
  from .turbine_type import TurbineType
6
6
  import foxes.constants as FC
7
+ import foxes.variables as FV
7
8
 
8
9
 
9
10
  class FarmController(FarmDataModel):
@@ -45,7 +46,6 @@ class FarmController(FarmDataModel):
45
46
  self.turbine_model_names = None
46
47
  self.pre_rotor_models = None
47
48
  self.post_rotor_models = None
48
-
49
49
  self.pars = pars
50
50
 
51
51
  def sub_models(self):
@@ -85,6 +85,36 @@ class FarmController(FarmDataModel):
85
85
  "final": final_pars,
86
86
  }
87
87
 
88
+ def needs_rews2(self):
89
+ """
90
+ Returns flag for requirering REWS2 variable
91
+
92
+ Returns
93
+ -------
94
+ flag: bool
95
+ True if REWS2 is required
96
+
97
+ """
98
+ for tt in self.turbine_types:
99
+ if tt.needs_rews2():
100
+ return True
101
+ return False
102
+
103
+ def needs_rews3(self):
104
+ """
105
+ Returns flag for requirering REWS3 variable
106
+
107
+ Returns
108
+ -------
109
+ flag: bool
110
+ True if REWS3 is required
111
+
112
+ """
113
+ for tt in self.turbine_types:
114
+ if tt.needs_rews3():
115
+ return True
116
+ return False
117
+
88
118
  def _analyze_models(self, algo, pre_rotor, models):
89
119
  """
90
120
  Helper function for model analysis
@@ -128,11 +158,11 @@ class FarmController(FarmDataModel):
128
158
  break
129
159
 
130
160
  if pre_rotor:
131
- self.pre_rotor_models = FarmDataModelList(tmodels)
161
+ self.pre_rotor_models = FarmDataModelList(models=tmodels)
132
162
  self.pre_rotor_models.name = f"{self.name}_prer"
133
163
  mtype = "pre-rotor"
134
164
  else:
135
- self.post_rotor_models = FarmDataModelList(tmodels)
165
+ self.post_rotor_models = FarmDataModelList(models=tmodels)
136
166
  self.post_rotor_models.name = f"{self.name}_postr"
137
167
  mtype = "post-rotor"
138
168
 
@@ -312,12 +342,10 @@ class FarmController(FarmDataModel):
312
342
  The output variable names
313
343
 
314
344
  """
315
- return list(
316
- dict.fromkeys(
317
- self.pre_rotor_models.output_farm_vars(algo)
318
- + self.post_rotor_models.output_farm_vars(algo)
319
- )
320
- )
345
+ ovars = set(self.pre_rotor_models.output_farm_vars(algo))
346
+ ovars.update(self.post_rotor_models.output_farm_vars(algo))
347
+
348
+ return list(ovars)
321
349
 
322
350
  def calculate(self, algo, mdata, fdata, pre_rotor, downwind_index=None):
323
351
  """ "