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
@@ -31,27 +31,35 @@ class Rathmann(TurbineInductionModel):
31
31
 
32
32
  """
33
33
 
34
- def __init__(self, pre_rotor_only=False, induction="Madsen"):
34
+ def __init__(
35
+ self,
36
+ superposition="ws_linear",
37
+ induction="Madsen",
38
+ pre_rotor_only=False,
39
+ ):
35
40
  """
36
41
  Constructor.
37
42
 
38
43
  Parameters
39
44
  ----------
40
- pre_rotor_only: bool
41
- Calculate only the pre-rotor region
45
+ superposition: str
46
+ The wind speed superposition
42
47
  induction: foxes.core.AxialInductionModel or str
43
48
  The induction model
49
+ pre_rotor_only: bool
50
+ Calculate only the pre-rotor region
44
51
 
45
52
  """
46
53
  super().__init__()
47
54
  self.induction = induction
48
55
  self.pre_rotor_only = pre_rotor_only
56
+ self._superp_name = superposition
49
57
 
50
58
  def __repr__(self):
51
59
  iname = (
52
60
  self.induction if isinstance(self.induction, str) else self.induction.name
53
61
  )
54
- return f"{type(self).__name__}(induction={iname})"
62
+ return f"{type(self).__name__}({self._superp_name}, induction={iname})"
55
63
 
56
64
  def sub_models(self):
57
65
  """
@@ -63,7 +71,7 @@ class Rathmann(TurbineInductionModel):
63
71
  All sub models
64
72
 
65
73
  """
66
- return [self.induction]
74
+ return [self._superp, self.induction]
67
75
 
68
76
  def initialize(self, algo, verbosity=0, force=False):
69
77
  """
@@ -79,6 +87,7 @@ class Rathmann(TurbineInductionModel):
79
87
  Overwrite existing data
80
88
 
81
89
  """
90
+ self._superp = algo.mbook.wake_superpositions[self._superp_name]
82
91
  if isinstance(self.induction, str):
83
92
  self.induction = algo.mbook.axial_induction[self.induction]
84
93
  super().initialize(algo, verbosity, force)
@@ -133,7 +142,7 @@ class Rathmann(TurbineInductionModel):
133
142
  The target point data
134
143
  downwind_index: int
135
144
  The index of the wake causing turbine
136
- in the downwnd order
145
+ in the downwind order
137
146
  wake_coos: numpy.ndarray
138
147
  The wake frame coordinates of the evaluation
139
148
  points, shape: (n_states, n_targets, n_tpoints, 3)
@@ -155,18 +164,6 @@ class Rathmann(TurbineInductionModel):
155
164
  downwind_index=downwind_index,
156
165
  )
157
166
 
158
- # get ws:
159
- ws = self.get_data(
160
- FV.REWS,
161
- FC.STATE_TARGET_TPOINT,
162
- lookup="w",
163
- algo=algo,
164
- fdata=fdata,
165
- tdata=tdata,
166
- upcast=True,
167
- downwind_index=downwind_index,
168
- )
169
-
170
167
  # get D
171
168
  R = 0.5 * self.get_data(
172
169
  FV.D,
@@ -199,21 +196,78 @@ class Rathmann(TurbineInductionModel):
199
196
  return sin_alpha * sin_beta * (1 + x_R**2)
200
197
 
201
198
  # ws delta in front of rotor
202
- sp_sel = (ct > 0) & (x_R <= 0)
199
+ sp_sel = (ct > 1e-8) & (x_R <= 0)
203
200
  if np.any(sp_sel):
204
201
  xr = x_R[sp_sel]
205
202
  a = self.induction.ct2a(ct[sp_sel])
206
- blockage = ws[sp_sel] * a * mu(xr) * G(xr, r_R[sp_sel]) # eqn 10
207
- wake_deltas[FV.WS][sp_sel] += -blockage
203
+ blockage = a * mu(xr) * G(xr, r_R[sp_sel]) # eqn 10
204
+
205
+ self._superp.add_wake(
206
+ algo,
207
+ mdata,
208
+ fdata,
209
+ tdata,
210
+ downwind_index,
211
+ sp_sel,
212
+ FV.WS,
213
+ wake_deltas[FV.WS],
214
+ -blockage,
215
+ )
208
216
 
209
217
  # ws delta behind rotor
210
218
  if not self.pre_rotor_only:
211
219
  # mirror -blockage in rotor plane
212
- sp_sel = (ct > 0) & (x_R > 0) & (r_R > 1)
220
+ sp_sel = (ct > 1e-8) & (x_R > 0) & (r_R > 1)
213
221
  if np.any(sp_sel):
214
222
  xr = x_R[sp_sel]
215
223
  a = self.induction.ct2a(ct[sp_sel])
216
- blockage = ws[sp_sel] * a * mu(-xr) * G(-xr, r_R[sp_sel]) # eqn 10
217
- wake_deltas[FV.WS][sp_sel] += blockage
224
+ blockage = a * mu(-xr) * G(-xr, r_R[sp_sel]) # eqn 10
225
+
226
+ self._superp.add_wake(
227
+ algo,
228
+ mdata,
229
+ fdata,
230
+ tdata,
231
+ downwind_index,
232
+ sp_sel,
233
+ FV.WS,
234
+ wake_deltas[FV.WS],
235
+ blockage,
236
+ )
218
237
 
219
238
  return wake_deltas
239
+
240
+ def finalize_wake_deltas(
241
+ self,
242
+ algo,
243
+ mdata,
244
+ fdata,
245
+ amb_results,
246
+ wake_deltas,
247
+ ):
248
+ """
249
+ Finalize the wake calculation.
250
+
251
+ Modifies wake_deltas on the fly.
252
+
253
+ Parameters
254
+ ----------
255
+ algo: foxes.core.Algorithm
256
+ The calculation algorithm
257
+ mdata: foxes.core.MData
258
+ The model data
259
+ fdata: foxes.core.FData
260
+ The farm data
261
+ amb_results: dict
262
+ The ambient results, key: variable name str,
263
+ values: numpy.ndarray with shape
264
+ (n_states, n_targets, n_tpoints)
265
+ wake_deltas: dict
266
+ The wake deltas object at the selected target
267
+ turbines. Key: variable str, value: numpy.ndarray
268
+ with shape (n_states, n_targets, n_tpoints)
269
+
270
+ """
271
+ wake_deltas[FV.WS] = self._superp.calc_final_wake_delta(
272
+ algo, mdata, fdata, FV.WS, amb_results[FV.WS], wake_deltas[FV.WS]
273
+ )
@@ -38,30 +38,39 @@ class SelfSimilar(TurbineInductionModel):
38
38
 
39
39
  """
40
40
 
41
- def __init__(self, pre_rotor_only=False, induction="Madsen", gamma=1.1):
41
+ def __init__(
42
+ self,
43
+ superposition="ws_linear",
44
+ induction="Madsen",
45
+ gamma=1.1,
46
+ pre_rotor_only=False,
47
+ ):
42
48
  """
43
49
  Constructor.
44
50
 
45
51
  Parameters
46
52
  ----------
47
- pre_rotor_only: bool
48
- Calculate only the pre-rotor region
53
+ superposition: str
54
+ The wind speed superposition.
49
55
  induction: foxes.core.AxialInductionModel or str
50
56
  The induction model
51
57
  gamma: float, default=1.1
52
58
  The parameter that multiplies Ct in the ct2a calculation
59
+ pre_rotor_only: bool
60
+ Calculate only the pre-rotor region
53
61
 
54
62
  """
55
63
  super().__init__()
56
64
  self.induction = induction
57
65
  self.pre_rotor_only = pre_rotor_only
58
66
  self.gamma = gamma
67
+ self._superp_name = superposition
59
68
 
60
69
  def __repr__(self):
61
70
  iname = (
62
71
  self.induction if isinstance(self.induction, str) else self.induction.name
63
72
  )
64
- return f"{type(self).__name__}(gamma={self.gamma}, induction={iname})"
73
+ return f"{type(self).__name__}({self._superp_name}, induction={iname}, gamma={self.gamma})"
65
74
 
66
75
  def sub_models(self):
67
76
  """
@@ -73,7 +82,7 @@ class SelfSimilar(TurbineInductionModel):
73
82
  All sub models
74
83
 
75
84
  """
76
- return [self.induction]
85
+ return [self._superp, self.induction]
77
86
 
78
87
  def initialize(self, algo, verbosity=0, force=False):
79
88
  """
@@ -89,6 +98,7 @@ class SelfSimilar(TurbineInductionModel):
89
98
  Overwrite existing data
90
99
 
91
100
  """
101
+ self._superp = algo.mbook.wake_superpositions[self._superp_name]
92
102
  if isinstance(self.induction, str):
93
103
  self.induction = algo.mbook.axial_induction[self.induction]
94
104
  super().initialize(algo, verbosity, force)
@@ -163,7 +173,7 @@ class SelfSimilar(TurbineInductionModel):
163
173
  The target point data
164
174
  downwind_index: int
165
175
  The index of the wake causing turbine
166
- in the downwnd order
176
+ in the downwind order
167
177
  wake_coos: numpy.ndarray
168
178
  The wake frame coordinates of the evaluation
169
179
  points, shape: (n_states, n_targets, n_tpoints, 3)
@@ -185,18 +195,6 @@ class SelfSimilar(TurbineInductionModel):
185
195
  downwind_index=downwind_index,
186
196
  )
187
197
 
188
- # get ws
189
- ws = self.get_data(
190
- FV.REWS,
191
- FC.STATE_TARGET_TPOINT,
192
- lookup="w",
193
- algo=algo,
194
- fdata=fdata,
195
- tdata=tdata,
196
- upcast=True,
197
- downwind_index=downwind_index,
198
- )
199
-
200
198
  # get R
201
199
  R = 0.5 * self.get_data(
202
200
  FV.D,
@@ -214,26 +212,78 @@ class SelfSimilar(TurbineInductionModel):
214
212
  r_R = np.linalg.norm(wake_coos[..., 1:3], axis=-1) / R
215
213
 
216
214
  # select values
217
- sp_sel = (ct > 0) & (x_R <= 0) # upstream
215
+ sp_sel = (ct > 1e-8) & (x_R <= 0) # upstream
218
216
  if np.any(sp_sel):
219
217
  # velocity eqn 10 from [1]
220
218
  xr = x_R[sp_sel]
221
- blockage = (
222
- ws[sp_sel] * self._a(ct[sp_sel], xr) * self._rad_fn(xr, r_R[sp_sel])
219
+ blockage = self._a(ct[sp_sel], xr) * self._rad_fn(xr, r_R[sp_sel])
220
+
221
+ self._superp.add_wake(
222
+ algo,
223
+ mdata,
224
+ fdata,
225
+ tdata,
226
+ downwind_index,
227
+ sp_sel,
228
+ FV.WS,
229
+ wake_deltas[FV.WS],
230
+ -blockage,
223
231
  )
224
- wake_deltas[FV.WS][sp_sel] -= blockage
225
232
 
226
233
  # set area behind to mirrored value EXCEPT for area behind turbine
227
234
  if not self.pre_rotor_only:
228
- sp_sel = (ct > 0) & (x_R > 0) & (r_R > 1)
235
+ sp_sel = (ct > 1e-8) & (x_R > 0) & (r_R > 1)
229
236
  if np.any(sp_sel):
230
237
  # velocity eqn 10 from [1]
231
238
  xr = x_R[sp_sel]
232
- blockage = (
233
- ws[sp_sel]
234
- * self._a(ct[sp_sel], -xr)
235
- * self._rad_fn(-xr, r_R[sp_sel])
239
+ blockage = self._a(ct[sp_sel], -xr) * self._rad_fn(-xr, r_R[sp_sel])
240
+
241
+ # wdelta[sp_sel] += blockage
242
+ self._superp.add_wake(
243
+ algo,
244
+ mdata,
245
+ fdata,
246
+ tdata,
247
+ downwind_index,
248
+ sp_sel,
249
+ FV.WS,
250
+ wake_deltas[FV.WS],
251
+ blockage,
236
252
  )
237
- wake_deltas[FV.WS][sp_sel] += blockage
238
253
 
239
254
  return wake_deltas
255
+
256
+ def finalize_wake_deltas(
257
+ self,
258
+ algo,
259
+ mdata,
260
+ fdata,
261
+ amb_results,
262
+ wake_deltas,
263
+ ):
264
+ """
265
+ Finalize the wake calculation.
266
+
267
+ Modifies wake_deltas on the fly.
268
+
269
+ Parameters
270
+ ----------
271
+ algo: foxes.core.Algorithm
272
+ The calculation algorithm
273
+ mdata: foxes.core.MData
274
+ The model data
275
+ fdata: foxes.core.FData
276
+ The farm data
277
+ amb_results: dict
278
+ The ambient results, key: variable name str,
279
+ values: numpy.ndarray with shape
280
+ (n_states, n_targets, n_tpoints)
281
+ wake_deltas: dict
282
+ The wake deltas object at the selected target
283
+ turbines. Key: variable str, value: numpy.ndarray
284
+ with shape (n_states, n_targets, n_tpoints)
285
+
286
+ """
287
+ wake_deltas[FV.WS] = self._superp.calc_final_wake_delta(
288
+ algo, mdata, fdata, FV.WS, amb_results[FV.WS], wake_deltas[FV.WS]
289
+ )
@@ -10,7 +10,10 @@ class VortexSheet(TurbineInductionModel):
10
10
  """
11
11
  The Vortex Sheet model implemented with a radial dependency
12
12
 
13
- Ref: Medici, D., et al. "The upstream flow of a wind turbine: blockage effect." Wind Energy 14.5 (2011): 691-697.
13
+ Notes
14
+ -----
15
+ Reference:
16
+ Medici, D., et al. "The upstream flow of a wind turbine: blockage effect." Wind Energy 14.5 (2011): 691-697.
14
17
  https://doi.org/10.1002/we.451
15
18
 
16
19
  Attributes
@@ -24,29 +27,35 @@ class VortexSheet(TurbineInductionModel):
24
27
 
25
28
  """
26
29
 
27
- def __init__(self, pre_rotor_only=False, induction="Madsen"):
30
+ def __init__(
31
+ self,
32
+ superposition="ws_linear",
33
+ induction="Madsen",
34
+ pre_rotor_only=False,
35
+ ):
28
36
  """
29
37
  Constructor.
30
38
 
31
39
  Parameters
32
40
  ----------
33
- pre_rotor_only: bool
34
- Calculate only the pre-rotor region
41
+ superposition: str
42
+ The wind speed superposition
35
43
  induction: foxes.core.AxialInductionModel or str
36
44
  The induction model
37
-
38
- :group: models.wake_models.induction
45
+ pre_rotor_only: bool
46
+ Calculate only the pre-rotor region
39
47
 
40
48
  """
41
49
  super().__init__()
42
50
  self.induction = induction
43
51
  self.pre_rotor_only = pre_rotor_only
52
+ self._superp_name = superposition
44
53
 
45
54
  def __repr__(self):
46
55
  iname = (
47
56
  self.induction if isinstance(self.induction, str) else self.induction.name
48
57
  )
49
- return f"{type(self).__name__}, induction={iname})"
58
+ return f"{type(self).__name__}({self._superp_name}, induction={iname})"
50
59
 
51
60
  def sub_models(self):
52
61
  """
@@ -58,7 +67,7 @@ class VortexSheet(TurbineInductionModel):
58
67
  All sub models
59
68
 
60
69
  """
61
- return [self.induction]
70
+ return [self._superp, self.induction]
62
71
 
63
72
  def initialize(self, algo, verbosity=0, force=False):
64
73
  """
@@ -74,6 +83,7 @@ class VortexSheet(TurbineInductionModel):
74
83
  Overwrite existing data
75
84
 
76
85
  """
86
+ self._superp = algo.mbook.wake_superpositions[self._superp_name]
77
87
  if isinstance(self.induction, str):
78
88
  self.induction = algo.mbook.axial_induction[self.induction]
79
89
  super().initialize(algo, verbosity, force)
@@ -129,7 +139,7 @@ class VortexSheet(TurbineInductionModel):
129
139
  The target point data
130
140
  downwind_index: int
131
141
  The index of the wake causing turbine
132
- in the downwnd order
142
+ in the downwind order
133
143
  wake_coos: numpy.ndarray
134
144
  The wake frame coordinates of the evaluation
135
145
  points, shape: (n_states, n_targets, n_tpoints, 3)
@@ -159,18 +169,6 @@ class VortexSheet(TurbineInductionModel):
159
169
  downwind_index=downwind_index,
160
170
  )
161
171
 
162
- # get ws
163
- ws = self.get_data(
164
- FV.REWS,
165
- FC.STATE_TARGET_TPOINT,
166
- lookup="w",
167
- algo=algo,
168
- fdata=fdata,
169
- tdata=tdata,
170
- upcast=True,
171
- downwind_index=downwind_index,
172
- )
173
-
174
172
  # get D
175
173
  D = self.get_data(
176
174
  FV.D,
@@ -183,45 +181,85 @@ class VortexSheet(TurbineInductionModel):
183
181
  downwind_index=downwind_index,
184
182
  )
185
183
 
186
- sp_sel = (ct > 0) & (x <= 0)
187
- ws_sel = ws[sp_sel]
184
+ sp_sel = (ct > 1e-8) & (x <= 0)
188
185
  ct_sel = ct[sp_sel]
189
186
  r_sph_sel = r_sph[sp_sel]
190
187
  D_sel = D[sp_sel]
191
188
 
192
189
  if np.any(sp_sel):
193
- blockage = (
194
- ws_sel
195
- * (
196
- 1
197
- - self.induction.ct2a(ct_sel)
198
- * ((1 + 2 * r_sph_sel / D_sel) * (1 + (2 * r_sph_sel / D_sel) ** 2))
199
- ** (-0.5)
200
- )
201
- ) - ws_sel
202
- wake_deltas[FV.WS][sp_sel] += blockage
190
+ blockage = self.induction.ct2a(ct_sel) * (
191
+ (1 + 2 * r_sph_sel / D_sel) * (1 + (2 * r_sph_sel / D_sel) ** 2)
192
+ ) ** (-0.5)
193
+
194
+ self._superp.add_wake(
195
+ algo,
196
+ mdata,
197
+ fdata,
198
+ tdata,
199
+ downwind_index,
200
+ sp_sel,
201
+ FV.WS,
202
+ wake_deltas[FV.WS],
203
+ -blockage,
204
+ )
203
205
 
204
206
  if not self.pre_rotor_only:
205
207
  sp_sel = (
206
- (ct > 0) & (x > 0) & (r > D / 2)
208
+ (ct > 1e-8) & (x > 0) & (r > D / 2)
207
209
  ) # mirror in rotor plane and inverse blockage, but not directly behind rotor
208
- ws_sel = ws[sp_sel]
209
210
  ct_sel = ct[sp_sel]
210
211
  r_sph_sel = r_sph[sp_sel]
211
212
  D_sel = D[sp_sel]
212
213
  if np.any(sp_sel):
213
- blockage = (
214
- ws_sel
215
- * (
216
- 1
217
- - self.induction.ct2a(ct_sel)
218
- * (
219
- (1 + 2 * r_sph_sel / D_sel)
220
- * (1 + (2 * r_sph_sel / D_sel) ** 2)
221
- )
222
- ** (-0.5)
223
- )
224
- ) - ws_sel
225
- wake_deltas[FV.WS][sp_sel] += -blockage
214
+ blockage = self.induction.ct2a(ct_sel) * (
215
+ (1 + 2 * r_sph_sel / D_sel) * (1 + (2 * r_sph_sel / D_sel) ** 2)
216
+ ) ** (-0.5)
217
+
218
+ self._superp.add_wake(
219
+ algo,
220
+ mdata,
221
+ fdata,
222
+ tdata,
223
+ downwind_index,
224
+ sp_sel,
225
+ FV.WS,
226
+ wake_deltas[FV.WS],
227
+ blockage,
228
+ )
226
229
 
227
230
  return wake_deltas
231
+
232
+ def finalize_wake_deltas(
233
+ self,
234
+ algo,
235
+ mdata,
236
+ fdata,
237
+ amb_results,
238
+ wake_deltas,
239
+ ):
240
+ """
241
+ Finalize the wake calculation.
242
+
243
+ Modifies wake_deltas on the fly.
244
+
245
+ Parameters
246
+ ----------
247
+ algo: foxes.core.Algorithm
248
+ The calculation algorithm
249
+ mdata: foxes.core.MData
250
+ The model data
251
+ fdata: foxes.core.FData
252
+ The farm data
253
+ amb_results: dict
254
+ The ambient results, key: variable name str,
255
+ values: numpy.ndarray with shape
256
+ (n_states, n_targets, n_tpoints)
257
+ wake_deltas: dict
258
+ The wake deltas object at the selected target
259
+ turbines. Key: variable str, value: numpy.ndarray
260
+ with shape (n_states, n_targets, n_tpoints)
261
+
262
+ """
263
+ wake_deltas[FV.WS] = self._superp.calc_final_wake_delta(
264
+ algo, mdata, fdata, FV.WS, amb_results[FV.WS], wake_deltas[FV.WS]
265
+ )
@@ -278,8 +278,9 @@ class CrespoHernandezTIWake(TopHatWakeModel):
278
278
  fdata=fdata,
279
279
  tdata=tdata,
280
280
  downwind_index=downwind_index,
281
- upcast=True,
282
- )[st_sel]
281
+ upcast=False,
282
+ selection=st_sel,
283
+ )
283
284
 
284
285
  # get TI:
285
286
  ti = self.get_data(
@@ -290,8 +291,9 @@ class CrespoHernandezTIWake(TopHatWakeModel):
290
291
  fdata=fdata,
291
292
  tdata=tdata,
292
293
  downwind_index=downwind_index,
293
- upcast=True,
294
- )[st_sel]
294
+ upcast=False,
295
+ selection=st_sel,
296
+ )
295
297
 
296
298
  # calculate induction factor:
297
299
  twoa = 2 * self.induction.ct2a(ct)
@@ -54,24 +54,27 @@ class IECTIWake(TopHatWakeModel):
54
54
 
55
55
  """
56
56
  super().__init__(superpositions={FV.TI: superposition}, induction=induction)
57
+ self.iec_type = iec_type
58
+ self.wake_k = None
57
59
 
58
- if opening_angle is not None:
60
+ if opening_angle is None:
61
+ self.wake_k = WakeK(**wake_k)
62
+ else:
59
63
  if "k" in wake_k or "ka" in wake_k or "kb" in wake_k:
60
64
  raise KeyError(
61
65
  f"Can handle 'opening_angle' or ('k', 'ka', 'kb') parameters, not both"
62
66
  )
63
- wake_k["k"] = float(np.tan(np.deg2rad(opening_angle / 2.0)))
64
-
65
- self.iec_type = iec_type
66
- self.wake_k = WakeK(**wake_k)
67
+ self._k = float(np.tan(np.deg2rad(opening_angle / 2.0)))
67
68
 
68
69
  def __repr__(self):
69
70
  iname = (
70
71
  self.induction if isinstance(self.induction, str) else self.induction.name
71
72
  )
72
73
  s = f"{type(self).__name__}"
73
- s += f"({self.superpositions[FV.TI]}, induction={iname}, "
74
- s += self.wake_k.repr() + ")"
74
+ s += f"({self.superpositions[FV.TI]}, induction={iname}"
75
+ if self.wake_k is not None:
76
+ s += ", " + self.wake_k.repr()
77
+ s += ")"
75
78
  return s
76
79
 
77
80
  def sub_models(self):
@@ -84,7 +87,7 @@ class IECTIWake(TopHatWakeModel):
84
87
  All sub models
85
88
 
86
89
  """
87
- return [self.wake_k]
90
+ return [self.wake_k] if self.wake_k is not None else []
88
91
 
89
92
  def new_wake_deltas(self, algo, mdata, fdata, tdata):
90
93
  """
@@ -147,15 +150,29 @@ class IECTIWake(TopHatWakeModel):
147
150
  The wake radii, shape: (n_states, n_targets)
148
151
 
149
152
  """
150
- k = self.wake_k(
151
- FC.STATE_TARGET,
152
- algo=algo,
153
- fdata=fdata,
154
- tdata=tdata,
155
- upcast=False,
156
- downwind_index=downwind_index,
157
- )
158
- return k * x
153
+ if self.wake_k is None:
154
+ return self._k * x
155
+ else:
156
+ D = self.get_data(
157
+ FV.D,
158
+ FC.STATE_TARGET,
159
+ lookup="w",
160
+ algo=algo,
161
+ fdata=fdata,
162
+ tdata=tdata,
163
+ downwind_index=downwind_index,
164
+ upcast=True,
165
+ )
166
+
167
+ k = self.wake_k(
168
+ FC.STATE_TARGET,
169
+ algo=algo,
170
+ fdata=fdata,
171
+ tdata=tdata,
172
+ upcast=False,
173
+ downwind_index=downwind_index,
174
+ )
175
+ return D / 2 + k * x
159
176
 
160
177
  def calc_centreline(
161
178
  self,
@@ -211,8 +228,9 @@ class IECTIWake(TopHatWakeModel):
211
228
  fdata=fdata,
212
229
  tdata=tdata,
213
230
  downwind_index=downwind_index,
214
- upcast=True,
215
- )[st_sel]
231
+ upcast=False,
232
+ selection=st_sel,
233
+ )
216
234
 
217
235
  # get ws:
218
236
  ws = self.get_data(
@@ -223,8 +241,9 @@ class IECTIWake(TopHatWakeModel):
223
241
  fdata=fdata,
224
242
  tdata=tdata,
225
243
  downwind_index=downwind_index,
226
- upcast=True,
227
- )[st_sel]
244
+ upcast=False,
245
+ selection=st_sel,
246
+ )
228
247
 
229
248
  # calculate wind deficit:
230
249
  if self.iec_type == "2005":
@@ -209,7 +209,7 @@ class TopHatWakeModel(AxisymmetricWakeModel):
209
209
  wake_r = self.calc_wake_radius(algo, mdata, fdata, tdata, downwind_index, x, ct)
210
210
 
211
211
  wdeltas = {}
212
- st_sel = (ct > 0) & (x > 0) & np.any(r < wake_r[:, :, None], axis=2)
212
+ st_sel = (x > 1e-8) & (ct > 1e-8) & np.any(r < wake_r[:, :, None], axis=2)
213
213
  if np.any(st_sel):
214
214
  x = x[st_sel]
215
215
  r = r[st_sel]