foxes 1.2.5__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 (201) 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 +16 -0
  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 +22 -18
  23. foxes/algorithms/__init__.py +6 -6
  24. foxes/algorithms/downwind/__init__.py +2 -2
  25. foxes/algorithms/downwind/downwind.py +53 -27
  26. foxes/algorithms/downwind/models/__init__.py +6 -6
  27. foxes/algorithms/downwind/models/farm_wakes_calc.py +22 -14
  28. foxes/algorithms/downwind/models/init_farm_data.py +4 -5
  29. foxes/algorithms/downwind/models/point_wakes_calc.py +7 -13
  30. foxes/algorithms/downwind/models/reorder_farm_output.py +5 -1
  31. foxes/algorithms/downwind/models/set_amb_point_results.py +7 -7
  32. foxes/algorithms/iterative/__init__.py +7 -3
  33. foxes/algorithms/iterative/iterative.py +1 -2
  34. foxes/algorithms/iterative/models/__init__.py +7 -3
  35. foxes/algorithms/iterative/models/farm_wakes_calc.py +15 -8
  36. foxes/algorithms/sequential/__init__.py +3 -3
  37. foxes/algorithms/sequential/models/__init__.py +2 -2
  38. foxes/algorithms/sequential/models/seq_state.py +0 -18
  39. foxes/algorithms/sequential/sequential.py +8 -22
  40. foxes/config/__init__.py +5 -1
  41. foxes/constants.py +22 -0
  42. foxes/core/__init__.py +45 -22
  43. foxes/core/algorithm.py +0 -1
  44. foxes/core/data.py +56 -29
  45. foxes/core/engine.py +28 -14
  46. foxes/core/farm_controller.py +2 -2
  47. foxes/core/farm_data_model.py +1 -0
  48. foxes/core/ground_model.py +4 -13
  49. foxes/core/model.py +5 -5
  50. foxes/core/partial_wakes_model.py +147 -10
  51. foxes/core/point_data_model.py +2 -3
  52. foxes/core/rotor_model.py +42 -38
  53. foxes/core/states.py +4 -50
  54. foxes/core/turbine.py +2 -1
  55. foxes/core/wake_deflection.py +130 -0
  56. foxes/core/wake_model.py +222 -9
  57. foxes/core/wake_superposition.py +122 -4
  58. foxes/core/wind_farm.py +6 -6
  59. foxes/data/__init__.py +7 -2
  60. foxes/data/states/weibull_sectors_12.csv +13 -0
  61. foxes/data/states/weibull_sectors_12.nc +0 -0
  62. foxes/engines/__init__.py +14 -15
  63. foxes/engines/dask.py +39 -14
  64. foxes/engines/numpy.py +0 -3
  65. foxes/input/__init__.py +3 -3
  66. foxes/input/farm_layout/__init__.py +8 -8
  67. foxes/input/farm_layout/from_csv.py +1 -1
  68. foxes/input/farm_layout/ring.py +0 -1
  69. foxes/input/states/__init__.py +22 -11
  70. foxes/input/states/create/__init__.py +3 -2
  71. foxes/input/states/field_data_nc.py +48 -84
  72. foxes/input/states/multi_height.py +40 -60
  73. foxes/input/states/one_point_flow.py +22 -25
  74. foxes/input/states/scan.py +6 -19
  75. foxes/input/states/single.py +6 -18
  76. foxes/input/states/states_table.py +25 -44
  77. foxes/input/states/weibull_sectors.py +225 -0
  78. foxes/input/states/wrg_states.py +151 -37
  79. foxes/input/yaml/__init__.py +9 -3
  80. foxes/input/yaml/dict.py +19 -19
  81. foxes/input/yaml/windio/__init__.py +10 -5
  82. foxes/input/yaml/windio/read_attributes.py +2 -2
  83. foxes/input/yaml/windio/read_farm.py +5 -5
  84. foxes/input/yaml/windio/read_fields.py +4 -2
  85. foxes/input/yaml/windio/read_site.py +52 -0
  86. foxes/input/yaml/windio/windio.py +1 -1
  87. foxes/models/__init__.py +15 -14
  88. foxes/models/axial_induction/__init__.py +2 -2
  89. foxes/models/farm_controllers/__init__.py +1 -1
  90. foxes/models/farm_models/__init__.py +1 -1
  91. foxes/models/ground_models/__init__.py +3 -2
  92. foxes/models/ground_models/wake_mirror.py +3 -3
  93. foxes/models/model_book.py +175 -49
  94. foxes/models/partial_wakes/__init__.py +6 -6
  95. foxes/models/partial_wakes/axiwake.py +30 -5
  96. foxes/models/partial_wakes/centre.py +47 -0
  97. foxes/models/partial_wakes/rotor_points.py +45 -9
  98. foxes/models/partial_wakes/segregated.py +2 -20
  99. foxes/models/partial_wakes/top_hat.py +27 -2
  100. foxes/models/point_models/__init__.py +4 -4
  101. foxes/models/rotor_models/__init__.py +3 -3
  102. foxes/models/rotor_models/centre.py +6 -4
  103. foxes/models/turbine_models/__init__.py +11 -11
  104. foxes/models/turbine_models/set_farm_vars.py +0 -1
  105. foxes/models/turbine_types/PCt_file.py +0 -2
  106. foxes/models/turbine_types/PCt_from_two.py +0 -2
  107. foxes/models/turbine_types/__init__.py +9 -9
  108. foxes/models/vertical_profiles/__init__.py +7 -7
  109. foxes/models/wake_deflections/__init__.py +3 -0
  110. foxes/models/{wake_frames/yawed_wakes.py → wake_deflections/bastankhah2016.py} +32 -111
  111. foxes/models/wake_deflections/jimenez.py +277 -0
  112. foxes/models/wake_deflections/no_deflection.py +94 -0
  113. foxes/models/wake_frames/__init__.py +6 -7
  114. foxes/models/wake_frames/dynamic_wakes.py +12 -3
  115. foxes/models/wake_frames/rotor_wd.py +3 -1
  116. foxes/models/wake_frames/seq_dynamic_wakes.py +45 -8
  117. foxes/models/wake_frames/streamlines.py +8 -6
  118. foxes/models/wake_frames/timelines.py +19 -3
  119. foxes/models/wake_models/__init__.py +7 -7
  120. foxes/models/wake_models/dist_sliced.py +50 -84
  121. foxes/models/wake_models/gaussian.py +20 -0
  122. foxes/models/wake_models/induction/__init__.py +5 -5
  123. foxes/models/wake_models/induction/rankine_half_body.py +30 -71
  124. foxes/models/wake_models/induction/rathmann.py +65 -64
  125. foxes/models/wake_models/induction/self_similar.py +65 -68
  126. foxes/models/wake_models/induction/self_similar2020.py +0 -3
  127. foxes/models/wake_models/induction/vortex_sheet.py +71 -75
  128. foxes/models/wake_models/ti/__init__.py +2 -2
  129. foxes/models/wake_models/ti/crespo_hernandez.py +5 -3
  130. foxes/models/wake_models/ti/iec_ti.py +6 -4
  131. foxes/models/wake_models/top_hat.py +58 -7
  132. foxes/models/wake_models/wind/__init__.py +6 -4
  133. foxes/models/wake_models/wind/bastankhah14.py +25 -7
  134. foxes/models/wake_models/wind/bastankhah16.py +35 -3
  135. foxes/models/wake_models/wind/jensen.py +15 -2
  136. foxes/models/wake_models/wind/turbopark.py +28 -2
  137. foxes/models/wake_superpositions/__init__.py +18 -9
  138. foxes/models/wake_superpositions/ti_linear.py +4 -4
  139. foxes/models/wake_superpositions/ti_max.py +4 -4
  140. foxes/models/wake_superpositions/ti_pow.py +4 -4
  141. foxes/models/wake_superpositions/ti_quadratic.py +4 -4
  142. foxes/models/wake_superpositions/wind_vector.py +257 -0
  143. foxes/models/wake_superpositions/ws_linear.py +9 -10
  144. foxes/models/wake_superpositions/ws_max.py +8 -8
  145. foxes/models/wake_superpositions/ws_pow.py +8 -8
  146. foxes/models/wake_superpositions/ws_product.py +4 -4
  147. foxes/models/wake_superpositions/ws_quadratic.py +8 -8
  148. foxes/output/__init__.py +21 -19
  149. foxes/output/farm_layout.py +14 -6
  150. foxes/output/farm_results_eval.py +51 -27
  151. foxes/output/flow_plots_2d/__init__.py +2 -2
  152. foxes/output/flow_plots_2d/get_fig.py +4 -2
  153. foxes/output/rose_plot.py +23 -5
  154. foxes/output/seq_plugins/__init__.py +2 -2
  155. foxes/output/seq_plugins/seq_flow_ani_plugin.py +0 -3
  156. foxes/output/seq_plugins/seq_wake_debug_plugin.py +0 -1
  157. foxes/output/slice_data.py +16 -19
  158. foxes/output/turbine_type_curves.py +7 -8
  159. foxes/utils/__init__.py +37 -19
  160. foxes/utils/abl/__init__.py +4 -4
  161. foxes/utils/cubic_roots.py +1 -1
  162. foxes/utils/data_book.py +4 -3
  163. foxes/utils/dict.py +3 -3
  164. foxes/utils/exec_python.py +5 -5
  165. foxes/utils/factory.py +1 -3
  166. foxes/utils/geom2d/__init__.py +7 -5
  167. foxes/utils/geopandas_utils.py +2 -2
  168. foxes/utils/pandas_utils.py +4 -3
  169. foxes/utils/tab_files.py +0 -1
  170. foxes/utils/weibull.py +28 -0
  171. foxes/utils/wrg_utils.py +3 -1
  172. foxes/utils/xarray_utils.py +9 -2
  173. foxes/variables.py +67 -9
  174. {foxes-1.2.5.dist-info → foxes-1.4.dist-info}/METADATA +14 -21
  175. foxes-1.4.dist-info/RECORD +320 -0
  176. {foxes-1.2.5.dist-info → foxes-1.4.dist-info}/WHEEL +1 -1
  177. tests/0_consistency/iterative/test_iterative.py +2 -3
  178. tests/0_consistency/partial_wakes/test_partial_wakes.py +2 -2
  179. tests/1_verification/flappy_0_6/PCt_files/flappy/run.py +2 -3
  180. tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +48 -56
  181. tests/1_verification/flappy_0_6/abl_states/flappy/run.py +0 -1
  182. tests/1_verification/flappy_0_6/abl_states/test_abl_states.py +33 -36
  183. tests/1_verification/flappy_0_6/partial_top_hat/flappy/run.py +0 -1
  184. tests/1_verification/flappy_0_6/partial_top_hat/test_partial_top_hat.py +0 -2
  185. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +3 -3
  186. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +3 -4
  187. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +3 -4
  188. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +3 -4
  189. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +3 -4
  190. tests/1_verification/flappy_0_6_2/grid_rotors/flappy/run.py +0 -2
  191. tests/1_verification/flappy_0_6_2/grid_rotors/test_grid_rotors.py +3 -3
  192. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +3 -3
  193. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/flappy/run.py +0 -1
  194. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +3 -4
  195. tests/3_examples/test_examples.py +3 -2
  196. foxes/output/round.py +0 -10
  197. foxes/utils/pandas_helpers.py +0 -178
  198. foxes-1.2.5.dist-info/RECORD +0 -312
  199. {foxes-1.2.5.dist-info → foxes-1.4.dist-info}/entry_points.txt +0 -0
  200. {foxes-1.2.5.dist-info → foxes-1.4.dist-info/licenses}/LICENSE +0 -0
  201. {foxes-1.2.5.dist-info → foxes-1.4.dist-info}/top_level.txt +0 -0
foxes/core/model.py CHANGED
@@ -77,7 +77,7 @@ class Model(ABC):
77
77
 
78
78
  Returns
79
79
  -------
80
- bool :
80
+ bool:
81
81
  True if the model has been initialized.
82
82
 
83
83
  """
@@ -424,8 +424,8 @@ class Model(ABC):
424
424
  # lookup mdata:
425
425
  elif s == "m" and mdata is not None and variable in mdata:
426
426
  a, d = _filter_dims(mdata)
427
- l = len(d)
428
- if l <= len(dims) and d == dims[:l]:
427
+ ld = len(d)
428
+ if ld <= len(dims) and d == dims[:ld]:
429
429
  out = _match_shape(mdata[variable])
430
430
 
431
431
  # lookup fdata:
@@ -448,8 +448,8 @@ class Model(ABC):
448
448
  and variable in tdata
449
449
  ):
450
450
  a, d = _filter_dims(tdata)
451
- l = len(d)
452
- if l <= len(dims) and d == dims[:l]:
451
+ ld = len(ld)
452
+ if ld <= len(dims) and d == dims[:ld]:
453
453
  out = _match_shape(tdata[variable])
454
454
 
455
455
  # lookup wake modelling data:
@@ -1,8 +1,13 @@
1
1
  from abc import abstractmethod
2
+ import numpy as np
2
3
 
3
- from foxes.utils import new_instance
4
+ from foxes.utils import new_instance, wd2uv, uv2wd
5
+ from foxes.config import config
6
+ import foxes.variables as FV
7
+ import foxes.constants as FC
4
8
 
5
9
  from .model import Model
10
+ from .data import TData
6
11
 
7
12
 
8
13
  class PartialWakesModel(Model):
@@ -68,6 +73,144 @@ class PartialWakesModel(Model):
68
73
  """
69
74
  pass
70
75
 
76
+ def get_initial_tdata(
77
+ self,
78
+ algo,
79
+ mdata,
80
+ fdata,
81
+ amb_rotor_res,
82
+ rotor_weights,
83
+ wmodels,
84
+ ):
85
+ """
86
+ Creates the initial target data object
87
+
88
+ Parameters
89
+ ----------
90
+ algo: foxes.core.Algorithm
91
+ The calculation algorithm
92
+ mdata: foxes.core.MData
93
+ The model data
94
+ fdata: foxes.core.FData
95
+ The farm data
96
+ amb_rotor_res: dict
97
+ The ambient results at rotor points,
98
+ key: variable name, value: numpy.ndarray
99
+ of shape: (n_states, n_turbines, n_rotor_points)
100
+ rotor_weights: numpy.ndarray
101
+ The rotor point weights, shape: (n_rotor_points,)
102
+ wmodels: list of foxes.core.WakeModel
103
+ The wake models for this partial wake model
104
+
105
+ Returns
106
+ -------
107
+ tdata: foxes.core.TData
108
+ The target point data for the wake points
109
+
110
+ """
111
+ tpoints, tweights = self.get_wake_points(algo, mdata, fdata)
112
+ tdata = TData.from_tpoints(tpoints, tweights)
113
+
114
+ # map wind data:
115
+ if FV.WD in amb_rotor_res or FV.WS in amb_rotor_res:
116
+ assert FV.WD in amb_rotor_res and FV.WS in amb_rotor_res, (
117
+ "Require both wind direction and speed in ambient rotor results."
118
+ )
119
+ uv = wd2uv(amb_rotor_res[FV.WD], amb_rotor_res[FV.WS])
120
+ uv = np.stack(
121
+ [
122
+ self.map_rotor_results(
123
+ algo, mdata, fdata, tdata, FV.U, uv[..., 0], rotor_weights
124
+ ),
125
+ self.map_rotor_results(
126
+ algo, mdata, fdata, tdata, FV.V, uv[..., 1], rotor_weights
127
+ ),
128
+ ],
129
+ axis=-1,
130
+ )
131
+ tdata.add(FV.AMB_WD, uv2wd(uv), dims=(FC.STATE, FC.TARGET, FC.TPOINT))
132
+ tdata.add(
133
+ FV.AMB_WS,
134
+ np.linalg.norm(uv, axis=-1),
135
+ dims=(FC.STATE, FC.TARGET, FC.TPOINT),
136
+ )
137
+ for wmodel in wmodels:
138
+ if wmodel.has_uv:
139
+ tdata.add(
140
+ FV.AMB_UV, uv, dims=(FC.STATE, FC.TARGET, FC.TPOINT, FC.XY)
141
+ )
142
+ break
143
+
144
+ # map rotor point results onto target points:
145
+ for v, d in amb_rotor_res.items():
146
+ if v not in [FV.WS, FV.WD, FV.U, FV.V, FV.UV]:
147
+ w = FV.var2amb.get(v, v)
148
+ tdata.add(
149
+ w,
150
+ self.map_rotor_results(
151
+ algo, mdata, fdata, tdata, v, d, rotor_weights
152
+ ),
153
+ dims=(FC.STATE, FC.TARGET, FC.TPOINT),
154
+ )
155
+
156
+ return tdata
157
+
158
+ def map_rotor_results(
159
+ self,
160
+ algo,
161
+ mdata,
162
+ fdata,
163
+ tdata,
164
+ variable,
165
+ rotor_res,
166
+ rotor_weights,
167
+ ):
168
+ """
169
+ Map ambient rotor point results onto target points.
170
+
171
+ Parameters
172
+ ----------
173
+ algo: foxes.core.Algorithm
174
+ The calculation algorithm
175
+ mdata: foxes.core.MData
176
+ The model data
177
+ fdata: foxes.core.FData
178
+ The farm data
179
+ tdata: foxes.core.TData
180
+ The target point data
181
+ variable: str
182
+ The variable name to map
183
+ rotor_res: numpy.ndarray
184
+ The results at rotor points, shape:
185
+ (n_states, n_turbines, n_rotor_points)
186
+ rotor_weights: numpy.ndarray
187
+ The rotor point weights, shape: (n_rotor_points,)
188
+
189
+ Returns
190
+ -------
191
+ res: numpy.ndarray
192
+ The mapped results at target points, shape:
193
+ (n_states, n_targets, n_tpoints)
194
+
195
+ """
196
+ if len(rotor_res.shape) > 2 and rotor_res.shape[:2] == (
197
+ tdata.n_states,
198
+ tdata.n_targets,
199
+ ):
200
+ q = np.zeros(
201
+ (tdata.n_states, tdata.n_targets, tdata.n_tpoints),
202
+ dtype=config.dtype_double,
203
+ )
204
+ if rotor_res.shape[2] == 1:
205
+ q[:] = rotor_res
206
+ else:
207
+ q[:] = np.einsum("str,r->st", rotor_res, rotor_weights)[:, :, None]
208
+ return q
209
+ else:
210
+ raise ValueError(
211
+ f"Partial wakes '{self.name}': Incompatible shape '{rotor_res.shape}' for variable '{variable}' in rotor results."
212
+ )
213
+
71
214
  def new_wake_deltas(self, algo, mdata, fdata, tdata, wmodel):
72
215
  """
73
216
  Creates new initial wake deltas, filled
@@ -77,11 +220,11 @@ class PartialWakesModel(Model):
77
220
  ----------
78
221
  algo: foxes.core.Algorithm
79
222
  The calculation algorithm
80
- mdata: foxes.core.Data
223
+ mdata: foxes.core.MData
81
224
  The model data
82
- fdata: foxes.core.Data
225
+ fdata: foxes.core.FData
83
226
  The farm data
84
- tdata: foxes.core.Data
227
+ tdata: foxes.core.TData
85
228
  The target point data
86
229
  wmodel: foxes.core.WakeModel
87
230
  The wake model
@@ -140,7 +283,6 @@ class PartialWakesModel(Model):
140
283
  mdata,
141
284
  fdata,
142
285
  tdata,
143
- amb_res,
144
286
  rpoint_weights,
145
287
  wake_deltas,
146
288
  wmodel,
@@ -162,11 +304,6 @@ class PartialWakesModel(Model):
162
304
  The farm data
163
305
  tdata: foxes.core.Data
164
306
  The target point data
165
- amb_res: dict
166
- The ambient results at the target points
167
- of all rotors. Key: variable name, value
168
- np.ndarray of shape:
169
- (n_states, n_turbines, n_rotor_points)
170
307
  rpoint_weights: numpy.ndarray
171
308
  The rotor point weights, shape: (n_rotor_points,)
172
309
  wake_deltas: dict
@@ -98,8 +98,7 @@ class PointDataModel(DataCalcModel):
98
98
 
99
99
  def run_calculation(self, algo, *data, out_vars, **calc_pars):
100
100
  """
101
- Starts the model calculation in parallel, via
102
- xarray's `apply_ufunc`.
101
+ Starts the model calculation in parallel.
103
102
 
104
103
  Typically this function is called by algorithms.
105
104
 
@@ -149,7 +148,7 @@ class PointDataModelList(PointDataModel):
149
148
 
150
149
  By using the PointDataModelList the models'
151
150
  `calculate` functions are called together
152
- under one common call of xarray's `apply_ufunc`.
151
+ under one common call by the engine.
153
152
 
154
153
  Attributes
155
154
  ----------
foxes/core/rotor_model.py CHANGED
@@ -60,6 +60,10 @@ class RotorModel(FarmDataModel):
60
60
  """
61
61
  if self.calc_vars is None:
62
62
  vrs = algo.states.output_point_vars(algo)
63
+ assert FV.WEIGHT not in vrs, (
64
+ f"Rotor '{self.name}': States '{algo.states.name}' output_point_vars contain '{FV.WEIGHT}', please remove"
65
+ )
66
+
63
67
  if FV.WS in vrs:
64
68
  self.calc_vars = [FV.REWS] + [v for v in vrs if v != FV.WS]
65
69
  else:
@@ -72,6 +76,9 @@ class RotorModel(FarmDataModel):
72
76
 
73
77
  self.calc_vars = sorted(self.calc_vars)
74
78
 
79
+ if FV.WEIGHT not in self.calc_vars:
80
+ self.calc_vars.append(FV.WEIGHT)
81
+
75
82
  return self.calc_vars
76
83
 
77
84
  @abstractmethod
@@ -174,9 +181,7 @@ class RotorModel(FarmDataModel):
174
181
  elif res.shape[1] == 1:
175
182
  fdata[v][:, downwind_index] = res[:, 0]
176
183
  else:
177
- raise ValueError(
178
- f"Rotor model '{self.name}': downwind_index is not None, but results shape for '{v}' has more than one turbine, {res.shape}"
179
- )
184
+ fdata[v, downwind_index] = res[:, downwind_index]
180
185
 
181
186
  def eval_rpoint_results(
182
187
  self,
@@ -184,7 +189,7 @@ class RotorModel(FarmDataModel):
184
189
  mdata,
185
190
  fdata,
186
191
  tdata,
187
- weights,
192
+ rpoint_weights,
188
193
  downwind_index=None,
189
194
  copy_to_ambient=False,
190
195
  ):
@@ -207,7 +212,7 @@ class RotorModel(FarmDataModel):
207
212
  The farm data
208
213
  tdata: foxes.core.TData
209
214
  The target point data
210
- weights: numpy.ndarray
215
+ rpoint_weights: numpy.ndarray
211
216
  The rotor point weights, shape: (n_rpoints,)
212
217
  downwind_index: int, optional
213
218
  The index in the downwind order
@@ -233,7 +238,7 @@ class RotorModel(FarmDataModel):
233
238
  wd = tdata[FV.WD]
234
239
  ws = tdata[FV.WS]
235
240
  uvp = wd2uv(wd, ws, axis=-1)
236
- uv = np.einsum("stpd,p->std", uvp, weights)
241
+ uv = np.einsum("stpd,p->std", uvp, rpoint_weights)
237
242
 
238
243
  wd = None
239
244
  vdone = []
@@ -243,7 +248,6 @@ class RotorModel(FarmDataModel):
243
248
  wd = uv2wd(uv, axis=-1)
244
249
  self._set_res(fdata, v, wd, downwind_index)
245
250
  vdone.append(v)
246
-
247
251
  elif v == FV.WS:
248
252
  ws = np.linalg.norm(uv, axis=-1)
249
253
  self._set_res(fdata, v, ws, downwind_index)
@@ -265,7 +269,7 @@ class RotorModel(FarmDataModel):
265
269
 
266
270
  for v in self.calc_vars:
267
271
  if v == FV.REWS:
268
- rews = np.maximum(np.einsum("stp,p->st", wsp, weights), 0.0)
272
+ rews = np.maximum(np.einsum("stp,p->st", wsp, rpoint_weights), 0.0)
269
273
  self._set_res(fdata, v, rews, downwind_index)
270
274
  del rews
271
275
  vdone.append(v)
@@ -278,12 +282,14 @@ class RotorModel(FarmDataModel):
278
282
  if uvp.shape[2] > 1:
279
283
  rews2 = np.sqrt(
280
284
  np.maximum(
281
- np.einsum("stp,p->st", np.sign(wsp) * wsp**2, weights),
285
+ np.einsum(
286
+ "stp,p->st", np.sign(wsp) * wsp**2, rpoint_weights
287
+ ),
282
288
  0.0,
283
289
  )
284
290
  )
285
291
  else:
286
- rews2 = np.sqrt(np.einsum("stp,p->st", wsp**2, weights))
292
+ rews2 = np.sqrt(np.einsum("stp,p->st", wsp**2, rpoint_weights))
287
293
  self._set_res(fdata, v, rews2, downwind_index)
288
294
  del rews2
289
295
  vdone.append(v)
@@ -295,10 +301,12 @@ class RotorModel(FarmDataModel):
295
301
  # turbine axis direction:
296
302
  if uvp.shape[2] > 1:
297
303
  rews3 = np.maximum(
298
- np.einsum("stp,p->st", wsp**3, weights), 0.0
304
+ np.einsum("stp,p->st", wsp**3, rpoint_weights), 0.0
299
305
  ) ** (1.0 / 3.0)
300
306
  else:
301
- rews3 = (np.einsum("stp,p->st", wsp**3, weights)) ** (1.0 / 3.0)
307
+ rews3 = (np.einsum("stp,p->st", wsp**3, rpoint_weights)) ** (
308
+ 1.0 / 3.0
309
+ )
302
310
  self._set_res(fdata, v, rews3, downwind_index)
303
311
  del rews3
304
312
  vdone.append(v)
@@ -307,8 +315,10 @@ class RotorModel(FarmDataModel):
307
315
  del uvp
308
316
 
309
317
  for v in self.calc_vars:
310
- if v not in vdone:
311
- res = np.einsum("stp,p->st", tdata[v], weights)
318
+ if v not in vdone and (
319
+ fdata[v].shape[1] > 1 or downwind_index is None or downwind_index == 0
320
+ ):
321
+ res = np.einsum("stp,p->st", tdata[v], rpoint_weights)
312
322
  self._set_res(fdata, v, res, downwind_index)
313
323
  if copy_to_ambient and v in FV.var2amb:
314
324
  fdata[FV.var2amb[v]] = fdata[v].copy()
@@ -319,10 +329,8 @@ class RotorModel(FarmDataModel):
319
329
  mdata,
320
330
  fdata,
321
331
  rpoints=None,
322
- weights=None,
323
- store_rpoints=False,
324
- store_rweights=False,
325
- store_amb_res=False,
332
+ rpoint_weights=None,
333
+ store=False,
326
334
  downwind_index=None,
327
335
  ):
328
336
  """
@@ -339,16 +347,11 @@ class RotorModel(FarmDataModel):
339
347
  rpoints: numpy.ndarray, optional
340
348
  The rotor points, or None for automatic for
341
349
  this rotor. Shape: (n_states, n_turbines, n_rpoints, 3)
342
- weights: numpy.ndarray, optional
350
+ rpoint_weights: numpy.ndarray, optional
343
351
  The rotor point weights, or None for automatic
344
352
  for this rotor. Shape: (n_rpoints,)
345
- store_rpoints: bool, optional
346
- Switch for storing rotor points to mdata
347
- store_rweights: bool, optional
348
- Switch for storing rotor point weights to mdata
349
- store_amb_res: bool, optional
350
- Switch for storing ambient rotor point reults as they
351
- come from the states to mdata
353
+ store: bool, optional
354
+ Flag for storing ambient rotor point results
352
355
  downwind_index: int, optional
353
356
  Only compute for index in the downwind order
354
357
 
@@ -364,18 +367,12 @@ class RotorModel(FarmDataModel):
364
367
  rpoints = mdata.get(
365
368
  FC.ROTOR_POINTS, self.get_rotor_points(algo, mdata, fdata)
366
369
  )
367
- if store_rpoints:
368
- algo.add_to_chunk_store(FC.ROTOR_POINTS, rpoints, mdata=mdata)
369
-
370
370
  if downwind_index is not None:
371
371
  rpoints = rpoints[:, downwind_index, None]
372
+ if rpoint_weights is None:
373
+ rpoint_weights = mdata.get_item(FC.TWEIGHTS, self.rotor_point_weights())
372
374
 
373
- if weights is None:
374
- weights = mdata.get(FC.TWEIGHTS, self.rotor_point_weights())
375
- if store_rweights:
376
- algo.add_to_chunk_store(FC.ROTOR_WEIGHTS, weights, mdata=mdata)
377
-
378
- tdata = TData.from_tpoints(rpoints, weights)
375
+ tdata = TData.from_tpoints(rpoints, rpoint_weights)
379
376
  svars = algo.states.output_point_vars(algo)
380
377
  for v in svars:
381
378
  tdata.add(
@@ -386,16 +383,23 @@ class RotorModel(FarmDataModel):
386
383
 
387
384
  sres = algo.states.calculate(algo, mdata, fdata, tdata)
388
385
  tdata.update(sres)
386
+ if FV.WEIGHT not in tdata:
387
+ raise KeyError(
388
+ f"Rotor '{self.name}': States '{algo.states.name}' failed to provide '{FV.WEIGHT}' in tdata"
389
+ )
389
390
 
390
- if store_amb_res:
391
- algo.add_to_chunk_store(FC.AMB_ROTOR_RES, sres.copy(), mdata=mdata)
391
+ if store:
392
+ algo.add_to_chunk_store(FC.ROTOR_POINTS, rpoints, mdata=mdata)
393
+ algo.add_to_chunk_store(FC.ROTOR_WEIGHTS, rpoint_weights, mdata=mdata)
394
+ algo.add_to_chunk_store(FC.AMB_ROTOR_RES, sres, mdata=mdata)
395
+ algo.add_to_chunk_store(FC.WEIGHT_RES, tdata[FV.WEIGHT], mdata=mdata)
392
396
 
393
397
  self.eval_rpoint_results(
394
398
  algo,
395
399
  mdata,
396
400
  fdata,
397
401
  tdata,
398
- weights,
402
+ rpoint_weights,
399
403
  downwind_index,
400
404
  copy_to_ambient=True,
401
405
  )
foxes/core/states.py CHANGED
@@ -2,7 +2,6 @@ from abc import abstractmethod
2
2
 
3
3
  from .point_data_model import PointDataModel, PointDataModelList
4
4
  from foxes.utils import new_instance
5
- import foxes.variables as FV
6
5
  import foxes.constants as FC
7
6
 
8
7
 
@@ -43,24 +42,6 @@ class States(PointDataModel):
43
42
  """
44
43
  return list(range(self.size()))
45
44
 
46
- @abstractmethod
47
- def weights(self, algo):
48
- """
49
- The statistical weights of all states.
50
-
51
- Parameters
52
- ----------
53
- algo: foxes.core.Algorithm
54
- The calculation algorithm
55
-
56
- Returns
57
- -------
58
- weights: numpy.ndarray
59
- The weights, shape: (n_states, n_turbines)
60
-
61
- """
62
- pass
63
-
64
45
  def reset(self, algo=None, states_sel=None, states_loc=None, verbosity=0):
65
46
  """
66
47
  Reset the states, optionally select states
@@ -106,19 +87,9 @@ class States(PointDataModel):
106
87
  if sinds is not None:
107
88
  idata["coords"][FC.STATE] = sinds
108
89
 
109
- weights = self.weights(algo)
110
- if len(weights.shape) != 2:
111
- raise ValueError(
112
- f"States '{self.name}': Wrong weights dimension, expecing ({FC.STATE}, {FC.TURBINE}), got shape {weights.shape}"
113
- )
114
- if weights.shape[1] != algo.n_turbines:
115
- raise ValueError(
116
- f"States '{self.name}': Wrong size of second axis dimension '{FC.TURBINE}': Expecting {algo.n_turbines}, got {weights.shape[1]}"
117
- )
118
- idata["data_vars"][FV.WEIGHT] = ((FC.STATE, FC.TURBINE), weights)
119
-
120
90
  return idata
121
91
 
92
+ @abstractmethod
122
93
  def output_point_vars(self, algo):
123
94
  """
124
95
  The variables which are being modified by the model.
@@ -134,7 +105,7 @@ class States(PointDataModel):
134
105
  The output variable names
135
106
 
136
107
  """
137
- return [FV.WS, FV.WD, FV.TI, FV.RHO]
108
+ pass
138
109
 
139
110
  def __add__(self, s):
140
111
  if isinstance(s, list):
@@ -142,7 +113,7 @@ class States(PointDataModel):
142
113
  elif isinstance(s, ExtendedStates):
143
114
  if s.states is not self:
144
115
  raise ValueError(
145
- f"Cannot add extended states, since not based on same states"
116
+ "Cannot add extended states, since not based on same states"
146
117
  )
147
118
  return ExtendedStates(self, s.pmodels.models[1:])
148
119
  else:
@@ -245,23 +216,6 @@ class ExtendedStates(States):
245
216
  """
246
217
  return self.states.index()
247
218
 
248
- def weights(self, algo):
249
- """
250
- The statistical weights of all states.
251
-
252
- Parameters
253
- ----------
254
- algo: foxes.core.Algorithm
255
- The calculation algorithm
256
-
257
- Returns
258
- -------
259
- weights: numpy.ndarray
260
- The weights, shape: (n_states, n_turbines)
261
-
262
- """
263
- return self.states.weights(algo)
264
-
265
219
  def output_point_vars(self, algo):
266
220
  """
267
221
  The variables which are being modified by the model.
@@ -313,7 +267,7 @@ class ExtendedStates(States):
313
267
  elif isinstance(m, ExtendedStates):
314
268
  if m.states is not self.states:
315
269
  raise ValueError(
316
- f"Cannot add extended states, since not based on same states"
270
+ "Cannot add extended states, since not based on same states"
317
271
  )
318
272
  return ExtendedStates(self.states, models + m.pmodels.models[1:])
319
273
  else:
foxes/core/turbine.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import numpy as np
2
+ from copy import deepcopy
2
3
 
3
4
 
4
5
  class Turbine:
@@ -71,7 +72,7 @@ class Turbine:
71
72
  self.index = index
72
73
  self.name = name
73
74
  self.xy = np.array(xy)
74
- self.models = turbine_models
75
+ self.models = deepcopy(turbine_models)
75
76
  self.D = D
76
77
  self.H = H
77
78
 
@@ -0,0 +1,130 @@
1
+ from abc import abstractmethod
2
+
3
+ from foxes.utils import new_instance
4
+ from .model import Model
5
+
6
+
7
+ class WakeDeflection(Model):
8
+ """
9
+ Abstract base class for wake deflection models.
10
+
11
+ :group: core
12
+
13
+ """
14
+
15
+ @property
16
+ def has_uv(self):
17
+ """
18
+ This model uses wind vector data
19
+
20
+ Returns
21
+ -------
22
+ hasuv: bool
23
+ Flag for wind vector data
24
+
25
+ """
26
+ return False
27
+
28
+ @abstractmethod
29
+ def calc_deflection(
30
+ self,
31
+ algo,
32
+ mdata,
33
+ fdata,
34
+ tdata,
35
+ downwind_index,
36
+ coos,
37
+ ):
38
+ """
39
+ Calculates the wake deflection.
40
+
41
+ This function optionally adds FC.WDEFL_ROT_ANGLE or
42
+ FC.WDEFL_DWS_FACTOR to the tdata.
43
+
44
+ Parameters
45
+ ----------
46
+ algo: foxes.core.Algorithm
47
+ The calculation algorithm
48
+ mdata: foxes.core.MData
49
+ The model data
50
+ fdata: foxes.core.FData
51
+ The farm data
52
+ tdata: foxes.core.TData
53
+ The target point data
54
+ downwind_index: int
55
+ The index of the wake causing turbine
56
+ in the downwind order
57
+ coos: numpy.ndarray
58
+ The wake frame coordinates of the evaluation
59
+ points, shape: (n_states, n_targets, n_tpoints, 3)
60
+
61
+ Returns
62
+ -------
63
+ coos: numpy.ndarray
64
+ The wake frame coordinates of the evaluation
65
+ points, shape: (n_states, n_targets, n_tpoints, 3)
66
+
67
+ """
68
+ pass
69
+
70
+ def get_yaw_alpha_seq(
71
+ self,
72
+ algo,
73
+ mdata,
74
+ fdata,
75
+ tdata,
76
+ downwind_index,
77
+ x,
78
+ ):
79
+ """
80
+ Computes sequential wind vector rotation angles.
81
+
82
+ Wind vector rotation angles are computed at the
83
+ current trace points due to a yawed rotor
84
+ for sequential runs.
85
+
86
+ Parameters
87
+ ----------
88
+ algo: foxes.core.Algorithm
89
+ The calculation algorithm
90
+ mdata: foxes.core.MData
91
+ The model data
92
+ fdata: foxes.core.FData
93
+ The farm data
94
+ tdata: foxes.core.TData
95
+ The target point data
96
+ downwind_index: int
97
+ The index of the wake causing turbine
98
+ in the downwind order
99
+ x: numpy.ndarray
100
+ The distance from the wake causing rotor
101
+ for the first n_times subsequent time steps,
102
+ shape: (n_times,)
103
+
104
+ Returns
105
+ -------
106
+ alpha: numpy.ndarray
107
+ The delta WD result at the x locations,
108
+ shape: (n_times,)
109
+
110
+ """
111
+ raise NotImplementedError(
112
+ f"Wake deflection '{self.name}' not implemented for sequential runs"
113
+ )
114
+
115
+ @classmethod
116
+ def new(cls, wframe_type, *args, **kwargs):
117
+ """
118
+ Run-time wake deflection model factory.
119
+
120
+ Parameters
121
+ ----------
122
+ wframe_type: str
123
+ The selected derived class name
124
+ args: tuple, optional
125
+ Additional parameters for constructor
126
+ kwargs: dict, optional
127
+ Additional parameters for constructor
128
+
129
+ """
130
+ return new_instance(cls, wframe_type, *args, **kwargs)