foxes 0.6.1__py3-none-any.whl → 0.7__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 (129) hide show
  1. foxes/VERSION +1 -1
  2. foxes/algorithms/downwind/downwind.py +131 -65
  3. foxes/algorithms/downwind/models/__init__.py +2 -1
  4. foxes/algorithms/downwind/models/farm_wakes_calc.py +87 -55
  5. foxes/algorithms/downwind/models/init_farm_data.py +134 -0
  6. foxes/algorithms/downwind/models/point_wakes_calc.py +54 -65
  7. foxes/algorithms/downwind/models/{calc_order.py → reorder_farm_output.py} +28 -26
  8. foxes/algorithms/iterative/iterative.py +100 -51
  9. foxes/algorithms/iterative/models/convergence.py +3 -3
  10. foxes/algorithms/iterative/models/farm_wakes_calc.py +55 -48
  11. foxes/algorithms/sequential/models/seq_state.py +7 -6
  12. foxes/algorithms/sequential/sequential.py +81 -44
  13. foxes/constants.py +33 -18
  14. foxes/core/__init__.py +2 -2
  15. foxes/core/algorithm.py +31 -12
  16. foxes/core/data.py +335 -41
  17. foxes/core/data_calc_model.py +27 -23
  18. foxes/core/farm_controller.py +27 -28
  19. foxes/core/farm_data_model.py +26 -4
  20. foxes/core/model.py +186 -129
  21. foxes/core/partial_wakes_model.py +84 -81
  22. foxes/core/point_data_model.py +51 -18
  23. foxes/core/rotor_model.py +59 -77
  24. foxes/core/states.py +6 -6
  25. foxes/core/turbine_model.py +4 -4
  26. foxes/core/turbine_type.py +24 -0
  27. foxes/core/vertical_profile.py +3 -3
  28. foxes/core/wake_frame.py +91 -50
  29. foxes/core/wake_model.py +74 -43
  30. foxes/core/wake_superposition.py +29 -26
  31. foxes/input/farm_layout/__init__.py +1 -0
  32. foxes/input/farm_layout/from_random.py +49 -0
  33. foxes/input/states/__init__.py +1 -1
  34. foxes/input/states/create/__init__.py +1 -0
  35. foxes/input/states/create/random_abl_states.py +6 -2
  36. foxes/input/states/create/random_timeseries.py +56 -0
  37. foxes/input/states/field_data_nc.py +12 -8
  38. foxes/input/states/multi_height.py +24 -14
  39. foxes/input/states/scan_ws.py +13 -17
  40. foxes/input/states/single.py +28 -20
  41. foxes/input/states/states_table.py +22 -18
  42. foxes/models/axial_induction_models/betz.py +1 -1
  43. foxes/models/farm_models/turbine2farm.py +2 -2
  44. foxes/models/model_book.py +40 -14
  45. foxes/models/partial_wakes/__init__.py +2 -2
  46. foxes/models/partial_wakes/axiwake.py +73 -200
  47. foxes/models/partial_wakes/centre.py +40 -0
  48. foxes/models/partial_wakes/grid.py +7 -63
  49. foxes/models/partial_wakes/rotor_points.py +53 -147
  50. foxes/models/partial_wakes/segregated.py +158 -0
  51. foxes/models/partial_wakes/top_hat.py +88 -196
  52. foxes/models/point_models/set_uniform_data.py +4 -4
  53. foxes/models/point_models/tke2ti.py +4 -4
  54. foxes/models/point_models/wake_deltas.py +4 -4
  55. foxes/models/rotor_models/centre.py +15 -19
  56. foxes/models/rotor_models/grid.py +2 -1
  57. foxes/models/rotor_models/levels.py +2 -1
  58. foxes/models/turbine_models/__init__.py +0 -1
  59. foxes/models/turbine_models/calculator.py +11 -7
  60. foxes/models/turbine_models/kTI_model.py +13 -11
  61. foxes/models/turbine_models/lookup_table.py +22 -9
  62. foxes/models/turbine_models/power_mask.py +81 -51
  63. foxes/models/turbine_models/rotor_centre_calc.py +17 -20
  64. foxes/models/turbine_models/sector_management.py +5 -6
  65. foxes/models/turbine_models/set_farm_vars.py +49 -20
  66. foxes/models/turbine_models/table_factors.py +5 -5
  67. foxes/models/turbine_models/thrust2ct.py +9 -5
  68. foxes/models/turbine_models/yaw2yawm.py +7 -13
  69. foxes/models/turbine_models/yawm2yaw.py +7 -11
  70. foxes/models/turbine_types/PCt_file.py +84 -3
  71. foxes/models/turbine_types/PCt_from_two.py +7 -3
  72. foxes/models/turbine_types/null_type.py +2 -2
  73. foxes/models/turbine_types/wsrho2PCt_from_two.py +2 -2
  74. foxes/models/turbine_types/wsti2PCt_from_two.py +6 -2
  75. foxes/models/wake_frames/farm_order.py +26 -22
  76. foxes/models/wake_frames/rotor_wd.py +32 -31
  77. foxes/models/wake_frames/seq_dynamic_wakes.py +112 -64
  78. foxes/models/wake_frames/streamlines.py +51 -47
  79. foxes/models/wake_frames/timelines.py +59 -47
  80. foxes/models/wake_frames/yawed_wakes.py +63 -40
  81. foxes/models/wake_models/axisymmetric.py +31 -35
  82. foxes/models/wake_models/dist_sliced.py +50 -56
  83. foxes/models/wake_models/gaussian.py +33 -35
  84. foxes/models/wake_models/induction/rankine_half_body.py +79 -87
  85. foxes/models/wake_models/induction/rathmann.py +56 -63
  86. foxes/models/wake_models/induction/self_similar.py +59 -62
  87. foxes/models/wake_models/ti/crespo_hernandez.py +83 -74
  88. foxes/models/wake_models/ti/iec_ti.py +65 -75
  89. foxes/models/wake_models/top_hat.py +60 -69
  90. foxes/models/wake_models/wake_mirror.py +49 -54
  91. foxes/models/wake_models/wind/bastankhah14.py +44 -66
  92. foxes/models/wake_models/wind/bastankhah16.py +84 -111
  93. foxes/models/wake_models/wind/jensen.py +67 -89
  94. foxes/models/wake_models/wind/turbopark.py +93 -133
  95. foxes/models/wake_superpositions/ti_linear.py +33 -27
  96. foxes/models/wake_superpositions/ti_max.py +33 -27
  97. foxes/models/wake_superpositions/ti_pow.py +35 -27
  98. foxes/models/wake_superpositions/ti_quadratic.py +33 -27
  99. foxes/models/wake_superpositions/ws_linear.py +39 -32
  100. foxes/models/wake_superpositions/ws_max.py +40 -33
  101. foxes/models/wake_superpositions/ws_pow.py +39 -32
  102. foxes/models/wake_superpositions/ws_product.py +35 -28
  103. foxes/models/wake_superpositions/ws_quadratic.py +39 -32
  104. foxes/opt/constraints/min_dist.py +1 -1
  105. foxes/opt/objectives/farm_vars.py +1 -1
  106. foxes/opt/problems/layout/farm_layout.py +38 -97
  107. foxes/output/__init__.py +1 -0
  108. foxes/output/farm_results_eval.py +1 -1
  109. foxes/output/flow_plots_2d/flow_plots.py +2 -0
  110. foxes/output/flow_plots_2d/get_fig.py +2 -0
  111. foxes/output/grids.py +1 -1
  112. foxes/output/rose_plot.py +3 -3
  113. foxes/output/rotor_point_plots.py +117 -0
  114. foxes/output/turbine_type_curves.py +2 -2
  115. foxes/utils/__init__.py +2 -1
  116. foxes/utils/load.py +29 -0
  117. foxes/utils/random_xy.py +56 -0
  118. foxes/utils/runners/runners.py +13 -1
  119. foxes/utils/windrose_plot.py +1 -1
  120. foxes/variables.py +10 -0
  121. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/METADATA +13 -7
  122. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/RECORD +126 -122
  123. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/WHEEL +1 -1
  124. foxes/models/partial_wakes/distsliced.py +0 -322
  125. foxes/models/partial_wakes/mapped.py +0 -252
  126. foxes/models/turbine_models/set_XYHD.py +0 -130
  127. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/LICENSE +0 -0
  128. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/top_level.txt +0 -0
  129. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/zip-safe +0 -0
foxes/core/data.py CHANGED
@@ -7,10 +7,8 @@ import foxes.constants as FC
7
7
 
8
8
  class Data(Dict):
9
9
  """
10
- Container for data and meta data.
11
-
12
- Used during the calculation of single chunks,
13
- usually for numpy data (not xarray data).
10
+ Container for numpy array data and
11
+ the associated meta data.
14
12
 
15
13
  Attributes
16
14
  ----------
@@ -53,9 +51,9 @@ class Data(Dict):
53
51
 
54
52
  self.sizes = {}
55
53
  for v, d in data.items():
56
- self.__run_entry_checks(v, d, dims[v])
54
+ self._run_entry_checks(v, d, dims[v])
57
55
 
58
- self.__auto_update()
56
+ self._auto_update()
59
57
 
60
58
  @property
61
59
  def n_states(self):
@@ -83,19 +81,6 @@ class Data(Dict):
83
81
  """
84
82
  return self.sizes[FC.TURBINE] if FC.TURBINE in self.sizes else None
85
83
 
86
- @property
87
- def n_points(self):
88
- """
89
- The number of points
90
-
91
- Returns
92
- -------
93
- int:
94
- The number of points
95
-
96
- """
97
- return self.sizes[FC.POINT] if FC.POINT in self.sizes else None
98
-
99
84
  def states_i0(self, counter=False, algo=None):
100
85
  """
101
86
  Get the state counter for first state in chunk
@@ -123,7 +108,8 @@ class Data(Dict):
123
108
  else:
124
109
  return self[FC.STATE][0]
125
110
 
126
- def __auto_update(self):
111
+ def _auto_update(self):
112
+ """Checks and operations after data changes"""
127
113
  data = self
128
114
  dims = self.dims
129
115
 
@@ -132,25 +118,19 @@ class Data(Dict):
132
118
  and FV.X in data
133
119
  and FV.Y in data
134
120
  and FV.H in data
135
- and dims[FV.X] == (FC.STATE, FC.TURBINE)
136
- and dims[FV.Y] == (FC.STATE, FC.TURBINE)
137
- and dims[FV.H] == (FC.STATE, FC.TURBINE)
121
+ and dims[FV.X] == dims[FV.Y]
122
+ and dims[FV.X] == dims[FV.H]
138
123
  ):
139
- self[FV.TXYH] = np.zeros(
140
- (self.n_states, self.n_turbines, 3), dtype=FC.DTYPE
141
- )
124
+ self[FV.TXYH] = np.stack([self[FV.X], self[FV.Y], self[FV.H]], axis=-1)
142
125
 
143
- self[FV.TXYH][:, :, 0] = self[FV.X]
144
- self[FV.TXYH][:, :, 1] = self[FV.Y]
145
- self[FV.TXYH][:, :, 2] = self[FV.H]
126
+ self[FV.X] = self[FV.TXYH][..., 0]
127
+ self[FV.Y] = self[FV.TXYH][..., 1]
128
+ self[FV.H] = self[FV.TXYH][..., 2]
146
129
 
147
- self[FV.X] = self[FV.TXYH][:, :, 0]
148
- self[FV.Y] = self[FV.TXYH][:, :, 1]
149
- self[FV.H] = self[FV.TXYH][:, :, 2]
130
+ self.dims[FV.TXYH] = tuple(list(dims[FV.X]) + [FC.XYH])
150
131
 
151
- self.dims[FV.TXYH] = (FC.STATE, FC.TURBINE, FC.XYH)
152
-
153
- def __run_entry_checks(self, name, data, dims):
132
+ def _run_entry_checks(self, name, data, dims):
133
+ """Run entry checks on new data"""
154
134
  # remove axes of size 1, added by dask for extra loop dimensions:
155
135
  if dims is not None:
156
136
  if len(dims) != len(data.shape):
@@ -182,8 +162,270 @@ class Data(Dict):
182
162
  """
183
163
  self[name] = data
184
164
  self.dims[name] = dims
185
- self.__run_entry_checks(name, data, dims)
186
- self.__auto_update()
165
+ self._run_entry_checks(name, data, dims)
166
+ self._auto_update()
167
+
168
+ def get_slice(self, s, dim_map={}, name=None, keep=True):
169
+ """
170
+ Get a slice of data.
171
+
172
+ Parameters
173
+ ----------
174
+ s: slice
175
+ The slice
176
+ dim_map: dict
177
+ Mapping from original to new dimensions.
178
+ If not found, same dimensions are assumed.
179
+ name: str, optional
180
+ The name of the data object
181
+ keep: bool
182
+ Keep non-matching fields as they are, else
183
+ throw them out
184
+
185
+ Returns
186
+ -------
187
+ data: Data
188
+ The new data object, containing slices
189
+
190
+ """
191
+ data = {}
192
+ dims = {}
193
+ for v in self.keys():
194
+ try:
195
+ d = self.dims[v]
196
+ data[v] = self[v][s]
197
+ dims[v] = dim_map.get(d, d)
198
+ except IndexError:
199
+ if keep:
200
+ data[v] = self[v]
201
+ dims[v] = self.dims[v]
202
+ if name is None:
203
+ name = self.name
204
+ return type(self)(data, dims, loop_dims=self.loop_dims, name=name)
205
+
206
+
207
+ class MData(Data):
208
+ """
209
+ Container for foxes model data.
210
+
211
+ :group: core
212
+
213
+ """
214
+
215
+ def __init__(self, *args, name="mdata", **kwargs):
216
+ """
217
+ Constructor
218
+
219
+ Parameters
220
+ ----------
221
+ args: tuple, optional
222
+ Arguments for the base class
223
+ name: str
224
+ The data name
225
+ kwargs: dict, optional
226
+ Arguments for the base class
227
+
228
+ """
229
+ super().__init__(*args, name=name, **kwargs)
230
+
231
+
232
+ class FData(Data):
233
+ """
234
+ Container for foxes farm data.
235
+
236
+ Each farm data entry has (n_states, n_turbines) shape,
237
+ except the dimensions.
238
+
239
+ :group: core
240
+
241
+ """
242
+
243
+ def __init__(self, *args, name="fdata", **kwargs):
244
+ """
245
+ Constructor
246
+
247
+ Parameters
248
+ ----------
249
+ args: tuple, optional
250
+ Arguments for the base class
251
+ name: str
252
+ The data name
253
+ kwargs: dict, optional
254
+ Arguments for the base class
255
+
256
+ """
257
+ super().__init__(*args, name=name, **kwargs)
258
+
259
+ def _run_entry_checks(self, name, data, dims):
260
+ """Run entry checks on new data"""
261
+ super()._run_entry_checks(name, data, dims)
262
+ data = self[name]
263
+ dims = self.dims[name]
264
+
265
+ if name not in self.sizes and name not in FC.TNAME:
266
+ dms = (FC.STATE, FC.TURBINE)
267
+ shp = (self.n_states, self.n_turbines)
268
+ if len(data.shape) < 2:
269
+ raise ValueError(
270
+ f"FData '{self.name}': Invalid shape for '{name}', expecting {shp}, got {data.shape}"
271
+ )
272
+ if len(dims) < 2 or dims[:2] != dms:
273
+ raise ValueError(
274
+ f"FData '{self.name}': Invalid dims for '{name}', expecting {dms}, got {dims}"
275
+ )
276
+
277
+ def _auto_update(self):
278
+ """Checks and operations after data changes"""
279
+ super()._auto_update()
280
+ if len(self):
281
+ for x in [FC.STATE, FC.TURBINE]:
282
+ if x not in self.sizes:
283
+ raise KeyError(
284
+ f"FData '{self.name}': Missing '{x}' in sizes, got {sorted(list(self.sizes.keys()))}"
285
+ )
286
+
287
+
288
+ class TData(Data):
289
+ """
290
+ Container for foxes target data.
291
+
292
+ Each target consists of a fixed number of
293
+ target points.
294
+
295
+ :group: core
296
+
297
+ """
298
+
299
+ def __init__(self, *args, name="tdata", **kwargs):
300
+ """
301
+ Constructor
302
+
303
+ Parameters
304
+ ----------
305
+ args: tuple, optional
306
+ Arguments for the base class
307
+ name: str
308
+ The data name
309
+ kwargs: dict, optional
310
+ Arguments for the base class
311
+
312
+ """
313
+ super().__init__(*args, name=name, **kwargs)
314
+
315
+ def _run_entry_checks(self, name, data, dims):
316
+ """Run entry checks on new data"""
317
+ super()._run_entry_checks(name, data, dims)
318
+ data = self[name]
319
+ dims = self.dims[name]
320
+
321
+ if name == FC.TARGETS:
322
+ dms = (FC.STATE, FC.TARGET, FC.TPOINT, FC.XYH)
323
+ shp = (self.n_states, self.n_targets, self.n_tpoints, 3)
324
+ if dims != dms:
325
+ raise ValueError(
326
+ f"TData '{self.name}': Invalid dims of {FC.TARGETS}, expecting {dms}, got {dims}"
327
+ )
328
+ if data.shape != shp:
329
+ raise ValueError(
330
+ f"TData '{self.name}': Invalid shape of {FC.TARGETS}, expecting {shp}, got {data.shape}"
331
+ )
332
+
333
+ elif name == FC.TWEIGHTS:
334
+ dms = (FC.TPOINT,)
335
+ shp = (self.n_tpoints,)
336
+ if dims != dms:
337
+ raise ValueError(
338
+ f"TData '{self.name}': Invalid dims of {FC.TWEIGHTS}, expecting {dms}, got {dims}"
339
+ )
340
+ if data.shape != shp:
341
+ raise ValueError(
342
+ f"TData '{self.name}': Invalid shape of {FC.TWEIGHTS}, expecting {shp}, got {data.shape}"
343
+ )
344
+
345
+ elif FC.TARGETS not in self:
346
+ raise KeyError(
347
+ f"TData '{self.name}': Missing '{FC.TARGETS}' before adding '{name}'"
348
+ )
349
+
350
+ elif FC.TWEIGHTS not in self:
351
+ raise KeyError(
352
+ f"TData '{self.name}': Missing '{FC.TWEIGHTS}' before adding '{name}'"
353
+ )
354
+
355
+ elif name not in self.sizes:
356
+ dms = (FC.STATE, FC.TARGET, FC.TPOINT)
357
+ shp = (self.n_states, self.n_targets, self.n_tpoints)
358
+ if len(data.shape) < 3:
359
+ raise ValueError(
360
+ f"TData '{self.name}': Invalid shape for '{name}', expecting {shp}, got {data.shape}"
361
+ )
362
+ if len(dims) < 3 or dims[:3] != dms:
363
+ raise ValueError(
364
+ f"TData '{self.name}': Invalid dims for '{name}', expecting {dms}, got {dims}"
365
+ )
366
+
367
+ def _auto_update(self):
368
+ """Checks and operations after data changes"""
369
+ super()._auto_update()
370
+ if len(self):
371
+ for x in [FC.TARGETS, FC.TWEIGHTS]:
372
+ if x not in self:
373
+ raise KeyError(
374
+ f"TData '{self.name}': Missing '{x}' in data, got {sorted(list(self.keys()))}"
375
+ )
376
+ if x not in self.dims:
377
+ raise KeyError(
378
+ f"TData '{self.name}': Missing '{x}' in dims, got {sorted(list(self.dims.keys()))}"
379
+ )
380
+ for x in [FC.STATE, FC.TARGET, FC.TPOINT]:
381
+ if x not in self.sizes:
382
+ raise KeyError(
383
+ f"TData '{self.name}': Missing '{x}' in sizes, got {sorted(list(self.sizes.keys()))}"
384
+ )
385
+
386
+ @property
387
+ def n_targets(self):
388
+ """
389
+ The number of targets
390
+
391
+ Returns
392
+ -------
393
+ int:
394
+ The number of targets
395
+
396
+ """
397
+ return self.sizes[FC.TARGET]
398
+
399
+ @property
400
+ def n_tpoints(self):
401
+ """
402
+ The number of points per target
403
+
404
+ Returns
405
+ -------
406
+ int:
407
+ The number of points per target
408
+
409
+ """
410
+ return self.sizes[FC.TPOINT]
411
+
412
+ def tpoint_mean(self, variable):
413
+ """
414
+ Take the mean over target points
415
+
416
+ Parameters
417
+ ----------
418
+ variable: str
419
+ The variable name
420
+
421
+ Returns
422
+ -------
423
+ data: numpy.ndarray
424
+ The reduced array, shape:
425
+ (n_states, n_targets, ...)
426
+
427
+ """
428
+ return np.einsum("stp...,p->st...", self[variable], self[FC.TWEIGHTS])
187
429
 
188
430
  @classmethod
189
431
  def from_points(
@@ -191,7 +433,8 @@ class Data(Dict):
191
433
  points,
192
434
  data={},
193
435
  dims={},
194
- name="pdata",
436
+ name="tdata",
437
+ **kwargs,
195
438
  ):
196
439
  """
197
440
  Create from points
@@ -207,6 +450,8 @@ class Data(Dict):
207
450
  of data keys
208
451
  name: str
209
452
  The data container name
453
+ kwargs: dict, optional
454
+ Additional parameters for the constructor
210
455
 
211
456
  Returns
212
457
  -------
@@ -218,6 +463,55 @@ class Data(Dict):
218
463
  raise ValueError(
219
464
  f"Expecting points shape (n_states, n_points, 3), got {points.shape}"
220
465
  )
221
- data[FC.POINTS] = points
222
- dims[FC.POINTS] = (FC.STATE, FC.POINT, FC.XYH)
223
- return Data(data, dims, [FC.STATE, FC.POINT], name)
466
+ data[FC.TARGETS] = points[:, :, None, :]
467
+ dims[FC.TARGETS] = (FC.STATE, FC.TARGET, FC.TPOINT, FC.XYH)
468
+ data[FC.TWEIGHTS] = np.array([1], dtype=FC.DTYPE)
469
+ dims[FC.TWEIGHTS] = (FC.TPOINT,)
470
+ return cls(data, dims, [FC.STATE, FC.TARGET], name=name, **kwargs)
471
+
472
+ @classmethod
473
+ def from_tpoints(
474
+ cls,
475
+ tpoints,
476
+ tweights,
477
+ data={},
478
+ dims={},
479
+ name="tdata",
480
+ **kwargs,
481
+ ):
482
+ """
483
+ Create from points at targets
484
+
485
+ Parameters
486
+ ----------
487
+ tpoints: np.ndarray
488
+ The points at targets, shape:
489
+ (n_states, n_targets, n_tpoints, 3)
490
+ tweights: np.ndarray, optional
491
+ The target point weights, shape:
492
+ (n_tpoints,)
493
+ data: dict
494
+ The initial data to be stored
495
+ dims: dict
496
+ The dimensions tuples, same or subset
497
+ of data keys
498
+ name: str
499
+ The data container name
500
+ kwargs: dict, optional
501
+ Additional parameters for the constructor
502
+
503
+ Returns
504
+ -------
505
+ pdata: Data
506
+ The data object
507
+
508
+ """
509
+ if len(tpoints.shape) != 4 or tpoints.shape[3] != 3:
510
+ raise ValueError(
511
+ f"Expecting tpoints shape (n_states, n_targets, n_tpoints, 3), got {tpoints.shape}"
512
+ )
513
+ data[FC.TARGETS] = tpoints
514
+ dims[FC.TARGETS] = (FC.STATE, FC.TARGET, FC.TPOINT, FC.XYH)
515
+ data[FC.TWEIGHTS] = tweights
516
+ dims[FC.TWEIGHTS] = (FC.TPOINT,)
517
+ return cls(data, dims, [FC.STATE], name=name, **kwargs)
@@ -5,7 +5,7 @@ from dask.distributed import progress
5
5
  from dask.diagnostics import ProgressBar
6
6
 
7
7
  from .model import Model
8
- from .data import Data
8
+ from .data import MData, FData, TData
9
9
  from foxes.utils.runners import DaskRunner
10
10
  import foxes.constants as FC
11
11
  import foxes.variables as FV
@@ -73,7 +73,6 @@ class DataCalcModel(Model):
73
73
  """
74
74
  Wrapper that mitigates between apply_ufunc and `calculate`.
75
75
  """
76
-
77
76
  n_prev = len(init_vars)
78
77
  if n_prev:
79
78
  prev = ldata[:n_prev]
@@ -81,13 +80,23 @@ class DataCalcModel(Model):
81
80
 
82
81
  # reconstruct original data:
83
82
  data = []
84
- for hvars in dvars:
83
+ for i, hvars in enumerate(dvars):
85
84
  v2l = {v: lvars.index(v) for v in hvars if v in lvars}
86
85
  v2e = {v: evars.index(v) for v in hvars if v in evars}
87
86
 
88
87
  hdata = {v: ldata[v2l[v]] if v in v2l else edata[v2e[v]] for v in hvars}
89
88
  hdims = {v: ldims[v2l[v]] if v in v2l else edims[v2e[v]] for v in hvars}
90
- data.append(Data(hdata, hdims, loop_dims))
89
+
90
+ if i == 0:
91
+ data.append(MData(data=hdata, dims=hdims, loop_dims=loop_dims))
92
+ elif i == 1:
93
+ data.append(FData(data=hdata, dims=hdims, loop_dims=loop_dims))
94
+ elif i == 2:
95
+ data.append(TData(data=hdata, dims=hdims, loop_dims=loop_dims))
96
+ else:
97
+ raise NotImplementedError(
98
+ f"Not more than 3 data sets implemented, found {len(dvars)}"
99
+ )
91
100
 
92
101
  del hdata, hdims, v2l, v2e
93
102
 
@@ -102,7 +111,7 @@ class DataCalcModel(Model):
102
111
  raise ValueError(f"Model '{self.name}': Failed to find loop dimension")
103
112
 
104
113
  # add zero output data arrays:
105
- odims = {v: out_dims for v in out_vars}
114
+ odims = {v: tuple(out_dims) for v in out_vars}
106
115
  odata = {
107
116
  v: (
108
117
  np.full(oshape, np.nan, dtype=FC.DTYPE)
@@ -112,35 +121,30 @@ class DataCalcModel(Model):
112
121
  for v in out_vars
113
122
  if v not in data[-1]
114
123
  }
115
- if (
116
- n_prev
117
- and FV.TXYH not in odata
118
- and FV.X in odata
119
- and FV.X in odata
120
- and FV.Y in odata
121
- and FV.H in odata
122
- ):
123
- txyh = np.zeros((data[0].n_states, data[0].n_turbines, 3), dtype=FC.DTYPE)
124
- txyh[..., 0] = odata[FV.X]
125
- txyh[..., 1] = odata[FV.Y]
126
- txyh[..., 2] = odata[FV.H]
127
- odata[FV.TXYH] = txyh
128
- odims[FV.TXYH] = (FC.STATE, FC.TURBINE, FC.XYH)
129
- del txyh
124
+
130
125
  if len(data) == 1:
131
- data.append(Data(odata, odims, loop_dims))
126
+ data.append(FData(odata, odims, loop_dims))
132
127
  else:
133
128
  odata.update(data[-1])
134
129
  odims.update(data[-1].dims)
135
- data[-1] = Data(odata, odims, loop_dims)
130
+ if len(data) == 2:
131
+ data[-1] = FData(odata, odims, loop_dims)
132
+ else:
133
+ data[-1] = TData(odata, odims, loop_dims)
136
134
  del odims, odata
137
135
 
138
- # link chunk state indices from mdata to fdata and pdata:
136
+ # link chunk state indices from mdata to fdata and tdata:
139
137
  if FC.STATE in data[0]:
140
138
  for d in data[1:]:
141
139
  d[FC.STATE] = data[0][FC.STATE]
142
140
 
141
+ # link weights from mdata to fdata:
142
+ if FV.WEIGHT in data[0]:
143
+ data[1][FV.WEIGHT] = data[0][FV.WEIGHT]
144
+ data[1].dims[FV.WEIGHT] = data[0].dims[FV.WEIGHT]
145
+
143
146
  # run model calculation:
147
+ self.ensure_variables(algo, *data)
144
148
  results = self.calculate(algo, *data, **calc_pars)
145
149
 
146
150
  # replace missing results by first input data with matching shape:
@@ -16,9 +16,6 @@ class FarmController(FarmDataModel):
16
16
  The turbine type of each turbine
17
17
  turbine_model_names: list of str
18
18
  Names of all turbine models found in the farm
19
- turbine_model_sels: numpy.ndarray of bool
20
- Selection flags for all turbine models,
21
- shape: (n_states, n_turbines, n_models)
22
19
  pre_rotor_models: foxes.core.FarmDataModelList
23
20
  The turbine models with pre-rotor flag
24
21
  post_rotor_models: foxes.core.FarmDataModelList
@@ -46,7 +43,6 @@ class FarmController(FarmDataModel):
46
43
 
47
44
  self.turbine_types = None
48
45
  self.turbine_model_names = None
49
- self.turbine_model_sels = None
50
46
  self.pre_rotor_models = None
51
47
  self.post_rotor_models = None
52
48
 
@@ -223,29 +219,32 @@ class FarmController(FarmDataModel):
223
219
  algo, pre_rotor=False, models=postr_models
224
220
  )
225
221
  tmsels = tmsels_pre + tmsels_post
222
+ self._tmall = [np.all(t) for t in tmsels]
226
223
  self.turbine_model_names = mnames_pre + mnames_post
227
224
  if len(self.turbine_model_names):
228
- self.turbine_model_sels = np.stack(tmsels, axis=2)
225
+ self._tmsels = np.stack(tmsels, axis=2)
229
226
  else:
230
227
  raise ValueError(f"Controller '{self.name}': No turbine model found.")
231
228
 
232
- def __get_pars(self, algo, models, ptype, mdata=None, st_sel=None, from_data=True):
229
+ def __get_pars(self, algo, models, ptype, mdata=None, downwind_index=None):
233
230
  """
234
231
  Private helper function for gathering model parameters.
235
232
  """
236
- if from_data:
237
- s = mdata[FC.TMODEL_SELS]
238
- else:
239
- s = self.turbine_model_sels
240
- if st_sel is not None:
241
- s = s & st_sel[:, :, None]
242
-
243
- pars = [
244
- {"st_sel": s[:, :, self.turbine_model_names.index(m.name)]} for m in models
245
- ]
246
- for mi, m in enumerate(models):
233
+ pars = []
234
+ for m in models:
235
+ mi = self.turbine_model_names.index(m.name)
236
+ if self._tmall[mi]:
237
+ s = np.s_[:, :] if downwind_index is None else np.s_[:, downwind_index]
238
+ else:
239
+ if downwind_index is None:
240
+ s = mdata[FC.TMODEL_SELS][:, :, mi]
241
+ else:
242
+ s = np.s_[
243
+ mdata[FC.TMODEL_SELS][:, downwind_index, mi], downwind_index
244
+ ]
245
+ pars.append({"st_sel": s})
247
246
  if m.name in self.pars:
248
- pars[mi].update(self.pars[m.name][ptype])
247
+ pars[-1].update(self.pars[m.name][ptype])
249
248
 
250
249
  return pars
251
250
 
@@ -288,11 +287,14 @@ class FarmController(FarmDataModel):
288
287
 
289
288
  """
290
289
  idata = super().load_data(algo, verbosity)
290
+
291
291
  idata["coords"][FC.TMODELS] = self.turbine_model_names
292
292
  idata["data_vars"][FC.TMODEL_SELS] = (
293
293
  (FC.STATE, FC.TURBINE, FC.TMODELS),
294
- self.turbine_model_sels,
294
+ self._tmsels,
295
295
  )
296
+ self._tmsels = None
297
+
296
298
  return idata
297
299
 
298
300
  def output_farm_vars(self, algo):
@@ -317,7 +319,7 @@ class FarmController(FarmDataModel):
317
319
  )
318
320
  )
319
321
 
320
- def calculate(self, algo, mdata, fdata, pre_rotor, st_sel=None):
322
+ def calculate(self, algo, mdata, fdata, pre_rotor, downwind_index=None):
321
323
  """ "
322
324
  The main model calculation.
323
325
 
@@ -328,16 +330,15 @@ class FarmController(FarmDataModel):
328
330
  ----------
329
331
  algo: foxes.core.Algorithm
330
332
  The calculation algorithm
331
- mdata: foxes.core.Data
333
+ mdata: foxes.core.MData
332
334
  The model data
333
- fdata: foxes.core.Data
335
+ fdata: foxes.core.FData
334
336
  The farm data
335
337
  pre_rotor: bool
336
338
  Flag for running pre-rotor or post-rotor
337
339
  models
338
- st_sel: numpy.ndarray of bool, optional
339
- Selection of states and turbines, shape:
340
- (n_states, n_turbines). None for all.
340
+ downwind_index: int, optional
341
+ The index in the downwind order
341
342
 
342
343
  Returns
343
344
  -------
@@ -347,9 +348,8 @@ class FarmController(FarmDataModel):
347
348
 
348
349
  """
349
350
  s = self.pre_rotor_models if pre_rotor else self.post_rotor_models
350
- pars = self.__get_pars(algo, s.models, "calc", mdata, st_sel, from_data=True)
351
+ pars = self.__get_pars(algo, s.models, "calc", mdata, downwind_index)
351
352
  res = s.calculate(algo, mdata, fdata, parameters=pars)
352
- self.turbine_model_sels = mdata[FC.TMODEL_SELS]
353
353
  return res
354
354
 
355
355
  def finalize(self, algo, verbosity=0):
@@ -366,4 +366,3 @@ class FarmController(FarmDataModel):
366
366
  """
367
367
  super().finalize(algo, verbosity)
368
368
  self.turbine_model_names = None
369
- self.turbine_model_sels = None