foxes 0.6.2__py3-none-any.whl → 0.7.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 (120) 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/from_random.py +10 -9
  32. foxes/input/states/create/random_timeseries.py +17 -19
  33. foxes/input/states/field_data_nc.py +12 -8
  34. foxes/input/states/multi_height.py +24 -14
  35. foxes/input/states/scan_ws.py +13 -17
  36. foxes/input/states/single.py +28 -20
  37. foxes/input/states/states_table.py +22 -18
  38. foxes/models/axial_induction_models/betz.py +1 -1
  39. foxes/models/farm_models/turbine2farm.py +2 -2
  40. foxes/models/model_book.py +39 -14
  41. foxes/models/partial_wakes/__init__.py +2 -3
  42. foxes/models/partial_wakes/axiwake.py +73 -200
  43. foxes/models/partial_wakes/centre.py +11 -79
  44. foxes/models/partial_wakes/grid.py +7 -63
  45. foxes/models/partial_wakes/rotor_points.py +53 -147
  46. foxes/models/partial_wakes/segregated.py +158 -0
  47. foxes/models/partial_wakes/top_hat.py +88 -196
  48. foxes/models/point_models/set_uniform_data.py +4 -4
  49. foxes/models/point_models/tke2ti.py +4 -4
  50. foxes/models/point_models/wake_deltas.py +4 -4
  51. foxes/models/rotor_models/centre.py +15 -19
  52. foxes/models/rotor_models/grid.py +2 -1
  53. foxes/models/rotor_models/levels.py +2 -1
  54. foxes/models/turbine_models/__init__.py +0 -1
  55. foxes/models/turbine_models/calculator.py +11 -7
  56. foxes/models/turbine_models/kTI_model.py +13 -11
  57. foxes/models/turbine_models/lookup_table.py +22 -9
  58. foxes/models/turbine_models/power_mask.py +81 -51
  59. foxes/models/turbine_models/rotor_centre_calc.py +17 -20
  60. foxes/models/turbine_models/sector_management.py +5 -6
  61. foxes/models/turbine_models/set_farm_vars.py +49 -20
  62. foxes/models/turbine_models/table_factors.py +5 -5
  63. foxes/models/turbine_models/thrust2ct.py +9 -5
  64. foxes/models/turbine_models/yaw2yawm.py +7 -13
  65. foxes/models/turbine_models/yawm2yaw.py +7 -11
  66. foxes/models/turbine_types/PCt_file.py +84 -3
  67. foxes/models/turbine_types/PCt_from_two.py +7 -3
  68. foxes/models/turbine_types/null_type.py +2 -2
  69. foxes/models/turbine_types/wsrho2PCt_from_two.py +2 -2
  70. foxes/models/turbine_types/wsti2PCt_from_two.py +6 -2
  71. foxes/models/wake_frames/farm_order.py +26 -22
  72. foxes/models/wake_frames/rotor_wd.py +32 -31
  73. foxes/models/wake_frames/seq_dynamic_wakes.py +112 -64
  74. foxes/models/wake_frames/streamlines.py +51 -47
  75. foxes/models/wake_frames/timelines.py +59 -47
  76. foxes/models/wake_frames/yawed_wakes.py +63 -40
  77. foxes/models/wake_models/axisymmetric.py +31 -35
  78. foxes/models/wake_models/dist_sliced.py +50 -56
  79. foxes/models/wake_models/gaussian.py +33 -35
  80. foxes/models/wake_models/induction/rankine_half_body.py +79 -87
  81. foxes/models/wake_models/induction/rathmann.py +56 -63
  82. foxes/models/wake_models/induction/self_similar.py +59 -62
  83. foxes/models/wake_models/ti/crespo_hernandez.py +83 -74
  84. foxes/models/wake_models/ti/iec_ti.py +65 -75
  85. foxes/models/wake_models/top_hat.py +60 -69
  86. foxes/models/wake_models/wake_mirror.py +49 -54
  87. foxes/models/wake_models/wind/bastankhah14.py +44 -66
  88. foxes/models/wake_models/wind/bastankhah16.py +84 -111
  89. foxes/models/wake_models/wind/jensen.py +67 -89
  90. foxes/models/wake_models/wind/turbopark.py +93 -133
  91. foxes/models/wake_superpositions/ti_linear.py +33 -27
  92. foxes/models/wake_superpositions/ti_max.py +33 -27
  93. foxes/models/wake_superpositions/ti_pow.py +35 -27
  94. foxes/models/wake_superpositions/ti_quadratic.py +33 -27
  95. foxes/models/wake_superpositions/ws_linear.py +39 -32
  96. foxes/models/wake_superpositions/ws_max.py +40 -33
  97. foxes/models/wake_superpositions/ws_pow.py +39 -32
  98. foxes/models/wake_superpositions/ws_product.py +35 -28
  99. foxes/models/wake_superpositions/ws_quadratic.py +39 -32
  100. foxes/opt/problems/layout/farm_layout.py +38 -97
  101. foxes/output/__init__.py +1 -0
  102. foxes/output/flow_plots_2d/flow_plots.py +2 -0
  103. foxes/output/rose_plot.py +3 -3
  104. foxes/output/rotor_point_plots.py +117 -0
  105. foxes/output/turbine_type_curves.py +2 -2
  106. foxes/utils/__init__.py +1 -1
  107. foxes/utils/load.py +29 -0
  108. foxes/utils/random_xy.py +11 -10
  109. foxes/utils/runners/runners.py +3 -4
  110. foxes/utils/windrose_plot.py +1 -1
  111. foxes/variables.py +10 -0
  112. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/METADATA +13 -7
  113. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/RECORD +117 -117
  114. foxes/models/partial_wakes/distsliced.py +0 -322
  115. foxes/models/partial_wakes/mapped.py +0 -252
  116. foxes/models/turbine_models/set_XYHD.py +0 -130
  117. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/LICENSE +0 -0
  118. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/WHEEL +0 -0
  119. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/top_level.txt +0 -0
  120. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/zip-safe +0 -0
@@ -3,7 +3,7 @@ from scipy.spatial.distance import cdist
3
3
 
4
4
  from foxes.core import WakeFrame
5
5
  from foxes.utils import wd2uv
6
- from foxes.core.data import Data
6
+ from foxes.core.data import TData
7
7
  import foxes.variables as FV
8
8
  import foxes.constants as FC
9
9
  from foxes.algorithms import Sequential
@@ -44,6 +44,9 @@ class SeqDynamicWakes(WakeFrame):
44
44
  self.cl_ipars = cl_ipars
45
45
  self.dt_min = dt_min
46
46
 
47
+ def __repr__(self):
48
+ return f"{type(self).__name__}(dt_min={self.dt_min})"
49
+
47
50
  def initialize(self, algo, verbosity=0):
48
51
  """
49
52
  Initializes the model.
@@ -98,9 +101,9 @@ class SeqDynamicWakes(WakeFrame):
98
101
  ----------
99
102
  algo: foxes.core.Algorithm
100
103
  The calculation algorithm
101
- mdata: foxes.core.Data
104
+ mdata: foxes.core.MData
102
105
  The model data
103
- fdata: foxes.core.Data
106
+ fdata: foxes.core.FData
104
107
  The farm data
105
108
 
106
109
  Returns
@@ -109,19 +112,18 @@ class SeqDynamicWakes(WakeFrame):
109
112
  The turbine order, shape: (n_states, n_turbines)
110
113
 
111
114
  """
112
-
113
115
  # prepare:
114
116
  n_states = fdata.n_states
115
117
  n_turbines = algo.n_turbines
116
- pdata = Data.from_points(points=fdata[FV.TXYH])
118
+ tdata = TData.from_points(points=fdata[FV.TXYH])
117
119
 
118
120
  # calculate streamline x coordinates for turbines rotor centre points:
119
121
  # n_states, n_turbines_source, n_turbines_target
120
122
  coosx = np.zeros((n_states, n_turbines, n_turbines), dtype=FC.DTYPE)
121
123
  for ti in range(n_turbines):
122
- coosx[:, ti, :] = self.get_wake_coos(
123
- algo, mdata, fdata, pdata, np.full(n_states, ti)
124
- )[..., 0]
124
+ coosx[:, ti, :] = self.get_wake_coos(algo, mdata, fdata, tdata, ti)[
125
+ :, :, 0, 0
126
+ ]
125
127
 
126
128
  # derive turbine order:
127
129
  # TODO: Remove loop over states
@@ -131,94 +133,112 @@ class SeqDynamicWakes(WakeFrame):
131
133
 
132
134
  return order
133
135
 
134
- def get_wake_coos(self, algo, mdata, fdata, pdata, states_source_turbine):
136
+ def get_wake_coos(
137
+ self,
138
+ algo,
139
+ mdata,
140
+ fdata,
141
+ tdata,
142
+ downwind_index,
143
+ ):
135
144
  """
136
- Calculate wake coordinates.
145
+ Calculate wake coordinates of rotor points.
137
146
 
138
147
  Parameters
139
148
  ----------
140
149
  algo: foxes.core.Algorithm
141
150
  The calculation algorithm
142
- mdata: foxes.core.Data
151
+ mdata: foxes.core.MData
143
152
  The model data
144
- fdata: foxes.core.Data
153
+ fdata: foxes.core.FData
145
154
  The farm data
146
- pdata: foxes.core.Data
147
- The evaluation point data
148
- states_source_turbine: numpy.ndarray
149
- For each state, one turbine index for the
150
- wake causing turbine. Shape: (n_states,)
155
+ tdata: foxes.core.TData
156
+ The target point data
157
+ downwind_index: int
158
+ The index of the wake causing turbine
159
+ in the downwnd order
151
160
 
152
161
  Returns
153
162
  -------
154
163
  wake_coos: numpy.ndarray
155
164
  The wake frame coordinates of the evaluation
156
- points, shape: (n_states, n_points, 3)
165
+ points, shape: (n_states, n_targets, n_tpoints, 3)
157
166
 
158
167
  """
159
-
160
168
  # prepare:
161
169
  n_states = 1
162
- n_points = pdata.n_points
163
- points = pdata[FC.POINTS]
164
- stsel = (np.arange(n_states), states_source_turbine)
165
- tindx = states_source_turbine[0]
170
+ n_targets = tdata.n_targets
171
+ n_tpoints = tdata.n_tpoints
172
+ n_points = n_targets * n_tpoints
173
+ points = tdata[FC.TARGETS].reshape(n_states, n_points, 3)
166
174
  counter = algo.states.counter
167
175
  N = counter + 1
168
176
 
169
177
  # new wake starts at turbine:
170
- self._traces_p[counter, tindx] = fdata[FV.TXYH][0, tindx]
171
- self._traces_l[counter, tindx] = 0
178
+ self._traces_p[counter, downwind_index] = fdata[FV.TXYH][0, downwind_index]
179
+ self._traces_l[counter, downwind_index] = 0
172
180
 
173
181
  # transport wakes that originate from previous time steps:
174
182
  if counter > 0:
175
- dxyz = self._traces_v[:counter, tindx] * self._dt[:counter, None]
176
- self._traces_p[:counter, tindx] += dxyz
177
- self._traces_l[:counter, tindx] += np.linalg.norm(dxyz, axis=-1)
183
+ dxyz = self._traces_v[:counter, downwind_index] * self._dt[:counter, None]
184
+ self._traces_p[:counter, downwind_index] += dxyz
185
+ self._traces_l[:counter, downwind_index] += np.linalg.norm(dxyz, axis=-1)
178
186
 
179
187
  # compute wind vectors at wake traces:
180
188
  # TODO: dz from U_z is missing here
181
189
  hpdata = {
182
- v: np.zeros((1, N), dtype=FC.DTYPE)
190
+ v: np.zeros((1, N, 1), dtype=FC.DTYPE)
183
191
  for v in algo.states.output_point_vars(algo)
184
192
  }
185
- hpdata[FC.POINTS] = self._traces_p[None, :N, tindx]
186
- hpdims = {FC.POINTS: (FC.STATE, FC.POINT, FC.XYH)}
187
- hpdims.update({v: (FC.STATE, FC.POINT) for v in hpdata.keys()})
188
- hpdata = Data(hpdata, hpdims, loop_dims=[FC.STATE, FC.POINT])
193
+ hpdims = {v: (FC.STATE, FC.TARGET, FC.TPOINT) for v in hpdata.keys()}
194
+ hpdata = TData.from_points(
195
+ points=self._traces_p[None, :N, downwind_index],
196
+ data=hpdata,
197
+ dims=hpdims,
198
+ )
189
199
  res = algo.states.calculate(algo, mdata, fdata, hpdata)
190
- self._traces_v[:N, tindx, :2] = wd2uv(res[FV.WD][0], res[FV.WS][0])
200
+ self._traces_v[:N, downwind_index, :2] = wd2uv(
201
+ res[FV.WD][0, :, 0], res[FV.WS][0, :, 0]
202
+ )
191
203
  del hpdata, hpdims, res
192
204
 
193
205
  # project:
194
- dists = cdist(points[0], self._traces_p[:N, tindx])
206
+ dists = cdist(points[0], self._traces_p[:N, downwind_index])
195
207
  tri = np.argmin(dists, axis=1)
196
208
  del dists
197
209
  wcoos = np.full((n_states, n_points, 3), 1e20, dtype=FC.DTYPE)
198
- wcoos[0, :, 2] = points[0, :, 2] - fdata[FV.TXYH][stsel][0, None, 2]
199
- delp = points[0, :, :2] - self._traces_p[tri, tindx, :2]
200
- nx = self._traces_v[tri, tindx, :2]
210
+ wcoos[0, :, 2] = points[0, :, 2] - fdata[FV.TXYH][:, downwind_index][0, None, 2]
211
+ delp = points[0, :, :2] - self._traces_p[tri, downwind_index, :2]
212
+ nx = self._traces_v[tri, downwind_index, :2]
201
213
  nx /= np.linalg.norm(nx, axis=1)[:, None]
202
214
  ny = np.concatenate([-nx[:, 1, None], nx[:, 0, None]], axis=1)
203
- wcoos[0, :, 0] = np.einsum("pd,pd->p", delp, nx) + self._traces_l[tri, tindx]
215
+ wcoos[0, :, 0] = (
216
+ np.einsum("pd,pd->p", delp, nx) + self._traces_l[tri, downwind_index]
217
+ )
204
218
  wcoos[0, :, 1] = np.einsum("pd,pd->p", delp, ny)
205
219
 
206
220
  # turbines that cause wake:
207
- pdata.add(FC.STATE_SOURCE_TURBINE, states_source_turbine, (FC.STATE,))
221
+ tdata[FC.STATE_SOURCE_ORDERI] = downwind_index
208
222
 
209
223
  # states that cause wake for each target point:
210
- pdata.add(FC.STATES_SEL, tri[None, :], (FC.STATE, FC.POINT))
224
+ tdata.add(
225
+ FC.STATES_SEL,
226
+ tri[None, :].reshape(n_states, n_targets, n_tpoints),
227
+ (FC.STATE, FC.TARGET, FC.TPOINT),
228
+ )
211
229
 
212
- return wcoos
230
+ return wcoos.reshape(n_states, n_targets, n_tpoints, 3)
213
231
 
214
232
  def get_wake_modelling_data(
215
233
  self,
216
234
  algo,
217
235
  variable,
218
- states_source_turbine,
236
+ downwind_index,
219
237
  fdata,
220
- pdata,
238
+ tdata,
239
+ target,
221
240
  states0=None,
241
+ upcast=False,
222
242
  ):
223
243
  """
224
244
  Return data that is required for computing the
@@ -230,35 +250,65 @@ class SeqDynamicWakes(WakeFrame):
230
250
  The algorithm, needed for data from previous iteration
231
251
  variable: str
232
252
  The variable, serves as data key
233
- states_source_turbine: numpy.ndarray
234
- For each state, one turbine index for the
235
- wake causing turbine. Shape: (n_states,)
236
- fdata: foxes.core.Data
253
+ downwind_index: int, optional
254
+ The index in the downwind order
255
+ fdata: foxes.core.FData
237
256
  The farm data
238
- pdata: foxes.core.Data
239
- The evaluation point data
257
+ tdata: foxes.core.TData
258
+ The target point data
259
+ target: str, optional
260
+ The dimensions identifier for the output,
261
+ FC.STATE_TARGET, FC.STATE_TARGET_TPOINT
240
262
  states0: numpy.ndarray, optional
241
263
  The states of wake creation
264
+ upcast: bool
265
+ Flag for ensuring targets dimension,
266
+ otherwise dimension 1 is entered
267
+
268
+ Returns
269
+ -------
270
+ data: numpy.ndarray
271
+ Data for wake modelling, shape:
272
+ (n_states, n_turbines) or (n_states, n_target)
273
+ dims: tuple
274
+ The data dimensions
242
275
 
243
276
  """
244
- if states0 is None:
277
+ if states0 is None and FC.STATE_SOURCE_ORDERI in tdata:
245
278
  # from previous iteration:
246
- if not np.all(states_source_turbine == pdata[FC.STATE_SOURCE_TURBINE]):
279
+ if downwind_index != tdata[FC.STATE_SOURCE_ORDERI]:
247
280
  raise ValueError(
248
- f"Model '{self.name}': Mismatch of 'states_source_turbine'. Expected {list(pdata[FC.STATE_SOURCE_TURBINE])}, got {list(states_source_turbine)}"
281
+ f"Model '{self.name}': Mismatch of '{FC.STATE_SOURCE_ORDERI}'. Expected {tdata[FC.STATE_SOURCE_ORDERI]}, got {downwind_index}"
249
282
  )
250
283
 
251
- s = pdata[FC.STATES_SEL]
252
- data = algo.farm_results[variable].to_numpy()
284
+ n_states = 1
285
+ n_targets = tdata.n_targets
286
+ n_tpoints = tdata.n_tpoints
287
+ n_points = n_targets * n_tpoints
253
288
 
254
- return data[s, states_source_turbine]
289
+ s = tdata[FC.STATES_SEL][0].reshape(n_points)
290
+ data = algo.farm_results[variable].to_numpy()
291
+ data = data[s, downwind_index].reshape(n_states, n_targets, n_tpoints)
292
+
293
+ if target == FC.STATE_TARGET:
294
+ if n_tpoints == 1:
295
+ data = data[:, :, 0]
296
+ else:
297
+ data = np.einsum("stp,p->st", data, tdata[FC.TWEIGHTS])
298
+ return data, (FC.STATE, FC.TARGET)
299
+ elif target == FC.STATE_TARGET_TPOINT:
300
+ return data, (FC.STATE, FC.TARGET, FC.TPOINT)
301
+ else:
302
+ raise ValueError(
303
+ f"Cannot handle target '{target}', choices are {FC.STATE_TARGET}, {FC.STATE_TARGET_TPOINT}"
304
+ )
255
305
 
256
306
  else:
257
307
  return super().get_wake_modelling_data(
258
- algo, variable, states_source_turbine, fdata, pdata, states0
308
+ algo, variable, downwind_index, fdata, tdata, target, states0, upcast
259
309
  )
260
310
 
261
- def get_centreline_points(self, algo, mdata, fdata, states_source_turbine, x):
311
+ def get_centreline_points(self, algo, mdata, fdata, downwind_index, x):
262
312
  """
263
313
  Gets the points along the centreline for given
264
314
  values of x.
@@ -267,13 +317,12 @@ class SeqDynamicWakes(WakeFrame):
267
317
  ----------
268
318
  algo: foxes.core.Algorithm
269
319
  The calculation algorithm
270
- mdata: foxes.core.Data
320
+ mdata: foxes.core.MData
271
321
  The model data
272
- fdata: foxes.core.Data
322
+ fdata: foxes.core.FData
273
323
  The farm data
274
- states_source_turbine: numpy.ndarray
275
- For each state, one turbine index for the
276
- wake causing turbine. Shape: (n_states,)
324
+ downwind_index: int
325
+ The index in the downwind order
277
326
  x: numpy.ndarray
278
327
  The wake frame x coordinates, shape: (n_states, n_points)
279
328
 
@@ -283,5 +332,4 @@ class SeqDynamicWakes(WakeFrame):
283
332
  The centreline points, shape: (n_states, n_points, 3)
284
333
 
285
334
  """
286
-
287
335
  raise NotImplementedError
@@ -3,7 +3,7 @@ from scipy.interpolate import interpn
3
3
 
4
4
  from foxes.core import WakeFrame
5
5
  from foxes.utils import wd2uv
6
- from foxes.core.data import Data
6
+ from foxes.core.data import TData
7
7
  import foxes.variables as FV
8
8
  import foxes.constants as FC
9
9
 
@@ -47,9 +47,11 @@ class Streamlines2D(WakeFrame):
47
47
  self.cl_ipars = cl_ipars
48
48
 
49
49
  self.DATA = self.var("DATA")
50
+ self.STEPS = self.var("STEPS")
51
+ self.SDAT = self.var("SDAT")
50
52
 
51
53
  def __repr__(self):
52
- return super().__repr__() + f"(step={self.step})"
54
+ return f"{type(self).__name__}(step={self.step})"
53
55
 
54
56
  def _calc_streamlines(self, algo, mdata, fdata):
55
57
  """
@@ -80,19 +82,17 @@ class Streamlines2D(WakeFrame):
80
82
 
81
83
  # calculate next tangential vector:
82
84
  svars = algo.states.output_point_vars(algo)
83
- pdata = {FC.POINTS: data[:, :, i, :3]}
84
- pdims = {FC.POINTS: (FC.STATE, FC.POINT, FC.XYH)}
85
- pdata.update(
86
- {
87
- v: np.full((n_states, n_turbines), np.nan, dtype=FC.DTYPE)
85
+ tdata = TData.from_points(
86
+ data[:, :, i, :3],
87
+ data={
88
+ v: np.full((n_states, n_turbines, 1), np.nan, dtype=FC.DTYPE)
88
89
  for v in svars
89
- }
90
+ },
91
+ dims={v: (FC.STATE, FC.TARGET, FC.TPOINT) for v in svars},
90
92
  )
91
- pdims.update({v: (FC.STATE, FC.POINT) for v in svars})
92
- pdata = Data(pdata, pdims, loop_dims=[FC.STATE, FC.POINT])
93
- data[:, :, i, 3] = algo.states.calculate(algo, mdata, fdata, pdata)[
93
+ data[:, :, i, 3] = algo.states.calculate(algo, mdata, fdata, tdata)[
94
94
  FV.WD
95
- ]
95
+ ][:, :, 0]
96
96
 
97
97
  sel = np.isnan(data[:, :, i, 3])
98
98
  if np.any(sel):
@@ -108,9 +108,9 @@ class Streamlines2D(WakeFrame):
108
108
  ----------
109
109
  algo: foxes.core.Algorithm
110
110
  The calculation algorithm
111
- mdata: foxes.core.Data
111
+ mdata: foxes.core.MData
112
112
  The model data
113
- fdata: foxes.core.Data
113
+ fdata: foxes.core.FData
114
114
  The farm data
115
115
 
116
116
  Returns
@@ -125,22 +125,23 @@ class Streamlines2D(WakeFrame):
125
125
  mdata[self.DATA][:, :, 0, :3] == fdata[FV.TXYH]
126
126
  ):
127
127
  mdata[self.DATA] = self._calc_streamlines(algo, mdata, fdata)
128
+ mdata.dims[self.DATA] = (FC.STATE, FC.TURBINE, self.STEPS, self.SDAT)
128
129
 
129
130
  return mdata[self.DATA]
130
131
 
131
- def _calc_coos(self, algo, mdata, fdata, points, states_source_turbine):
132
+ def _calc_coos(self, algo, mdata, fdata, targets, downwind_index):
132
133
  """
133
134
  Helper function, calculates streamline coordinates
134
135
  for given points and given turbine
135
136
  """
136
137
 
137
138
  # prepare:
138
- n_states = mdata.n_states
139
- n_points = points.shape[1]
140
- st_sel = (np.arange(n_states), states_source_turbine)
139
+ n_states, n_targets, n_tpoints = targets.shape[:3]
140
+ n_points = n_targets * n_tpoints
141
+ points = targets.reshape(n_states, n_points, 3)
141
142
 
142
143
  # find nearest streamline points:
143
- data = self.get_streamline_data(algo, mdata, fdata)[st_sel]
144
+ data = self.get_streamline_data(algo, mdata, fdata)[:, downwind_index]
144
145
  dists = np.linalg.norm(points[:, :, None, :2] - data[:, None, :, :2], axis=-1)
145
146
  selp = np.argmin(dists, axis=2)
146
147
  data = np.take_along_axis(data[:, None], selp[:, :, None, None], axis=2)[
@@ -158,7 +159,7 @@ class Streamlines2D(WakeFrame):
158
159
  coos[:, :, 1] = np.einsum("spd,spd->sp", delta, ny)
159
160
  coos[:, :, 2] = points[:, :, 2] - data[:, :, 2]
160
161
 
161
- return coos
162
+ return coos.reshape(n_states, n_targets, n_tpoints, 3)
162
163
 
163
164
  def calc_order(self, algo, mdata, fdata):
164
165
  """ "
@@ -171,9 +172,9 @@ class Streamlines2D(WakeFrame):
171
172
  ----------
172
173
  algo: foxes.core.Algorithm
173
174
  The calculation algorithm
174
- mdata: foxes.core.Data
175
+ mdata: foxes.core.MData
175
176
  The model data
176
- fdata: foxes.core.Data
177
+ fdata: foxes.core.FData
177
178
  The farm data
178
179
 
179
180
  Returns
@@ -185,15 +186,15 @@ class Streamlines2D(WakeFrame):
185
186
  # prepare:
186
187
  n_states = fdata.n_states
187
188
  n_turbines = algo.n_turbines
188
- pdata = Data.from_points(points=fdata[FV.TXYH])
189
+ tdata = TData.from_points(points=fdata[FV.TXYH])
189
190
 
190
191
  # calculate streamline x coordinates for turbines rotor centre points:
191
192
  # n_states, n_turbines_source, n_turbines_target
192
193
  coosx = np.zeros((n_states, n_turbines, n_turbines), dtype=FC.DTYPE)
193
194
  for ti in range(n_turbines):
194
- coosx[:, ti, :] = self.get_wake_coos(
195
- algo, mdata, fdata, pdata, np.full(n_states, ti)
196
- )[..., 0]
195
+ coosx[:, ti, :] = self.get_wake_coos(algo, mdata, fdata, tdata, ti)[
196
+ :, :, 0, 0
197
+ ]
197
198
 
198
199
  # derive turbine order:
199
200
  # TODO: Remove loop over states
@@ -203,36 +204,41 @@ class Streamlines2D(WakeFrame):
203
204
 
204
205
  return order
205
206
 
206
- def get_wake_coos(self, algo, mdata, fdata, pdata, states_source_turbine):
207
+ def get_wake_coos(
208
+ self,
209
+ algo,
210
+ mdata,
211
+ fdata,
212
+ tdata,
213
+ downwind_index,
214
+ ):
207
215
  """
208
- Calculate wake coordinates.
216
+ Calculate wake coordinates of rotor points.
209
217
 
210
218
  Parameters
211
219
  ----------
212
220
  algo: foxes.core.Algorithm
213
221
  The calculation algorithm
214
- mdata: foxes.core.Data
222
+ mdata: foxes.core.MData
215
223
  The model data
216
- fdata: foxes.core.Data
224
+ fdata: foxes.core.FData
217
225
  The farm data
218
- pdata: foxes.core.Data
219
- The evaluation point data
220
- states_source_turbine: numpy.ndarray
221
- For each state, one turbine index for the
222
- wake causing turbine. Shape: (n_states,)
226
+ tdata: foxes.core.TData
227
+ The target point data
228
+ downwind_index: int
229
+ The index of the wake causing turbine
230
+ in the downwnd order
223
231
 
224
232
  Returns
225
233
  -------
226
234
  wake_coos: numpy.ndarray
227
235
  The wake frame coordinates of the evaluation
228
- points, shape: (n_states, n_points, 3)
236
+ points, shape: (n_states, n_targets, n_tpoints, 3)
229
237
 
230
238
  """
231
- return self._calc_coos(
232
- algo, mdata, fdata, pdata[FC.POINTS], states_source_turbine
233
- )
239
+ return self._calc_coos(algo, mdata, fdata, tdata[FC.TARGETS], downwind_index)
234
240
 
235
- def get_centreline_points(self, algo, mdata, fdata, states_source_turbine, x):
241
+ def get_centreline_points(self, algo, mdata, fdata, downwind_index, x):
236
242
  """
237
243
  Gets the points along the centreline for given
238
244
  values of x.
@@ -241,13 +247,12 @@ class Streamlines2D(WakeFrame):
241
247
  ----------
242
248
  algo: foxes.core.Algorithm
243
249
  The calculation algorithm
244
- mdata: foxes.core.Data
250
+ mdata: foxes.core.MData
245
251
  The model data
246
- fdata: foxes.core.Data
252
+ fdata: foxes.core.FData
247
253
  The farm data
248
- states_source_turbine: numpy.ndarray
249
- For each state, one turbine index for the
250
- wake causing turbine. Shape: (n_states,)
254
+ downwind_index: int
255
+ The index in the downwind order
251
256
  x: numpy.ndarray
252
257
  The wake frame x coordinates, shape: (n_states, n_points)
253
258
 
@@ -263,8 +268,7 @@ class Streamlines2D(WakeFrame):
263
268
 
264
269
  # get streamline points:
265
270
  n_states, n_points = x.shape
266
- st_sel = (np.arange(n_states), states_source_turbine)
267
- data = self.get_streamline_data(algo, mdata, fdata)[st_sel]
271
+ data = self.get_streamline_data(algo, mdata, fdata)[:, downwind_index]
268
272
  spts = data[:, :, :3]
269
273
  n_spts = spts.shape[1]
270
274
  xs = self.step * np.arange(n_spts)