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
foxes/core/data.py CHANGED
@@ -28,9 +28,9 @@ class Data(Dict):
28
28
 
29
29
  def __init__(
30
30
  self,
31
- data,
32
- dims,
33
- loop_dims,
31
+ data={},
32
+ dims={},
33
+ loop_dims=[FC.STATE],
34
34
  states_i0=None,
35
35
  name="data",
36
36
  ):
@@ -153,13 +153,13 @@ class Data(Dict):
153
153
  # remove axes of size 1, added by dask for extra loop dimensions:
154
154
  if dims is not None:
155
155
  if len(dims) != len(data.shape):
156
- for li, l in enumerate(self.loop_dims):
157
- if data.shape[li] == 1 and (len(dims) < li + 1 or dims[li] != l):
156
+ for li, ld in enumerate(self.loop_dims):
157
+ if data.shape[li] == 1 and (len(dims) < li + 1 or dims[li] != ld):
158
158
  self[name] = np.squeeze(data, axis=li)
159
159
  for ci, c in enumerate(dims):
160
160
  if c not in self.sizes or self.sizes[c] == 1:
161
161
  self.sizes[c] = self[name].shape[ci]
162
- elif self[name].shape[ci] == 1:
162
+ elif c != FC.TARGET and self[name].shape[ci] == 1:
163
163
  pass
164
164
  elif self.sizes[c] != self[name].shape[ci]:
165
165
  raise ValueError(
@@ -240,9 +240,14 @@ class Data(Dict):
240
240
  )
241
241
  else:
242
242
  states_i0 = None
243
- return type(self)(
244
- data, dims, loop_dims=self.loop_dims, name=name, states_i0=states_i0
245
- )
243
+
244
+ cls = type(self)
245
+ if issubclass(cls, Data):
246
+ return cls(data, dims, name=name, states_i0=states_i0)
247
+ else:
248
+ return cls(
249
+ data, dims, loop_dims=self.loop_dims, name=name, states_i0=states_i0
250
+ )
246
251
 
247
252
  @classmethod
248
253
  def from_dataset(cls, ds, *args, callback=None, s_states=None, copy=True, **kwargs):
@@ -360,7 +365,7 @@ class FData(Data):
360
365
  Arguments for the base class
361
366
 
362
367
  """
363
- super().__init__(*args, name=name, **kwargs)
368
+ super().__init__(*args, loop_dims=[FC.STATE], name=name, **kwargs)
364
369
 
365
370
  def _run_entry_checks(self, name, data, dims):
366
371
  """Run entry checks on new data"""
@@ -453,7 +458,7 @@ class TData(Data):
453
458
  Arguments for the base class
454
459
 
455
460
  """
456
- super().__init__(*args, name=name, **kwargs)
461
+ super().__init__(*args, loop_dims=[FC.STATE, FC.TARGET], name=name, **kwargs)
457
462
 
458
463
  def _run_entry_checks(self, name, data, dims):
459
464
  """Run entry checks on new data"""
@@ -635,9 +640,7 @@ class TData(Data):
635
640
  for v in variables:
636
641
  data[v] = np.full_like(points[:, :, None, 0], np.nan)
637
642
  dims[v] = (FC.STATE, FC.TARGET, FC.TPOINT)
638
- return cls(
639
- data=data, dims=dims, loop_dims=[FC.STATE, FC.TARGET], name=name, **kwargs
640
- )
643
+ return cls(data=data, dims=dims, name=name, **kwargs)
641
644
 
642
645
  @classmethod
643
646
  def from_tpoints(
@@ -694,9 +697,7 @@ class TData(Data):
694
697
  for v in variables:
695
698
  data[v] = np.full_like(tpoints[..., 0], np.nan)
696
699
  dims[v] = (FC.STATE, FC.TARGET, FC.TPOINT)
697
- return cls(
698
- data=data, dims=dims, loop_dims=[FC.STATE, FC.TARGET], name=name, **kwargs
699
- )
700
+ return cls(data=data, dims=dims, name=name, **kwargs)
700
701
 
701
702
  @classmethod
702
703
  def from_dataset(
@@ -764,7 +765,7 @@ class TData(Data):
764
765
  FC.TPOINT,
765
766
  ):
766
767
  raise ValueError(
767
- f"Expecting coordinates '{ (FC.STATE, FC.TARGET, FC.TPOINT)}' at positions 0-2 for data variable '{v}', got {dims[v]}"
768
+ f"Expecting coordinates '{(FC.STATE, FC.TARGET, FC.TPOINT)}' at positions 0-2 for data variable '{v}', got {dims[v]}"
768
769
  )
769
770
  else:
770
771
  data[v] = d[:, s_targets]
foxes/core/engine.py CHANGED
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
4
4
  from tqdm import tqdm
5
5
  from xarray import Dataset
6
6
 
7
- from foxes.core import MData, FData, TData
7
+ from .data import MData, FData, TData
8
8
  from foxes.utils import new_instance
9
9
  from foxes.config import config
10
10
  import foxes.constants as FC
@@ -73,7 +73,7 @@ class Engine(ABC):
73
73
 
74
74
  def __enter__(self):
75
75
  if self.__entered:
76
- raise ValueError(f"Enter called for already entered engine")
76
+ raise ValueError("Enter called for already entered engine")
77
77
  self.__entered = True
78
78
  if not self.initialized:
79
79
  self.initialize()
@@ -81,7 +81,7 @@ class Engine(ABC):
81
81
 
82
82
  def __exit__(self, *exit_args):
83
83
  if not self.__entered:
84
- raise ValueError(f"Exit called for not entered engine")
84
+ raise ValueError("Exit called for not entered engine")
85
85
  self.__entered = False
86
86
  if self.initialized:
87
87
  self.finalize(*exit_args)
@@ -127,7 +127,6 @@ class Engine(ABC):
127
127
  raise ValueError(
128
128
  f"Cannot initialize engine '{type(self).__name__}', since engine already set to '{type(get_engine()).__name__}'"
129
129
  )
130
- global __global_engine_data__
131
130
  __global_engine_data__["engine"] = self
132
131
  self.__initialized = True
133
132
 
@@ -148,7 +147,6 @@ class Engine(ABC):
148
147
  if self.entered:
149
148
  self.__exit__(type, value, traceback)
150
149
  elif self.initialized:
151
- global __global_engine_data__
152
150
  __global_engine_data__["engine"] = None
153
151
  self.__initialized = False
154
152
 
@@ -304,9 +302,9 @@ class Engine(ABC):
304
302
  chunk_sizes_targets[-extra:] += 1
305
303
 
306
304
  s = np.sum(chunk_sizes_targets)
307
- assert (
308
- s == n_targets
309
- ), f"Targets count mismatch: Expecting {n_targets}, chunks sum is {s}. Chunks: {[int(c) for c in chunk_sizes_targets]}"
305
+ assert s == n_targets, (
306
+ f"Targets count mismatch: Expecting {n_targets}, chunks sum is {s}. Chunks: {[int(c) for c in chunk_sizes_targets]}"
307
+ )
310
308
 
311
309
  chunk_sizes_states = np.full(n_chunks_states, chunk_size_states)
312
310
  extra = n_states - n_chunks_states * chunk_size_states
@@ -314,9 +312,9 @@ class Engine(ABC):
314
312
  chunk_sizes_states[-extra:] += 1
315
313
 
316
314
  s = np.sum(chunk_sizes_states)
317
- assert (
318
- s == n_states
319
- ), f"States count mismatch: Expecting {n_states}, chunks sum is {s}. Chunks: {[int(c) for c in chunk_sizes_states]}"
315
+ assert s == n_states, (
316
+ f"States count mismatch: Expecting {n_states}, chunks sum is {s}. Chunks: {[int(c) for c in chunk_sizes_states]}"
317
+ )
320
318
 
321
319
  return chunk_sizes_states, chunk_sizes_targets
322
320
 
@@ -389,7 +387,6 @@ class Engine(ABC):
389
387
  mdata=mdata,
390
388
  s_states=s_states,
391
389
  callback=cb,
392
- loop_dims=[FC.STATE],
393
390
  states_i0=i0_states,
394
391
  copy=True,
395
392
  )
@@ -413,7 +410,6 @@ class Engine(ABC):
413
410
  s_states=s_states,
414
411
  s_targets=s_targets,
415
412
  callback=cb,
416
- loop_dims=[FC.STATE, FC.TARGET],
417
413
  states_i0=i0_states,
418
414
  copy=True,
419
415
  )
@@ -171,7 +171,7 @@ class FarmController(FarmDataModel):
171
171
  for ti, t in enumerate(algo.farm.turbines):
172
172
  if tmis[ti] != len(models[ti]):
173
173
  raise ValueError(
174
- f"Turbine {ti}, {t.name}: Could not find turbine model order that includes all {mtype} turbine models, missing {t.models[tmis[ti]:]}"
174
+ f"Turbine {ti}, {t.name}: Could not find turbine model order that includes all {mtype} turbine models, missing {t.models[tmis[ti] :]}"
175
175
  )
176
176
 
177
177
  return [m.name for m in tmodels], tmsels
@@ -253,7 +253,7 @@ class FarmController(FarmDataModel):
253
253
  prer = m.pre_rotor
254
254
  elif not prer and m.pre_rotor:
255
255
  raise ValueError(
256
- f"Turbine {ti}, {t.name}: Model is classified as pre-rotor, but following the post-rotor model '{t.models[mi-1]}'"
256
+ f"Turbine {ti}, {t.name}: Model is classified as pre-rotor, but following the post-rotor model '{t.models[mi - 1]}'"
257
257
  )
258
258
  if m.pre_rotor:
259
259
  prer_models[ti].append(m)
@@ -91,7 +91,6 @@ class GroundModel(Model):
91
91
  mdata,
92
92
  fdata,
93
93
  tdata,
94
- amb_res,
95
94
  rpoint_weights,
96
95
  wake_deltas,
97
96
  wmodel,
@@ -114,11 +113,6 @@ class GroundModel(Model):
114
113
  The farm data
115
114
  tdata: foxes.core.Data
116
115
  The target point data
117
- amb_res: dict
118
- The ambient results at the target points
119
- of all rotors. Key: variable name, value
120
- np.ndarray of shape:
121
- (n_states, n_turbines, n_rotor_points)
122
116
  rpoint_weights: numpy.ndarray
123
117
  The rotor point weights, shape: (n_rotor_points,)
124
118
  wake_deltas: dict
@@ -143,7 +137,6 @@ class GroundModel(Model):
143
137
  mdata,
144
138
  fdata,
145
139
  tdata,
146
- amb_res,
147
140
  rpoint_weights,
148
141
  wake_deltas,
149
142
  wmodel,
@@ -226,7 +219,7 @@ class GroundModel(Model):
226
219
  algo,
227
220
  mdata,
228
221
  fdata,
229
- amb_results,
222
+ tdata,
230
223
  wake_deltas,
231
224
  wmodel,
232
225
  ):
@@ -243,17 +236,15 @@ class GroundModel(Model):
243
236
  The model data
244
237
  fdata: foxes.core.FData
245
238
  The farm data
246
- amb_results: dict
247
- The ambient results, key: variable name str,
248
- values: numpy.ndarray with shape
249
- (n_states, n_targets, n_tpoints)
239
+ tdata: foxes.core.TData
240
+ The target point data
250
241
  wake_deltas: dict
251
242
  The wake deltas object at the selected target
252
243
  turbines. Key: variable str, value: numpy.ndarray
253
244
  with shape (n_states, n_targets, n_tpoints)
254
245
 
255
246
  """
256
- wmodel.finalize_wake_deltas(algo, mdata, fdata, amb_results, wake_deltas)
247
+ wmodel.finalize_wake_deltas(algo, mdata, fdata, tdata, wake_deltas)
257
248
 
258
249
  @classmethod
259
250
  def new(cls, ground_type, *args, **kwargs):
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,9 +60,9 @@ class RotorModel(FarmDataModel):
60
60
  """
61
61
  if self.calc_vars is None:
62
62
  vrs = algo.states.output_point_vars(algo)
63
- assert (
64
- FV.WEIGHT not in vrs
65
- ), f"Rotor '{self.name}': States '{algo.states.name}' output_point_vars contain '{FV.WEIGHT}', please remove"
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
66
 
67
67
  if FV.WS in vrs:
68
68
  self.calc_vars = [FV.REWS] + [v for v in vrs if v != FV.WS]
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
 
@@ -114,7 +113,7 @@ class States(PointDataModel):
114
113
  elif isinstance(s, ExtendedStates):
115
114
  if s.states is not self:
116
115
  raise ValueError(
117
- f"Cannot add extended states, since not based on same states"
116
+ "Cannot add extended states, since not based on same states"
118
117
  )
119
118
  return ExtendedStates(self, s.pmodels.models[1:])
120
119
  else:
@@ -268,7 +267,7 @@ class ExtendedStates(States):
268
267
  elif isinstance(m, ExtendedStates):
269
268
  if m.states is not self.states:
270
269
  raise ValueError(
271
- f"Cannot add extended states, since not based on same states"
270
+ "Cannot add extended states, since not based on same states"
272
271
  )
273
272
  return ExtendedStates(self.states, models + m.pmodels.models[1:])
274
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)