foxes 0.8.2__py3-none-any.whl → 1.1.0.2__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 (215) 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/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 +190 -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 +247 -111
  27. foxes/algorithms/downwind/models/farm_wakes_calc.py +12 -7
  28. foxes/algorithms/downwind/models/init_farm_data.py +2 -2
  29. foxes/algorithms/downwind/models/point_wakes_calc.py +6 -7
  30. foxes/algorithms/downwind/models/reorder_farm_output.py +1 -2
  31. foxes/algorithms/downwind/models/set_amb_farm_results.py +1 -1
  32. foxes/algorithms/downwind/models/set_amb_point_results.py +5 -3
  33. foxes/algorithms/iterative/iterative.py +74 -34
  34. foxes/algorithms/iterative/models/farm_wakes_calc.py +12 -7
  35. foxes/algorithms/iterative/models/urelax.py +3 -3
  36. foxes/algorithms/sequential/models/plugin.py +5 -5
  37. foxes/algorithms/sequential/models/seq_state.py +1 -1
  38. foxes/algorithms/sequential/sequential.py +126 -255
  39. foxes/constants.py +22 -7
  40. foxes/core/__init__.py +1 -0
  41. foxes/core/algorithm.py +632 -147
  42. foxes/core/data.py +252 -20
  43. foxes/core/data_calc_model.py +15 -291
  44. foxes/core/engine.py +640 -0
  45. foxes/core/farm_controller.py +38 -10
  46. foxes/core/farm_data_model.py +16 -1
  47. foxes/core/ground_model.py +2 -2
  48. foxes/core/model.py +249 -182
  49. foxes/core/partial_wakes_model.py +1 -1
  50. foxes/core/point_data_model.py +17 -2
  51. foxes/core/rotor_model.py +27 -21
  52. foxes/core/states.py +17 -1
  53. foxes/core/turbine_type.py +28 -0
  54. foxes/core/wake_frame.py +30 -34
  55. foxes/core/wake_model.py +5 -5
  56. foxes/core/wake_superposition.py +1 -1
  57. foxes/data/windio/windio_5turbines_timeseries.yaml +31 -15
  58. foxes/engines/__init__.py +17 -0
  59. foxes/engines/dask.py +982 -0
  60. foxes/engines/default.py +75 -0
  61. foxes/engines/futures.py +72 -0
  62. foxes/engines/mpi.py +38 -0
  63. foxes/engines/multiprocess.py +71 -0
  64. foxes/engines/numpy.py +167 -0
  65. foxes/engines/pool.py +249 -0
  66. foxes/engines/ray.py +79 -0
  67. foxes/engines/single.py +141 -0
  68. foxes/input/farm_layout/__init__.py +1 -0
  69. foxes/input/farm_layout/from_csv.py +4 -0
  70. foxes/input/farm_layout/from_json.py +2 -2
  71. foxes/input/farm_layout/grid.py +2 -2
  72. foxes/input/farm_layout/ring.py +65 -0
  73. foxes/input/farm_layout/row.py +2 -2
  74. foxes/input/states/__init__.py +7 -0
  75. foxes/input/states/create/random_abl_states.py +1 -1
  76. foxes/input/states/field_data_nc.py +158 -33
  77. foxes/input/states/multi_height.py +128 -14
  78. foxes/input/states/one_point_flow.py +577 -0
  79. foxes/input/states/scan_ws.py +74 -3
  80. foxes/input/states/single.py +1 -1
  81. foxes/input/states/slice_data_nc.py +681 -0
  82. foxes/input/states/states_table.py +204 -35
  83. foxes/input/windio/__init__.py +2 -2
  84. foxes/input/windio/get_states.py +44 -23
  85. foxes/input/windio/read_attributes.py +48 -17
  86. foxes/input/windio/read_farm.py +116 -102
  87. foxes/input/windio/read_fields.py +16 -6
  88. foxes/input/windio/read_outputs.py +71 -24
  89. foxes/input/windio/runner.py +31 -17
  90. foxes/input/windio/windio.py +41 -23
  91. foxes/models/farm_models/turbine2farm.py +1 -1
  92. foxes/models/ground_models/wake_mirror.py +10 -6
  93. foxes/models/model_book.py +58 -20
  94. foxes/models/partial_wakes/axiwake.py +3 -3
  95. foxes/models/partial_wakes/rotor_points.py +3 -3
  96. foxes/models/partial_wakes/top_hat.py +2 -2
  97. foxes/models/point_models/set_uniform_data.py +1 -1
  98. foxes/models/point_models/tke2ti.py +1 -1
  99. foxes/models/point_models/wake_deltas.py +1 -1
  100. foxes/models/rotor_models/centre.py +4 -0
  101. foxes/models/rotor_models/grid.py +24 -25
  102. foxes/models/rotor_models/levels.py +4 -5
  103. foxes/models/turbine_models/calculator.py +4 -6
  104. foxes/models/turbine_models/kTI_model.py +22 -6
  105. foxes/models/turbine_models/lookup_table.py +30 -4
  106. foxes/models/turbine_models/rotor_centre_calc.py +4 -3
  107. foxes/models/turbine_models/set_farm_vars.py +103 -34
  108. foxes/models/turbine_types/PCt_file.py +27 -3
  109. foxes/models/turbine_types/PCt_from_two.py +27 -3
  110. foxes/models/turbine_types/TBL_file.py +80 -0
  111. foxes/models/turbine_types/__init__.py +2 -0
  112. foxes/models/turbine_types/lookup.py +316 -0
  113. foxes/models/turbine_types/null_type.py +51 -1
  114. foxes/models/turbine_types/wsrho2PCt_from_two.py +29 -5
  115. foxes/models/turbine_types/wsti2PCt_from_two.py +31 -7
  116. foxes/models/vertical_profiles/__init__.py +1 -1
  117. foxes/models/vertical_profiles/data_profile.py +1 -1
  118. foxes/models/wake_frames/__init__.py +1 -0
  119. foxes/models/wake_frames/dynamic_wakes.py +424 -0
  120. foxes/models/wake_frames/farm_order.py +25 -5
  121. foxes/models/wake_frames/rotor_wd.py +6 -4
  122. foxes/models/wake_frames/seq_dynamic_wakes.py +61 -74
  123. foxes/models/wake_frames/streamlines.py +21 -22
  124. foxes/models/wake_frames/timelines.py +330 -129
  125. foxes/models/wake_frames/yawed_wakes.py +7 -4
  126. foxes/models/wake_models/dist_sliced.py +2 -4
  127. foxes/models/wake_models/induction/rankine_half_body.py +5 -5
  128. foxes/models/wake_models/induction/rathmann.py +78 -24
  129. foxes/models/wake_models/induction/self_similar.py +78 -28
  130. foxes/models/wake_models/induction/vortex_sheet.py +86 -48
  131. foxes/models/wake_models/ti/crespo_hernandez.py +6 -4
  132. foxes/models/wake_models/ti/iec_ti.py +40 -21
  133. foxes/models/wake_models/top_hat.py +1 -1
  134. foxes/models/wake_models/wind/bastankhah14.py +8 -6
  135. foxes/models/wake_models/wind/bastankhah16.py +17 -16
  136. foxes/models/wake_models/wind/jensen.py +4 -3
  137. foxes/models/wake_models/wind/turbopark.py +16 -13
  138. foxes/models/wake_superpositions/ti_linear.py +1 -1
  139. foxes/models/wake_superpositions/ti_max.py +1 -1
  140. foxes/models/wake_superpositions/ti_pow.py +1 -1
  141. foxes/models/wake_superpositions/ti_quadratic.py +1 -1
  142. foxes/models/wake_superpositions/ws_linear.py +8 -7
  143. foxes/models/wake_superpositions/ws_max.py +8 -7
  144. foxes/models/wake_superpositions/ws_pow.py +8 -7
  145. foxes/models/wake_superpositions/ws_product.py +5 -5
  146. foxes/models/wake_superpositions/ws_quadratic.py +8 -7
  147. foxes/output/__init__.py +4 -1
  148. foxes/output/farm_layout.py +16 -12
  149. foxes/output/farm_results_eval.py +1 -1
  150. foxes/output/flow_plots_2d/__init__.py +0 -1
  151. foxes/output/flow_plots_2d/flow_plots.py +70 -30
  152. foxes/output/grids.py +92 -22
  153. foxes/output/results_writer.py +2 -2
  154. foxes/output/rose_plot.py +3 -3
  155. foxes/output/seq_plugins/__init__.py +2 -0
  156. foxes/output/{flow_plots_2d → seq_plugins}/seq_flow_ani_plugin.py +64 -22
  157. foxes/output/seq_plugins/seq_wake_debug_plugin.py +145 -0
  158. foxes/output/slice_data.py +131 -111
  159. foxes/output/state_turbine_map.py +19 -14
  160. foxes/output/state_turbine_table.py +19 -19
  161. foxes/utils/__init__.py +1 -1
  162. foxes/utils/abl/neutral.py +2 -2
  163. foxes/utils/abl/stable.py +2 -2
  164. foxes/utils/abl/unstable.py +2 -2
  165. foxes/utils/data_book.py +1 -1
  166. foxes/utils/dev_utils.py +42 -0
  167. foxes/utils/dict.py +24 -1
  168. foxes/utils/exec_python.py +1 -1
  169. foxes/utils/factory.py +176 -53
  170. foxes/utils/geom2d/circle.py +1 -1
  171. foxes/utils/geom2d/polygon.py +1 -1
  172. foxes/utils/geopandas_utils.py +2 -2
  173. foxes/utils/load.py +2 -2
  174. foxes/utils/pandas_helpers.py +3 -2
  175. foxes/utils/wind_dir.py +0 -2
  176. foxes/utils/xarray_utils.py +24 -14
  177. foxes/variables.py +39 -2
  178. {foxes-0.8.2.dist-info → foxes-1.1.0.2.dist-info}/METADATA +75 -33
  179. foxes-1.1.0.2.dist-info/RECORD +309 -0
  180. {foxes-0.8.2.dist-info → foxes-1.1.0.2.dist-info}/WHEEL +1 -1
  181. foxes-1.1.0.2.dist-info/top_level.txt +4 -0
  182. tests/0_consistency/iterative/test_iterative.py +92 -0
  183. tests/0_consistency/partial_wakes/test_partial_wakes.py +90 -0
  184. tests/1_verification/flappy_0_6/PCt_files/flappy/run.py +85 -0
  185. tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +103 -0
  186. tests/1_verification/flappy_0_6/abl_states/flappy/run.py +85 -0
  187. tests/1_verification/flappy_0_6/abl_states/test_abl_states.py +87 -0
  188. tests/1_verification/flappy_0_6/partial_top_hat/flappy/run.py +82 -0
  189. tests/1_verification/flappy_0_6/partial_top_hat/test_partial_top_hat.py +82 -0
  190. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/flappy/run.py +92 -0
  191. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +93 -0
  192. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/flappy/run.py +92 -0
  193. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +96 -0
  194. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/flappy/run.py +94 -0
  195. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +122 -0
  196. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/flappy/run.py +94 -0
  197. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +122 -0
  198. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/flappy/run.py +92 -0
  199. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +93 -0
  200. tests/1_verification/flappy_0_6_2/grid_rotors/flappy/run.py +85 -0
  201. tests/1_verification/flappy_0_6_2/grid_rotors/test_grid_rotors.py +130 -0
  202. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/flappy/run.py +96 -0
  203. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +116 -0
  204. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/flappy/run.py +93 -0
  205. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +99 -0
  206. tests/3_examples/test_examples.py +34 -0
  207. foxes/VERSION +0 -1
  208. foxes/output/flow_plots_2d.py +0 -0
  209. foxes/utils/geopandas_helpers.py +0 -294
  210. foxes/utils/runners/__init__.py +0 -1
  211. foxes/utils/runners/runners.py +0 -280
  212. foxes-0.8.2.dist-info/RECORD +0 -247
  213. foxes-0.8.2.dist-info/top_level.txt +0 -1
  214. foxes-0.8.2.dist-info/zip-safe +0 -1
  215. {foxes-0.8.2.dist-info → foxes-1.1.0.2.dist-info}/LICENSE +0 -0
foxes/core/model.py CHANGED
@@ -1,10 +1,8 @@
1
1
  import numpy as np
2
2
  from abc import ABC
3
3
  from itertools import count
4
- from copy import deepcopy
5
4
 
6
5
  import foxes.constants as FC
7
- from .data import Data
8
6
 
9
7
 
10
8
  class Model(ABC):
@@ -35,8 +33,8 @@ class Model(ABC):
35
33
  if self._id > 0:
36
34
  self.name += f"_instance{self._id}"
37
35
 
38
- self._store = {}
39
36
  self.__initialized = False
37
+ self.__running = False
40
38
 
41
39
  def __repr__(self):
42
40
  return f"{type(self).__name__}()"
@@ -119,6 +117,10 @@ class Model(ABC):
119
117
  and `coords`, a dict with entries `dim_name_str -> dim_array`
120
118
 
121
119
  """
120
+ if self.initialized:
121
+ raise ValueError(
122
+ f"Model '{self.name}': Cannot call load_data after initialization"
123
+ )
122
124
  return {"coords": {}, "data_vars": {}}
123
125
 
124
126
  def initialize(self, algo, verbosity=0, force=False):
@@ -135,6 +137,8 @@ class Model(ABC):
135
137
  Overwrite existing data
136
138
 
137
139
  """
140
+ if self.running:
141
+ raise ValueError(f"Model '{self.name}': Cannot initialize while running")
138
142
  if not self.initialized:
139
143
  pr = False
140
144
  for m in self.sub_models():
@@ -152,6 +156,103 @@ class Model(ABC):
152
156
 
153
157
  self.__initialized = True
154
158
 
159
+ @property
160
+ def running(self):
161
+ """
162
+ Flag for currently running models
163
+
164
+ Returns
165
+ -------
166
+ flag: bool
167
+ True if currently running
168
+
169
+ """
170
+ return self.__running
171
+
172
+ def set_running(
173
+ self,
174
+ algo,
175
+ data_stash,
176
+ sel=None,
177
+ isel=None,
178
+ verbosity=0,
179
+ ):
180
+ """
181
+ Sets this model status to running, and moves
182
+ all large data to stash.
183
+
184
+ The stashed data will be returned by the
185
+ unset_running() function after running calculations.
186
+
187
+ Parameters
188
+ ----------
189
+ algo: foxes.core.Algorithm
190
+ The calculation algorithm
191
+ data_stash: dict
192
+ Large data stash, this function adds data here.
193
+ Key: model name. Value: dict, large model data
194
+ sel: dict, optional
195
+ The subset selection dictionary
196
+ isel: dict, optional
197
+ The index subset selection dictionary
198
+ verbosity: int
199
+ The verbosity level, 0 = silent
200
+
201
+ """
202
+ if self.running:
203
+ raise ValueError(
204
+ f"Model '{self.name}': Cannot call set_running while running"
205
+ )
206
+ for m in self.sub_models():
207
+ if not m.running:
208
+ m.set_running(algo, data_stash, sel, isel, verbosity=verbosity)
209
+
210
+ if verbosity > 0:
211
+ print(f"Model '{self.name}': running")
212
+ if self.name not in data_stash:
213
+ data_stash[self.name] = {}
214
+
215
+ self.__running = True
216
+
217
+ def unset_running(
218
+ self,
219
+ algo,
220
+ data_stash,
221
+ sel=None,
222
+ isel=None,
223
+ verbosity=0,
224
+ ):
225
+ """
226
+ Sets this model status to not running, recovering large data
227
+ from stash
228
+
229
+ Parameters
230
+ ----------
231
+ algo: foxes.core.Algorithm
232
+ The calculation algorithm
233
+ data_stash: dict
234
+ Large data stash, this function adds data here.
235
+ Key: model name. Value: dict, large model data
236
+ sel: dict, optional
237
+ The subset selection dictionary
238
+ isel: dict, optional
239
+ The index subset selection dictionary
240
+ verbosity: int
241
+ The verbosity level, 0 = silent
242
+
243
+ """
244
+ if not self.running:
245
+ raise ValueError(
246
+ f"Model '{self.name}': Cannot call unset_running when not running"
247
+ )
248
+ for m in self.sub_models():
249
+ if m.running:
250
+ m.unset_running(algo, data_stash, sel, isel, verbosity=verbosity)
251
+
252
+ if verbosity > 0:
253
+ print(f"Model '{self.name}': not running")
254
+ self.__running = False
255
+
155
256
  def finalize(self, algo, verbosity=0):
156
257
  """
157
258
  Finalizes the model.
@@ -164,6 +265,8 @@ class Model(ABC):
164
265
  The verbosity level, 0 = silent
165
266
 
166
267
  """
268
+ if self.running:
269
+ raise ValueError(f"Model '{self.name}': Cannot finalize while running")
167
270
  if self.initialized:
168
271
  pr = False
169
272
  for m in self.sub_models():
@@ -178,7 +281,6 @@ class Model(ABC):
178
281
  print(f"Finalizing model '{self.name}'")
179
282
  algo.del_model_data(self)
180
283
 
181
- self._store = {}
182
284
  self.__initialized = False
183
285
 
184
286
  def get_data(
@@ -194,6 +296,7 @@ class Model(ABC):
194
296
  accept_nan=True,
195
297
  algo=None,
196
298
  upcast=False,
299
+ selection=None,
197
300
  ):
198
301
  """
199
302
  Getter for a data entry in the model object
@@ -233,6 +336,9 @@ class Model(ABC):
233
336
  upcast: bool
234
337
  Flag for ensuring targets dimension,
235
338
  otherwise dimension 1 is entered
339
+ selection: numpy.ndarray, optional
340
+ Apply this selection to the result,
341
+ state-turbine, state-target, or state-target-tpoint
236
342
 
237
343
  """
238
344
 
@@ -241,7 +347,7 @@ class Model(ABC):
241
347
  for s in sources:
242
348
  try:
243
349
  if a == "states_i0":
244
- out = s.states_i0(counter=True, algo=algo)
350
+ out = s.states_i0(counter=True)
245
351
  if out is not None:
246
352
  return out
247
353
  else:
@@ -256,6 +362,10 @@ class Model(ABC):
256
362
 
257
363
  n_states = _geta("n_states")
258
364
  if target == FC.STATE_TURBINE:
365
+ if downwind_index is not None:
366
+ raise ValueError(
367
+ f"Target '{target}' is incompatible with downwind_index (here {downwind_index})"
368
+ )
259
369
  n_turbines = _geta("n_turbines")
260
370
  dims = (FC.STATE, FC.TURBINE)
261
371
  shp = (n_states, n_turbines)
@@ -273,42 +383,49 @@ class Model(ABC):
273
383
  f"Model '{self.name}': Wrong parameter 'target = {target}'. Choices: {FC.STATE_TURBINE}, {FC.STATE_TARGET}, {FC.STATE_TARGET_TPOINT}"
274
384
  )
275
385
 
386
+ def _match_shape(a):
387
+ out = np.asarray(a)
388
+ if len(out.shape) < len(shp):
389
+ for i, s in enumerate(shp):
390
+ if i >= len(out.shape):
391
+ out = out[..., None]
392
+ elif a.shape[i] not in (1, s):
393
+ raise ValueError(
394
+ f"Shape mismatch for '{variable}': Got {out.shape}, expecting {shp}"
395
+ )
396
+ elif len(out.shape) > len(shp):
397
+ raise ValueError(
398
+ f"Shape mismatch for '{variable}': Got {out.shape}, expecting {shp}"
399
+ )
400
+ return out
401
+
402
+ def _filter_dims(source):
403
+ a = source[variable]
404
+ a_dims = tuple(source.dims[variable])
405
+ if downwind_index is None or FC.TURBINE not in a_dims:
406
+ d = a_dims
407
+ else:
408
+ slc = tuple(
409
+ [downwind_index if dd == FC.TURBINE else np.s_[:] for dd in a_dims]
410
+ )
411
+ a = a[slc]
412
+ d = tuple([dd for dd in a_dims if dd != FC.TURBINE])
413
+ return a, d
414
+
276
415
  out = None
277
- out_dims = None
278
416
  for s in lookup:
279
417
  # lookup self:
280
418
  if s == "s" and hasattr(self, variable):
281
419
  a = getattr(self, variable)
282
420
  if a is not None:
283
- if not upcast:
284
- out = a
285
- out_dims = None
286
- elif target == FC.STATE_TURBINE:
287
- out = np.full((n_states, n_turbines), np.nan, dtype=FC.DTYPE)
288
- out[:] = a
289
- out_dims = (FC.STATE, FC.TURBINE)
290
- elif target == FC.STATE_TARGET:
291
- out = np.full((n_states, n_targets), np.nan, dtype=FC.DTYPE)
292
- out[:] = a
293
- out_dims = (FC.STATE, FC.TARGET)
294
- elif target == FC.STATE_TARGET_TPOINT:
295
- out = np.full(
296
- (n_states, n_targets, n_tpoints), np.nan, dtype=FC.DTYPE
297
- )
298
- out[:] = a
299
- out_dims = (FC.STATE, FC.TARGET, FC.TPOINT)
300
- else:
301
- raise NotImplementedError
421
+ out = _match_shape(a)
302
422
 
303
423
  # lookup mdata:
304
- elif (
305
- s == "m"
306
- and mdata is not None
307
- and variable in mdata
308
- and tuple(mdata.dims[variable]) == dims
309
- ):
310
- out = mdata[variable]
311
- out_dims = dims
424
+ elif s == "m" and mdata is not None and variable in mdata:
425
+ a, d = _filter_dims(mdata)
426
+ l = len(d)
427
+ if l <= len(dims) and d == dims[:l]:
428
+ out = _match_shape(mdata[variable])
312
429
 
313
430
  # lookup fdata:
314
431
  elif (
@@ -317,18 +434,22 @@ class Model(ABC):
317
434
  and variable in fdata
318
435
  and tuple(fdata.dims[variable]) == (FC.STATE, FC.TURBINE)
319
436
  ):
320
- out = fdata[variable]
321
- out_dims = (FC.STATE, FC.TURBINE)
437
+ if target == FC.STATE_TURBINE:
438
+ out = fdata[variable]
439
+ elif downwind_index is not None:
440
+ out = _match_shape(fdata[variable][:, downwind_index])
322
441
 
323
442
  # lookup pdata:
324
443
  elif (
325
444
  s == "t"
445
+ and target != FC.STATE_TURBINE
326
446
  and tdata is not None
327
447
  and variable in tdata
328
- and tuple(tdata.dims[variable]) == (FC.STATE, FC.TARGET, FC.TPOINT)
329
448
  ):
330
- out = tdata[variable]
331
- out_dims = (FC.STATE, FC.TARGET, FC.TPOINT)
449
+ a, d = _filter_dims(tdata)
450
+ l = len(d)
451
+ if l <= len(dims) and d == dims[:l]:
452
+ out = _match_shape(tdata[variable])
332
453
 
333
454
  # lookup wake modelling data:
334
455
  elif (
@@ -340,78 +461,27 @@ class Model(ABC):
340
461
  and downwind_index is not None
341
462
  and algo is not None
342
463
  ):
343
- out, out_dims = algo.wake_frame.get_wake_modelling_data(
344
- algo,
345
- variable,
346
- downwind_index,
347
- fdata,
348
- tdata=tdata,
349
- target=target,
350
- upcast=upcast,
464
+ out = _match_shape(
465
+ algo.wake_frame.get_wake_modelling_data(
466
+ algo,
467
+ variable,
468
+ downwind_index,
469
+ fdata,
470
+ tdata=tdata,
471
+ target=target,
472
+ )
351
473
  )
352
474
 
353
475
  if out is not None:
354
476
  break
355
477
 
356
- # cast dimensions:
357
- if out_dims != dims:
358
- if out_dims is None:
359
- if upcast:
360
- out0 = out
361
- out = np.zeros(shp, dtype=FC.DTYPE)
362
- out[:] = out0
363
- out_dims = dims
364
- del out0
365
- else:
366
- out_dims = tuple([1 for _ in dims])
367
-
368
- elif out_dims == (FC.STATE, FC.TURBINE):
369
- if downwind_index is None:
370
- raise KeyError(
371
- f"Require downwind_index for target {target} and out dims {out_dims}"
372
- )
373
- out0 = out[:, downwind_index, None]
374
- if len(dims) == 3:
375
- out0 = out0[:, :, None]
376
- if upcast:
377
- out = np.zeros(shp, dtype=FC.DTYPE)
378
- out[:] = out0
379
- out_dims = dims
380
- else:
381
- out = out0
382
- out_dims = (FC.STATE, 1) if len(dims) == 2 else (FC.STATE, 1, 1)
383
- del out0
384
-
385
- elif out_dims == (FC.STATE, 1):
386
- out0 = out
387
- if len(dims) == 3:
388
- out0 = out0[:, :, None]
389
- out_dims = (FC.STATE, 1, 1)
390
- if upcast:
391
- out = np.zeros(shp, dtype=FC.DTYPE)
392
- out[:] = out0
393
- out_dims = dims
394
- else:
395
- out = out0
396
- del out0
397
-
398
- elif out_dims == (FC.STATE, 1, 1):
399
- out0 = out
400
- if len(dims) == 2:
401
- out0 = out0[:, :, 0]
402
- out_dims = (FC.STATE, 1)
403
- if upcast:
404
- out = np.zeros(shp, dtype=FC.DTYPE)
405
- out[:] = out0
406
- out_dims = dims
407
- else:
408
- out = out0
409
- del out0
410
-
411
- else:
412
- raise NotImplementedError(
413
- f"No casting implemented for target {target} and out dims {out_dims} fo upcast {upcast}"
478
+ # check for None:
479
+ if out is None:
480
+ if not accept_none:
481
+ raise ValueError(
482
+ f"Model '{self.name}': Variable '{variable}' is requested but not found."
414
483
  )
484
+ return out
415
485
 
416
486
  # data from other chunks, only with iterations:
417
487
  if (
@@ -421,10 +491,14 @@ class Model(ABC):
421
491
  and tdata is not None
422
492
  and FC.STATES_SEL in tdata
423
493
  ):
424
- if out_dims != dims:
425
- raise ValueError(
426
- f"Model '{self.name}': Iteration data found for variable '{variable}', but missing upcast: out_dims = {out_dims}, expecting {dims}"
427
- )
494
+ if out.shape != shp:
495
+ # upcast to dims:
496
+ tmp = np.zeros(shp, dtype=out.dtype)
497
+ tmp[:] = out
498
+ out = tmp
499
+ del tmp
500
+ else:
501
+ out = out.copy()
428
502
  if downwind_index is None:
429
503
  raise KeyError(
430
504
  f"Model '{self.name}': Require downwind_index for obtaining results from previous iteration"
@@ -438,7 +512,12 @@ class Model(ABC):
438
512
  f"Model '{self.name}': Iteration data found for variable '{variable}', requiring algo"
439
513
  )
440
514
 
441
- i0 = _geta("states_i0")
515
+ from foxes.algorithms.sequential import Sequential
516
+
517
+ if isinstance(algo, Sequential):
518
+ i0 = algo.states.counter
519
+ else:
520
+ i0 = _geta("states_i0")
442
521
  sts = tdata[FC.STATES_SEL]
443
522
  if target == FC.STATE_TARGET and tdata.n_tpoints != 1:
444
523
  # find the mean index and round it to nearest integer:
@@ -446,27 +525,31 @@ class Model(ABC):
446
525
  sts = (sts + 0.5).astype(FC.ITYPE)
447
526
  sel = sts < i0
448
527
  if np.any(sel):
449
- if not hasattr(algo, "prev_farm_results"):
528
+ if not hasattr(algo, "farm_results_downwind"):
450
529
  raise KeyError(
451
530
  f"Model '{self.name}': Iteration data found for variable '{variable}', requiring iterative algorithm"
452
531
  )
453
- prev_fres = getattr(algo, "prev_farm_results")
532
+ prev_fres = getattr(algo, "farm_results_downwind")
454
533
  if prev_fres is not None:
455
534
  prev_data = prev_fres[variable].to_numpy()[sts[sel], downwind_index]
456
535
  if target == FC.STATE_TARGET:
457
536
  out[sel[:, :, 0]] = prev_data
458
537
  else:
459
538
  out[sel] = prev_data
460
- del prev_fres, prev_data
461
-
462
- # check for None:
463
- if not accept_none and out is None:
464
- raise ValueError(
465
- f"Model '{self.name}': Variable '{variable}' is requested but not found."
466
- )
539
+ del prev_data
540
+ del prev_fres
541
+ if np.any(~sel):
542
+ sts = sts[~sel] - i0
543
+ sel_data = fdata[variable][sts, downwind_index]
544
+ if target == FC.STATE_TARGET:
545
+ out[~sel[:, :, 0]] = sel_data
546
+ else:
547
+ out[~sel] = sel_data
548
+ del sel_data
549
+ del sel, sts
467
550
 
468
551
  # check for nan:
469
- elif not accept_nan:
552
+ if not accept_nan:
470
553
  try:
471
554
  if np.all(np.isnan(np.atleast_1d(out))):
472
555
  raise ValueError(
@@ -475,68 +558,52 @@ class Model(ABC):
475
558
  except TypeError:
476
559
  pass
477
560
 
478
- return out
479
-
480
- def data_to_store(self, name, algo, data):
481
- """
482
- Adds data from mdata to the local store, intended
483
- for iterative runs.
484
-
485
- Parameters
486
- ----------
487
- name: str
488
- The data name
489
- algo: foxes.core.Algorithm
490
- The algorithm
491
- data: foxes.utils.Data
492
- The mdata, fdata or pdata object
493
-
494
- """
495
- i0 = data.states_i0(counter=True, algo=algo)
496
- if i0 not in self._store:
497
- self._store[i0] = Data(
498
- data={}, dims={}, loop_dims=data.loop_dims, name=f"{self.name}_{i0}"
499
- )
500
-
501
- self._store[i0][name] = deepcopy(data[name])
502
- self._store[i0].dims[name] = (
503
- deepcopy(data.dims[name]) if name in data.dims else None
504
- )
505
-
506
- def from_data_or_store(self, name, algo, data, ret_dims=False, safe=False):
507
- """
508
- Get data from mdata or local store
509
-
510
- Parameters
511
- ----------
512
- name: str
513
- The data name
514
- algo: foxes.core.Algorithm
515
- The algorithm
516
- data: foxes.utils.Data
517
- The mdata, fdata or pdata object
518
- ret_dims: bool
519
- Return dimensions
520
- safe: bool
521
- Return None instead of error if
522
- not found
523
-
524
- Returns
525
- -------
526
- data: numpy.ndarray
527
- The data
528
- dims: tuple of dims, optional
529
- The data dimensions
561
+ # apply selection:
562
+ if selection is not None:
563
+
564
+ def _upcast_sel(sel_shape):
565
+ chp = []
566
+ for i, s in enumerate(out.shape):
567
+ if i < len(sel_shape) and sel_shape[i] > 1:
568
+ if sel_shape[i] != shp[i]:
569
+ raise ValueError(
570
+ f"Incompatible selection shape {sel_shape} for output shape {shp[i]}"
571
+ )
572
+ chp.append(shp[i])
573
+ else:
574
+ chp.append(s)
575
+ chp = tuple(chp)
576
+ eshp = list(shp[len(sel_shape) :])
577
+ if chp != out.shape:
578
+ nout = np.zeros(chp, dtype=out.dtype)
579
+ nout[:] = out
580
+ return nout, eshp
581
+ return out, eshp
582
+
583
+ if isinstance(selection, np.ndarray) and selection.dtype == bool:
584
+ if len(selection.shape) > len(out.shape):
585
+ raise ValueError(
586
+ f"Expecting selection of shape {out.shape}, got {selection.shape}"
587
+ )
588
+ out, eshp = _upcast_sel(selection.shape)
589
+ elif isinstance(selection, (tuple, list)):
590
+ if len(selection) > len(out.shape):
591
+ raise ValueError(
592
+ f"Selection is tuple/list of length {len(selection)}, expecting <= {len(out.shape)} "
593
+ )
594
+ out, eshp = _upcast_sel(shp[: len(selection)])
595
+ else:
596
+ raise TypeError(
597
+ f"Expecting selection of type np.ndarray (bool), or tuple, or list. Got {type(selection).__name__}"
598
+ )
599
+ out = out[selection]
600
+ shp = tuple([len(out)] + list(eshp))
530
601
 
531
- """
532
- if name in data:
533
- return (data[name], data.dims[name]) if ret_dims else data[name]
602
+ # apply upcast:
603
+ if upcast and out.shape != shp:
604
+ tmp = np.zeros(shp, dtype=out.dtype)
605
+ tmp[:] = out
606
+ out = tmp
607
+ del tmp
534
608
 
535
- i0 = data.states_i0(counter=True, algo=algo)
536
- if not safe or (i0 in self._store and name in self._store[i0]):
537
- if ret_dims:
538
- return self._store[i0][name], self._store[i0].dims[name]
539
- else:
540
- return self._store[i0][name]
541
- else:
542
- return (None, None) if ret_dims else None
609
+ return out
@@ -121,7 +121,7 @@ class PartialWakesModel(Model):
121
121
  The target point data
122
122
  downwind_index: int
123
123
  The index of the wake causing turbine
124
- in the downwnd order
124
+ in the downwind order
125
125
  wake_deltas: dict
126
126
  The wake deltas. Key: variable name,
127
127
  value: numpy.ndarray with shape
@@ -27,6 +27,18 @@ class PointDataModel(DataCalcModel):
27
27
  """
28
28
  return []
29
29
 
30
+ def output_coords(self):
31
+ """
32
+ Gets the coordinates of all output arrays
33
+
34
+ Returns
35
+ -------
36
+ dims: tuple of str
37
+ The coordinates of all output arrays
38
+
39
+ """
40
+ return (FC.STATE, FC.TARGET, FC.TPOINT)
41
+
30
42
  def ensure_variables(self, algo, mdata, fdata, tdata):
31
43
  """
32
44
  Add variables to tdata, initialized with NaN
@@ -54,7 +66,7 @@ class PointDataModel(DataCalcModel):
54
66
 
55
67
  @abstractmethod
56
68
  def calculate(self, algo, mdata, fdata, tdata):
57
- """ "
69
+ """
58
70
  The main model calculation.
59
71
 
60
72
  This function is executed on a single chunk of data,
@@ -158,6 +170,9 @@ class PointDataModelList(PointDataModel):
158
170
  super().__init__()
159
171
  self.models = models
160
172
 
173
+ def __repr__(self):
174
+ return f"{type(self).__name__}({[m.name for m in self.models]})"
175
+
161
176
  def append(self, model):
162
177
  """
163
178
  Add a model to the list
@@ -203,7 +218,7 @@ class PointDataModelList(PointDataModel):
203
218
  return list(dict.fromkeys(ovars))
204
219
 
205
220
  def calculate(self, algo, mdata, fdata, tdata, parameters=None):
206
- """ "
221
+ """
207
222
  The main model calculation.
208
223
 
209
224
  This function is executed on a single chunk of data,