foxes 1.3__py3-none-any.whl → 1.4__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 (190) hide show
  1. docs/source/conf.py +3 -3
  2. examples/abl_states/run.py +2 -2
  3. examples/compare_rotors_pwakes/run.py +1 -1
  4. examples/compare_wakes/run.py +1 -2
  5. examples/dyn_wakes/run.py +29 -6
  6. examples/induction/run.py +3 -3
  7. examples/multi_height/run.py +1 -1
  8. examples/power_mask/run.py +2 -2
  9. examples/quickstart/run.py +0 -1
  10. examples/random_timeseries/run.py +3 -4
  11. examples/scan_row/run.py +3 -3
  12. examples/sequential/run.py +33 -10
  13. examples/single_state/run.py +3 -4
  14. examples/states_lookup_table/run.py +3 -3
  15. examples/streamline_wakes/run.py +27 -4
  16. examples/tab_file/run.py +3 -3
  17. examples/timelines/run.py +29 -5
  18. examples/timeseries/run.py +3 -3
  19. examples/timeseries_slurm/run.py +3 -3
  20. examples/wind_rose/run.py +3 -3
  21. examples/yawed_wake/run.py +16 -8
  22. foxes/__init__.py +21 -17
  23. foxes/algorithms/__init__.py +6 -6
  24. foxes/algorithms/downwind/__init__.py +2 -2
  25. foxes/algorithms/downwind/downwind.py +44 -12
  26. foxes/algorithms/downwind/models/__init__.py +6 -6
  27. foxes/algorithms/downwind/models/farm_wakes_calc.py +11 -9
  28. foxes/algorithms/downwind/models/init_farm_data.py +0 -1
  29. foxes/algorithms/downwind/models/point_wakes_calc.py +7 -13
  30. foxes/algorithms/downwind/models/set_amb_point_results.py +6 -6
  31. foxes/algorithms/iterative/__init__.py +7 -3
  32. foxes/algorithms/iterative/iterative.py +1 -2
  33. foxes/algorithms/iterative/models/__init__.py +7 -3
  34. foxes/algorithms/iterative/models/farm_wakes_calc.py +9 -5
  35. foxes/algorithms/sequential/__init__.py +3 -3
  36. foxes/algorithms/sequential/models/__init__.py +2 -2
  37. foxes/algorithms/sequential/sequential.py +3 -4
  38. foxes/config/__init__.py +5 -1
  39. foxes/constants.py +16 -0
  40. foxes/core/__init__.py +45 -22
  41. foxes/core/algorithm.py +0 -1
  42. foxes/core/data.py +19 -18
  43. foxes/core/engine.py +9 -13
  44. foxes/core/farm_controller.py +2 -2
  45. foxes/core/ground_model.py +4 -13
  46. foxes/core/model.py +5 -5
  47. foxes/core/partial_wakes_model.py +147 -10
  48. foxes/core/point_data_model.py +2 -3
  49. foxes/core/rotor_model.py +3 -3
  50. foxes/core/states.py +2 -3
  51. foxes/core/turbine.py +2 -1
  52. foxes/core/wake_deflection.py +130 -0
  53. foxes/core/wake_model.py +222 -9
  54. foxes/core/wake_superposition.py +122 -4
  55. foxes/core/wind_farm.py +6 -6
  56. foxes/data/__init__.py +7 -2
  57. foxes/data/states/weibull_sectors_12.csv +13 -0
  58. foxes/data/states/weibull_sectors_12.nc +0 -0
  59. foxes/engines/__init__.py +14 -15
  60. foxes/engines/dask.py +39 -14
  61. foxes/engines/numpy.py +0 -3
  62. foxes/input/__init__.py +3 -3
  63. foxes/input/farm_layout/__init__.py +8 -8
  64. foxes/input/farm_layout/from_csv.py +1 -1
  65. foxes/input/farm_layout/ring.py +0 -1
  66. foxes/input/states/__init__.py +22 -12
  67. foxes/input/states/create/__init__.py +3 -2
  68. foxes/input/states/field_data_nc.py +10 -24
  69. foxes/input/states/multi_height.py +9 -6
  70. foxes/input/states/one_point_flow.py +0 -4
  71. foxes/input/states/single.py +1 -1
  72. foxes/input/states/states_table.py +10 -7
  73. foxes/input/states/weibull_sectors.py +225 -0
  74. foxes/input/states/wrg_states.py +7 -5
  75. foxes/input/yaml/__init__.py +9 -3
  76. foxes/input/yaml/dict.py +19 -19
  77. foxes/input/yaml/windio/__init__.py +10 -5
  78. foxes/input/yaml/windio/read_attributes.py +2 -2
  79. foxes/input/yaml/windio/read_farm.py +5 -5
  80. foxes/input/yaml/windio/read_fields.py +4 -2
  81. foxes/input/yaml/windio/read_site.py +52 -0
  82. foxes/input/yaml/windio/windio.py +1 -1
  83. foxes/models/__init__.py +15 -14
  84. foxes/models/axial_induction/__init__.py +2 -2
  85. foxes/models/farm_controllers/__init__.py +1 -1
  86. foxes/models/farm_models/__init__.py +1 -1
  87. foxes/models/ground_models/__init__.py +3 -2
  88. foxes/models/ground_models/wake_mirror.py +3 -3
  89. foxes/models/model_book.py +175 -49
  90. foxes/models/partial_wakes/__init__.py +6 -6
  91. foxes/models/partial_wakes/axiwake.py +30 -5
  92. foxes/models/partial_wakes/centre.py +47 -0
  93. foxes/models/partial_wakes/rotor_points.py +41 -11
  94. foxes/models/partial_wakes/segregated.py +2 -25
  95. foxes/models/partial_wakes/top_hat.py +27 -2
  96. foxes/models/point_models/__init__.py +4 -4
  97. foxes/models/rotor_models/__init__.py +3 -3
  98. foxes/models/turbine_models/__init__.py +11 -11
  99. foxes/models/turbine_models/set_farm_vars.py +0 -1
  100. foxes/models/turbine_types/PCt_file.py +0 -2
  101. foxes/models/turbine_types/PCt_from_two.py +0 -2
  102. foxes/models/turbine_types/__init__.py +9 -9
  103. foxes/models/vertical_profiles/__init__.py +7 -7
  104. foxes/models/wake_deflections/__init__.py +3 -0
  105. foxes/models/{wake_frames/yawed_wakes.py → wake_deflections/bastankhah2016.py} +32 -111
  106. foxes/models/wake_deflections/jimenez.py +277 -0
  107. foxes/models/wake_deflections/no_deflection.py +94 -0
  108. foxes/models/wake_frames/__init__.py +6 -7
  109. foxes/models/wake_frames/dynamic_wakes.py +12 -3
  110. foxes/models/wake_frames/rotor_wd.py +3 -1
  111. foxes/models/wake_frames/seq_dynamic_wakes.py +41 -7
  112. foxes/models/wake_frames/streamlines.py +8 -6
  113. foxes/models/wake_frames/timelines.py +9 -3
  114. foxes/models/wake_models/__init__.py +7 -7
  115. foxes/models/wake_models/dist_sliced.py +50 -84
  116. foxes/models/wake_models/gaussian.py +20 -0
  117. foxes/models/wake_models/induction/__init__.py +5 -5
  118. foxes/models/wake_models/induction/rankine_half_body.py +30 -71
  119. foxes/models/wake_models/induction/rathmann.py +65 -64
  120. foxes/models/wake_models/induction/self_similar.py +65 -68
  121. foxes/models/wake_models/induction/self_similar2020.py +0 -3
  122. foxes/models/wake_models/induction/vortex_sheet.py +71 -75
  123. foxes/models/wake_models/ti/__init__.py +2 -2
  124. foxes/models/wake_models/ti/crespo_hernandez.py +5 -3
  125. foxes/models/wake_models/ti/iec_ti.py +6 -4
  126. foxes/models/wake_models/top_hat.py +58 -7
  127. foxes/models/wake_models/wind/__init__.py +6 -4
  128. foxes/models/wake_models/wind/bastankhah14.py +25 -7
  129. foxes/models/wake_models/wind/bastankhah16.py +35 -3
  130. foxes/models/wake_models/wind/jensen.py +15 -2
  131. foxes/models/wake_models/wind/turbopark.py +28 -2
  132. foxes/models/wake_superpositions/__init__.py +18 -9
  133. foxes/models/wake_superpositions/ti_linear.py +4 -4
  134. foxes/models/wake_superpositions/ti_max.py +4 -4
  135. foxes/models/wake_superpositions/ti_pow.py +4 -4
  136. foxes/models/wake_superpositions/ti_quadratic.py +4 -4
  137. foxes/models/wake_superpositions/wind_vector.py +257 -0
  138. foxes/models/wake_superpositions/ws_linear.py +9 -10
  139. foxes/models/wake_superpositions/ws_max.py +8 -8
  140. foxes/models/wake_superpositions/ws_pow.py +8 -8
  141. foxes/models/wake_superpositions/ws_product.py +4 -4
  142. foxes/models/wake_superpositions/ws_quadratic.py +8 -8
  143. foxes/output/__init__.py +21 -19
  144. foxes/output/farm_layout.py +2 -2
  145. foxes/output/farm_results_eval.py +15 -15
  146. foxes/output/flow_plots_2d/__init__.py +2 -2
  147. foxes/output/flow_plots_2d/get_fig.py +4 -2
  148. foxes/output/rose_plot.py +3 -3
  149. foxes/output/seq_plugins/__init__.py +2 -2
  150. foxes/output/seq_plugins/seq_flow_ani_plugin.py +0 -3
  151. foxes/output/seq_plugins/seq_wake_debug_plugin.py +0 -1
  152. foxes/output/turbine_type_curves.py +7 -8
  153. foxes/utils/__init__.py +37 -19
  154. foxes/utils/abl/__init__.py +4 -4
  155. foxes/utils/cubic_roots.py +1 -1
  156. foxes/utils/data_book.py +4 -3
  157. foxes/utils/dict.py +3 -3
  158. foxes/utils/exec_python.py +5 -5
  159. foxes/utils/factory.py +1 -3
  160. foxes/utils/geom2d/__init__.py +7 -5
  161. foxes/utils/geopandas_utils.py +2 -2
  162. foxes/utils/pandas_utils.py +4 -3
  163. foxes/utils/tab_files.py +0 -1
  164. foxes/utils/weibull.py +28 -0
  165. foxes/utils/wrg_utils.py +3 -1
  166. foxes/utils/xarray_utils.py +9 -2
  167. foxes/variables.py +67 -9
  168. {foxes-1.3.dist-info → foxes-1.4.dist-info}/METADATA +6 -15
  169. foxes-1.4.dist-info/RECORD +320 -0
  170. {foxes-1.3.dist-info → foxes-1.4.dist-info}/WHEEL +1 -1
  171. tests/1_verification/flappy_0_6/PCt_files/flappy/run.py +2 -3
  172. tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +1 -1
  173. tests/1_verification/flappy_0_6/abl_states/flappy/run.py +0 -1
  174. tests/1_verification/flappy_0_6/partial_top_hat/flappy/run.py +0 -1
  175. tests/1_verification/flappy_0_6/partial_top_hat/test_partial_top_hat.py +0 -2
  176. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +0 -1
  177. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +0 -1
  178. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +0 -1
  179. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +0 -1
  180. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +0 -1
  181. tests/1_verification/flappy_0_6_2/grid_rotors/flappy/run.py +0 -2
  182. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +0 -1
  183. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/flappy/run.py +0 -1
  184. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +0 -1
  185. foxes/output/round.py +0 -10
  186. foxes/utils/pandas_helpers.py +0 -178
  187. foxes-1.3.dist-info/RECORD +0 -313
  188. {foxes-1.3.dist-info → foxes-1.4.dist-info}/entry_points.txt +0 -0
  189. {foxes-1.3.dist-info → foxes-1.4.dist-info/licenses}/LICENSE +0 -0
  190. {foxes-1.3.dist-info → foxes-1.4.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,12 @@
1
1
  from abc import abstractmethod
2
+ import numpy as np
2
3
 
3
- from foxes.core import WakeModel
4
+ from foxes.core import SingleTurbineWakeModel
5
+ from foxes.config import config
6
+ import foxes.variables as FV
4
7
 
5
8
 
6
- class DistSlicedWakeModel(WakeModel):
9
+ class DistSlicedWakeModel(SingleTurbineWakeModel):
7
10
  """
8
11
  Abstract base class for wake models for which
9
12
  the x-denpendency can be separated from the
@@ -12,65 +15,44 @@ class DistSlicedWakeModel(WakeModel):
12
15
  The multi-yz ability is used by the `PartialDistSlicedWake`
13
16
  partial wakes model.
14
17
 
15
- Attributes
16
- ----------
17
- superpositions: dict
18
- The superpositions. Key: variable name str,
19
- value: The wake superposition model name,
20
- will be looked up in model book
21
- superp: dict
22
- The superposition dict, key: variable name str,
23
- value: `foxes.core.WakeSuperposition`
24
-
25
18
  :group: models.wake_models
26
19
 
27
20
  """
28
21
 
29
- def __init__(self, superpositions):
22
+ def new_wake_deltas(self, algo, mdata, fdata, tdata):
30
23
  """
31
- Constructor.
24
+ Creates new empty wake delta arrays.
32
25
 
33
26
  Parameters
34
27
  ----------
35
- superpositions: dict
36
- The superpositions. Key: variable name str,
37
- value: The wake superposition model name,
38
- will be looked up in model book
39
-
40
- """
41
- super().__init__()
42
- self.superpositions = superpositions
43
-
44
- def sub_models(self):
45
- """
46
- List of all sub-models
28
+ algo: foxes.core.Algorithm
29
+ The calculation algorithm
30
+ mdata: foxes.core.MData
31
+ The model data
32
+ fdata: foxes.core.FData
33
+ The farm data
34
+ tdata: foxes.core.TData
35
+ The target point data
47
36
 
48
37
  Returns
49
38
  -------
50
- smdls: list of foxes.core.Model
51
- Names of all sub models
52
-
53
- """
54
- return list(self.superp.values())
55
-
56
- def initialize(self, algo, verbosity=0, force=False):
57
- """
58
- Initializes the model.
59
-
60
- Parameters
61
- ----------
62
- algo: foxes.core.Algorithm
63
- The calculation algorithm
64
- verbosity: int
65
- The verbosity level, 0 = silent
66
- force: bool
67
- Overwrite existing data
39
+ wake_deltas: dict
40
+ Key: variable name, value: The zero filled
41
+ wake deltas, shape: (n_states, n_targets, n_tpoints, ...)
68
42
 
69
43
  """
70
- self.superp = {
71
- v: algo.mbook.wake_superpositions[s] for v, s in self.superpositions.items()
72
- }
73
- super().initialize(algo, verbosity, force)
44
+ if self.has_uv:
45
+ duv = np.zeros(
46
+ (tdata.n_states, tdata.n_targets, tdata.n_tpoints, 2),
47
+ dtype=config.dtype_double,
48
+ )
49
+ return {FV.UV: duv}
50
+ else:
51
+ dws = np.zeros(
52
+ (tdata.n_states, tdata.n_targets, tdata.n_tpoints),
53
+ dtype=config.dtype_double,
54
+ )
55
+ return {FV.WS: dws}
74
56
 
75
57
  @abstractmethod
76
58
  def calc_wakes_x_yz(
@@ -159,6 +141,26 @@ class DistSlicedWakeModel(WakeModel):
159
141
  algo, mdata, fdata, tdata, downwind_index, x, yz
160
142
  )
161
143
 
144
+ if self.affects_ws and self.has_uv:
145
+ assert self.has_vector_wind_superp, (
146
+ f"Wake model {self.name}: Missing vector wind superposition, got '{self.wind_superposition}'"
147
+ )
148
+ if FV.UV in wdeltas or FV.WS in wdeltas:
149
+ if FV.UV not in wdeltas:
150
+ self.vec_superp.wdeltas_ws2uv(
151
+ algo, fdata, tdata, downwind_index, wdeltas, st_sel
152
+ )
153
+ wake_deltas[FV.UV] = self.vec_superp.add_wake_vector(
154
+ algo,
155
+ mdata,
156
+ fdata,
157
+ tdata,
158
+ downwind_index,
159
+ st_sel,
160
+ wake_deltas[FV.UV],
161
+ wdeltas.pop(FV.UV),
162
+ )
163
+
162
164
  for v, hdel in wdeltas.items():
163
165
  try:
164
166
  superp = self.superp[v]
@@ -178,39 +180,3 @@ class DistSlicedWakeModel(WakeModel):
178
180
  wake_deltas[v],
179
181
  hdel,
180
182
  )
181
-
182
- def finalize_wake_deltas(
183
- self,
184
- algo,
185
- mdata,
186
- fdata,
187
- amb_results,
188
- wake_deltas,
189
- ):
190
- """
191
- Finalize the wake calculation.
192
-
193
- Modifies wake_deltas on the fly.
194
-
195
- Parameters
196
- ----------
197
- algo: foxes.core.Algorithm
198
- The calculation algorithm
199
- mdata: foxes.core.MData
200
- The model data
201
- fdata: foxes.core.FData
202
- The farm data
203
- amb_results: dict
204
- The ambient results, key: variable name str,
205
- values: numpy.ndarray with shape
206
- (n_states, n_targets, n_tpoints)
207
- wake_deltas: dict
208
- The wake deltas object at the selected target
209
- turbines. Key: variable str, value: numpy.ndarray
210
- with shape (n_states, n_targets, n_tpoints)
211
-
212
- """
213
- for v, s in self.superp.items():
214
- wake_deltas[v] = s.calc_final_wake_delta(
215
- algo, mdata, fdata, v, amb_results[v], wake_deltas[v]
216
- )
@@ -2,6 +2,8 @@ import numpy as np
2
2
  from abc import abstractmethod
3
3
 
4
4
  from foxes.models.wake_models.axisymmetric import AxisymmetricWakeModel
5
+ import foxes.variables as FV
6
+ import foxes.constants as FC
5
7
 
6
8
 
7
9
  class GaussianWakeModel(AxisymmetricWakeModel):
@@ -94,13 +96,31 @@ class GaussianWakeModel(AxisymmetricWakeModel):
94
96
  is non-zero, shape: (n_states, n_targets)
95
97
 
96
98
  """
99
+ # compute amplitude and sigma:
97
100
  amsi, st_sel = self.calc_amplitude_sigma(
98
101
  algo, mdata, fdata, tdata, downwind_index, x
99
102
  )
103
+
104
+ # evaluate the Gaussian function:
100
105
  wdeltas = {}
101
106
  rsel = r[st_sel]
102
107
  for v in amsi.keys():
103
108
  ampld, sigma = amsi[v]
104
109
  wdeltas[v] = ampld[:, None] * np.exp(-0.5 * (rsel / sigma[:, None]) ** 2)
105
110
 
111
+ if self.affects_ws and FV.WS in wdeltas:
112
+ # wake deflection causes wind vector rotation:
113
+ if FC.WDEFL_ROT_ANGLE in tdata:
114
+ dwd_defl = tdata.pop(FC.WDEFL_ROT_ANGLE)
115
+ if FV.WD not in wdeltas:
116
+ wdeltas[FV.WD] = np.zeros_like(wdeltas[FV.WS])
117
+ wdeltas[FV.WD][:] = dwd_defl[st_sel]
118
+ else:
119
+ wdeltas[FV.WD] += dwd_defl[st_sel]
120
+
121
+ # wake deflection causes wind speed reduction:
122
+ if FC.WDEFL_DWS_FACTOR in tdata:
123
+ dws_defl = tdata.pop(FC.WDEFL_DWS_FACTOR)
124
+ wdeltas[FV.WS] *= dws_defl[st_sel]
125
+
106
126
  return wdeltas, st_sel
@@ -2,8 +2,8 @@
2
2
  Induction wake models
3
3
  """
4
4
 
5
- from .rankine_half_body import RankineHalfBody
6
- from .rathmann import Rathmann
7
- from .self_similar import SelfSimilar
8
- from .self_similar2020 import SelfSimilar2020
9
- from .vortex_sheet import VortexSheet
5
+ from .rankine_half_body import RankineHalfBody as RankineHalfBody
6
+ from .rathmann import Rathmann as Rathmann
7
+ from .self_similar import SelfSimilar as SelfSimilar
8
+ from .self_similar2020 import SelfSimilar2020 as SelfSimilar2020
9
+ from .vortex_sheet import VortexSheet as VortexSheet
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
2
 
3
+ from foxes.config import config
3
4
  from foxes.core import TurbineInductionModel
4
- from foxes.utils import uv2wd, wd2uv, delta_wd
5
5
  import foxes.variables as FV
6
6
  import foxes.constants as FC
7
7
 
@@ -10,9 +10,6 @@ class RankineHalfBody(TurbineInductionModel):
10
10
  """
11
11
  The Rankine half body induction wake model
12
12
 
13
- The individual wake effects are superposed linearly,
14
- without invoking a wake superposition model.
15
-
16
13
  Notes
17
14
  -----
18
15
  Reference:
@@ -30,24 +27,41 @@ class RankineHalfBody(TurbineInductionModel):
30
27
 
31
28
  """
32
29
 
33
- def __init__(self, induction="Madsen"):
30
+ def __init__(self, superposition="vector", induction="Madsen"):
34
31
  """
35
32
  Constructor.
36
33
 
37
34
  Parameters
38
35
  ----------
36
+ superposition: str
37
+ The wind speed deficit superposition.
39
38
  induction: foxes.core.AxialInductionModel or str
40
39
  The induction model
41
40
 
42
41
  """
43
- super().__init__()
42
+ super().__init__(wind_superposition=superposition, other_superpositions={})
44
43
  self.induction = induction
45
44
 
45
+ self._has_uv = True
46
+
46
47
  def __repr__(self):
47
48
  iname = (
48
49
  self.induction if isinstance(self.induction, str) else self.induction.name
49
50
  )
50
- return f"{type(self).__name__}(induction={iname})"
51
+ return f"{type(self).__name__}({self.wind_superposition}, induction={iname})"
52
+
53
+ @property
54
+ def affects_ws(self):
55
+ """
56
+ Flag for wind speed wake models
57
+
58
+ Returns
59
+ -------
60
+ dws: bool
61
+ If True, this model affects wind speed
62
+
63
+ """
64
+ return True
51
65
 
52
66
  def sub_models(self):
53
67
  """
@@ -59,7 +73,7 @@ class RankineHalfBody(TurbineInductionModel):
59
73
  All sub models
60
74
 
61
75
  """
62
- return [self.induction]
76
+ return super().sub_models() + [self.induction]
63
77
 
64
78
  def initialize(self, algo, verbosity=0, force=False):
65
79
  """
@@ -98,15 +112,14 @@ class RankineHalfBody(TurbineInductionModel):
98
112
  -------
99
113
  wake_deltas: dict
100
114
  Key: variable name, value: The zero filled
101
- wake deltas, shape: (n_states, n_turbines, n_rpoints, ...)
115
+ wake deltas, shape: (n_states, n_targets, n_tpoints, ...)
102
116
 
103
117
  """
104
- return {
105
- FV.WS: np.zeros_like(tdata[FC.TARGETS][..., 0]),
106
- FV.WD: np.zeros_like(tdata[FC.TARGETS][..., 0]),
107
- "U": np.zeros_like(tdata[FC.TARGETS][..., 0]),
108
- "V": np.zeros_like(tdata[FC.TARGETS][..., 0]),
109
- }
118
+ duv = np.zeros(
119
+ (tdata.n_states, tdata.n_targets, tdata.n_tpoints, 2),
120
+ dtype=config.dtype_double,
121
+ )
122
+ return {FV.UV: duv}
110
123
 
111
124
  def contribute(
112
125
  self,
@@ -205,8 +218,7 @@ class RankineHalfBody(TurbineInductionModel):
205
218
 
206
219
  # calc velocity components
207
220
  vel_factor = m[st_sel] / (4 * np.linalg.norm(xyz, axis=-1) ** 3)
208
- wake_deltas["U"][st_sel] += vel_factor * xyz[:, 0]
209
- wake_deltas["V"][st_sel] += vel_factor * xyz[:, 1]
221
+ wake_deltas[FV.UV][st_sel] += vel_factor[:, None] * xyz[:, :2]
210
222
 
211
223
  # set values inside body shape
212
224
  st_sel = (ct > 1e-8) & (RHB_shape >= -1) & (x >= xs) & (x <= 0)
@@ -217,57 +229,4 @@ class RankineHalfBody(TurbineInductionModel):
217
229
 
218
230
  # calc velocity components
219
231
  vel_factor = m[st_sel] / (4 * np.linalg.norm(xyz, axis=-1) ** 3)
220
- wake_deltas["U"][st_sel] += vel_factor * xyz[:, 0]
221
-
222
- def finalize_wake_deltas(
223
- self,
224
- algo,
225
- mdata,
226
- fdata,
227
- amb_results,
228
- wake_deltas,
229
- ):
230
- """
231
- Finalize the wake calculation.
232
-
233
- Modifies wake_deltas on the fly.
234
-
235
- Parameters
236
- ----------
237
- algo: foxes.core.Algorithm
238
- The calculation algorithm
239
- mdata: foxes.core.MData
240
- The model data
241
- fdata: foxes.core.FData
242
- The farm data
243
- amb_results: dict
244
- The ambient results, key: variable name str,
245
- values: numpy.ndarray with shape
246
- (n_states, n_targets, n_tpoints)
247
- wake_deltas: dict
248
- The wake deltas object at the selected target
249
- turbines. Key: variable str, value: numpy.ndarray
250
- with shape (n_states, n_targets, n_tpoints)
251
-
252
- """
253
- # calc ambient wind vector:
254
- ws0 = amb_results[FV.WS]
255
- nx = wd2uv(amb_results[FV.WD])
256
- wind_vec = nx * ws0[:, :, :, None]
257
-
258
- # wake deltas are in wake frame, rotate back to global frame:
259
- ny = np.stack((-nx[..., 1], nx[..., 0]), axis=-1)
260
- delta_uv = (
261
- wake_deltas["U"][:, :, :, None] * nx + wake_deltas["V"][:, :, :, None] * ny
262
- )
263
- del ws0, nx, ny
264
-
265
- # add ambient result to wake deltas:
266
- wind_vec += delta_uv
267
- del delta_uv
268
-
269
- # deduce WS and WD deltas:
270
- new_wd = uv2wd(wind_vec)
271
- new_ws = np.linalg.norm(wind_vec, axis=-1)
272
- wake_deltas[FV.WS] += new_ws - amb_results[FV.WS]
273
- wake_deltas[FV.WD] += delta_wd(amb_results[FV.WD], new_wd)
232
+ wake_deltas[FV.UV][st_sel, 0] += vel_factor * xyz[:, 0]
@@ -1,5 +1,6 @@
1
1
  import numpy as np
2
2
 
3
+ from foxes.config import config
3
4
  from foxes.core import TurbineInductionModel
4
5
  import foxes.variables as FV
5
6
  import foxes.constants as FC
@@ -50,16 +51,28 @@ class Rathmann(TurbineInductionModel):
50
51
  Calculate only the pre-rotor region
51
52
 
52
53
  """
53
- super().__init__()
54
+ super().__init__(wind_superposition=superposition)
54
55
  self.induction = induction
55
56
  self.pre_rotor_only = pre_rotor_only
56
- self._superp_name = superposition
57
57
 
58
58
  def __repr__(self):
59
59
  iname = (
60
60
  self.induction if isinstance(self.induction, str) else self.induction.name
61
61
  )
62
- return f"{type(self).__name__}({self._superp_name}, induction={iname})"
62
+ return f"{type(self).__name__}({self.wind_superposition}, induction={iname})"
63
+
64
+ @property
65
+ def affects_ws(self):
66
+ """
67
+ Flag for wind speed wake models
68
+
69
+ Returns
70
+ -------
71
+ dws: bool
72
+ If True, this model affects wind speed
73
+
74
+ """
75
+ return True
63
76
 
64
77
  def sub_models(self):
65
78
  """
@@ -71,7 +84,7 @@ class Rathmann(TurbineInductionModel):
71
84
  All sub models
72
85
 
73
86
  """
74
- return [self._superp, self.induction]
87
+ return super().sub_models() + [self.induction]
75
88
 
76
89
  def initialize(self, algo, verbosity=0, force=False):
77
90
  """
@@ -87,7 +100,6 @@ class Rathmann(TurbineInductionModel):
87
100
  Overwrite existing data
88
101
 
89
102
  """
90
- self._superp = algo.mbook.wake_superpositions[self._superp_name]
91
103
  if isinstance(self.induction, str):
92
104
  self.induction = algo.mbook.axial_induction[self.induction]
93
105
  super().initialize(algo, verbosity, force)
@@ -111,10 +123,21 @@ class Rathmann(TurbineInductionModel):
111
123
  -------
112
124
  wake_deltas: dict
113
125
  Key: variable name, value: The zero filled
114
- wake deltas, shape: (n_states, n_turbines, n_rpoints, ...)
126
+ wake deltas, shape: (n_states, n_targets, n_tpoints, ...)
115
127
 
116
128
  """
117
- return {FV.WS: np.zeros_like(tdata[FC.TARGETS][..., 0])}
129
+ if self.has_uv:
130
+ duv = np.zeros(
131
+ (tdata.n_states, tdata.n_targets, tdata.n_tpoints, 2),
132
+ dtype=config.dtype_double,
133
+ )
134
+ return {FV.UV: duv}
135
+ else:
136
+ dws = np.zeros(
137
+ (tdata.n_states, tdata.n_targets, tdata.n_tpoints),
138
+ dtype=config.dtype_double,
139
+ )
140
+ return {FV.WS: dws}
118
141
 
119
142
  def contribute(
120
143
  self,
@@ -195,6 +218,39 @@ class Rathmann(TurbineInductionModel):
195
218
  sin_beta = 1 / np.sqrt(x_R**2 + r_R**2 + 1) # eqn 19
196
219
  return sin_alpha * sin_beta * (1 + x_R**2)
197
220
 
221
+ def add_wake(sp_sel, wake_deltas, blockage):
222
+ """adds to wake deltas"""
223
+ if self.has_uv:
224
+ assert self.has_vector_wind_superp, (
225
+ f"Wake model {self.name}: Missing vector wind superposition, got '{self.wind_superposition}'"
226
+ )
227
+ wdeltas = {FV.WS: blockage}
228
+ self.vec_superp.wdeltas_ws2uv(
229
+ algo, fdata, tdata, downwind_index, wdeltas, sp_sel
230
+ )
231
+ wake_deltas[FV.UV] = self.vec_superp.add_wake_vector(
232
+ algo,
233
+ mdata,
234
+ fdata,
235
+ tdata,
236
+ downwind_index,
237
+ sp_sel,
238
+ wake_deltas[FV.UV],
239
+ wdeltas.pop(FV.UV),
240
+ )
241
+ else:
242
+ self.superp[FV.WS].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,
252
+ )
253
+
198
254
  # ws delta in front of rotor
199
255
  sp_sel = (ct > 1e-8) & (x_R <= 0)
200
256
  if np.any(sp_sel):
@@ -202,17 +258,7 @@ class Rathmann(TurbineInductionModel):
202
258
  a = self.induction.ct2a(ct[sp_sel])
203
259
  blockage = a * mu(xr) * G(xr, r_R[sp_sel]) # eqn 10
204
260
 
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
- )
261
+ add_wake(sp_sel, wake_deltas, -blockage)
216
262
 
217
263
  # ws delta behind rotor
218
264
  if not self.pre_rotor_only:
@@ -223,51 +269,6 @@ class Rathmann(TurbineInductionModel):
223
269
  a = self.induction.ct2a(ct[sp_sel])
224
270
  blockage = a * mu(-xr) * G(-xr, r_R[sp_sel]) # eqn 10
225
271
 
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
- )
272
+ add_wake(sp_sel, wake_deltas, blockage)
237
273
 
238
274
  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
- )