foxes 0.6.2__py3-none-any.whl → 0.7.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of foxes might be problematic. Click here for more details.

Files changed (120) hide show
  1. foxes/VERSION +1 -1
  2. foxes/algorithms/downwind/downwind.py +131 -65
  3. foxes/algorithms/downwind/models/__init__.py +2 -1
  4. foxes/algorithms/downwind/models/farm_wakes_calc.py +87 -55
  5. foxes/algorithms/downwind/models/init_farm_data.py +134 -0
  6. foxes/algorithms/downwind/models/point_wakes_calc.py +54 -65
  7. foxes/algorithms/downwind/models/{calc_order.py → reorder_farm_output.py} +28 -26
  8. foxes/algorithms/iterative/iterative.py +100 -51
  9. foxes/algorithms/iterative/models/convergence.py +3 -3
  10. foxes/algorithms/iterative/models/farm_wakes_calc.py +55 -48
  11. foxes/algorithms/sequential/models/seq_state.py +7 -6
  12. foxes/algorithms/sequential/sequential.py +81 -44
  13. foxes/constants.py +33 -18
  14. foxes/core/__init__.py +2 -2
  15. foxes/core/algorithm.py +31 -12
  16. foxes/core/data.py +335 -41
  17. foxes/core/data_calc_model.py +27 -23
  18. foxes/core/farm_controller.py +27 -28
  19. foxes/core/farm_data_model.py +26 -4
  20. foxes/core/model.py +186 -129
  21. foxes/core/partial_wakes_model.py +84 -81
  22. foxes/core/point_data_model.py +51 -18
  23. foxes/core/rotor_model.py +59 -77
  24. foxes/core/states.py +6 -6
  25. foxes/core/turbine_model.py +4 -4
  26. foxes/core/turbine_type.py +24 -0
  27. foxes/core/vertical_profile.py +3 -3
  28. foxes/core/wake_frame.py +91 -50
  29. foxes/core/wake_model.py +74 -43
  30. foxes/core/wake_superposition.py +29 -26
  31. foxes/input/farm_layout/from_random.py +10 -9
  32. foxes/input/states/create/random_timeseries.py +17 -19
  33. foxes/input/states/field_data_nc.py +12 -8
  34. foxes/input/states/multi_height.py +24 -14
  35. foxes/input/states/scan_ws.py +13 -17
  36. foxes/input/states/single.py +28 -20
  37. foxes/input/states/states_table.py +22 -18
  38. foxes/models/axial_induction_models/betz.py +1 -1
  39. foxes/models/farm_models/turbine2farm.py +2 -2
  40. foxes/models/model_book.py +39 -14
  41. foxes/models/partial_wakes/__init__.py +2 -3
  42. foxes/models/partial_wakes/axiwake.py +73 -200
  43. foxes/models/partial_wakes/centre.py +11 -79
  44. foxes/models/partial_wakes/grid.py +7 -63
  45. foxes/models/partial_wakes/rotor_points.py +53 -147
  46. foxes/models/partial_wakes/segregated.py +158 -0
  47. foxes/models/partial_wakes/top_hat.py +88 -196
  48. foxes/models/point_models/set_uniform_data.py +4 -4
  49. foxes/models/point_models/tke2ti.py +4 -4
  50. foxes/models/point_models/wake_deltas.py +4 -4
  51. foxes/models/rotor_models/centre.py +15 -19
  52. foxes/models/rotor_models/grid.py +2 -1
  53. foxes/models/rotor_models/levels.py +2 -1
  54. foxes/models/turbine_models/__init__.py +0 -1
  55. foxes/models/turbine_models/calculator.py +11 -7
  56. foxes/models/turbine_models/kTI_model.py +13 -11
  57. foxes/models/turbine_models/lookup_table.py +22 -9
  58. foxes/models/turbine_models/power_mask.py +81 -51
  59. foxes/models/turbine_models/rotor_centre_calc.py +17 -20
  60. foxes/models/turbine_models/sector_management.py +5 -6
  61. foxes/models/turbine_models/set_farm_vars.py +49 -20
  62. foxes/models/turbine_models/table_factors.py +5 -5
  63. foxes/models/turbine_models/thrust2ct.py +9 -5
  64. foxes/models/turbine_models/yaw2yawm.py +7 -13
  65. foxes/models/turbine_models/yawm2yaw.py +7 -11
  66. foxes/models/turbine_types/PCt_file.py +84 -3
  67. foxes/models/turbine_types/PCt_from_two.py +7 -3
  68. foxes/models/turbine_types/null_type.py +2 -2
  69. foxes/models/turbine_types/wsrho2PCt_from_two.py +2 -2
  70. foxes/models/turbine_types/wsti2PCt_from_two.py +6 -2
  71. foxes/models/wake_frames/farm_order.py +26 -22
  72. foxes/models/wake_frames/rotor_wd.py +32 -31
  73. foxes/models/wake_frames/seq_dynamic_wakes.py +112 -64
  74. foxes/models/wake_frames/streamlines.py +51 -47
  75. foxes/models/wake_frames/timelines.py +59 -47
  76. foxes/models/wake_frames/yawed_wakes.py +63 -40
  77. foxes/models/wake_models/axisymmetric.py +31 -35
  78. foxes/models/wake_models/dist_sliced.py +50 -56
  79. foxes/models/wake_models/gaussian.py +33 -35
  80. foxes/models/wake_models/induction/rankine_half_body.py +79 -87
  81. foxes/models/wake_models/induction/rathmann.py +56 -63
  82. foxes/models/wake_models/induction/self_similar.py +59 -62
  83. foxes/models/wake_models/ti/crespo_hernandez.py +83 -74
  84. foxes/models/wake_models/ti/iec_ti.py +65 -75
  85. foxes/models/wake_models/top_hat.py +60 -69
  86. foxes/models/wake_models/wake_mirror.py +49 -54
  87. foxes/models/wake_models/wind/bastankhah14.py +44 -66
  88. foxes/models/wake_models/wind/bastankhah16.py +84 -111
  89. foxes/models/wake_models/wind/jensen.py +67 -89
  90. foxes/models/wake_models/wind/turbopark.py +93 -133
  91. foxes/models/wake_superpositions/ti_linear.py +33 -27
  92. foxes/models/wake_superpositions/ti_max.py +33 -27
  93. foxes/models/wake_superpositions/ti_pow.py +35 -27
  94. foxes/models/wake_superpositions/ti_quadratic.py +33 -27
  95. foxes/models/wake_superpositions/ws_linear.py +39 -32
  96. foxes/models/wake_superpositions/ws_max.py +40 -33
  97. foxes/models/wake_superpositions/ws_pow.py +39 -32
  98. foxes/models/wake_superpositions/ws_product.py +35 -28
  99. foxes/models/wake_superpositions/ws_quadratic.py +39 -32
  100. foxes/opt/problems/layout/farm_layout.py +38 -97
  101. foxes/output/__init__.py +1 -0
  102. foxes/output/flow_plots_2d/flow_plots.py +2 -0
  103. foxes/output/rose_plot.py +3 -3
  104. foxes/output/rotor_point_plots.py +117 -0
  105. foxes/output/turbine_type_curves.py +2 -2
  106. foxes/utils/__init__.py +1 -1
  107. foxes/utils/load.py +29 -0
  108. foxes/utils/random_xy.py +11 -10
  109. foxes/utils/runners/runners.py +3 -4
  110. foxes/utils/windrose_plot.py +1 -1
  111. foxes/variables.py +10 -0
  112. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/METADATA +13 -7
  113. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/RECORD +117 -117
  114. foxes/models/partial_wakes/distsliced.py +0 -322
  115. foxes/models/partial_wakes/mapped.py +0 -252
  116. foxes/models/turbine_models/set_XYHD.py +0 -130
  117. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/LICENSE +0 -0
  118. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/WHEEL +0 -0
  119. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/top_level.txt +0 -0
  120. {foxes-0.6.2.dist-info → foxes-0.7.0.2.dist-info}/zip-safe +0 -0
foxes/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.2
1
+ 0.7.0.2
@@ -18,14 +18,16 @@ class Downwind(Algorithm):
18
18
  ----------
19
19
  states: foxes.core.States
20
20
  The ambient states
21
- wake_models: list of foxes.core.WakeModel
22
- The wake models, applied to all turbines
21
+ wake_models: dict
22
+ The wake models. Key: wake model name,
23
+ value: foxes.core.WakeModel
23
24
  rotor_model: foxes.core.RotorModel
24
25
  The rotor model, for all turbines
25
26
  wake_frame: foxes.core.WakeFrame
26
27
  The wake frame
27
- partial_wakes_model: foxes.core.PartialWakesModel
28
- The partial wakes model
28
+ partial_wakes: dict
29
+ The partial wakes mapping. Key: wake model name,
30
+ value: foxes.core.PartialWakesModel
29
31
  farm_controller: foxes.core.FarmController
30
32
  The farm controller
31
33
  n_states: int
@@ -35,36 +37,38 @@ class Downwind(Algorithm):
35
37
 
36
38
  """
37
39
 
38
- @classmethod
39
- def get_model(cls, name):
40
- """
41
- Get the algorithm specific model
42
-
43
- Parameters
44
- ----------
45
- name: str
46
- The model name
47
-
48
- Returns
49
- -------
50
- model: foxes.core.model
51
- The model
52
-
53
- """
54
- return getattr(mdls, name)
40
+ DEFAULT_FARM_OUTPUTS = [
41
+ FV.X,
42
+ FV.Y,
43
+ FV.H,
44
+ FV.D,
45
+ FV.AMB_WD,
46
+ FV.AMB_REWS,
47
+ FV.AMB_TI,
48
+ FV.AMB_RHO,
49
+ FV.AMB_P,
50
+ FV.WD,
51
+ FV.REWS,
52
+ FV.YAW,
53
+ FV.TI,
54
+ FV.CT,
55
+ FV.P,
56
+ FV.ORDER,
57
+ FV.WEIGHT,
58
+ ]
55
59
 
56
60
  def __init__(
57
61
  self,
58
- mbook,
59
62
  farm,
60
63
  states,
61
64
  wake_models,
62
65
  rotor_model="centre",
63
66
  wake_frame="rotor_wd",
64
- partial_wakes_model="auto",
67
+ partial_wakes=None,
65
68
  farm_controller="basic_ctrl",
66
- chunks={FC.STATE: 1000, FC.POINT: 10000},
69
+ chunks={FC.STATE: 1000, FC.POINT: 4000},
67
70
  wake_mirrors={},
71
+ mbook=None,
68
72
  dbook=None,
69
73
  verbosity=1,
70
74
  ):
@@ -73,8 +77,6 @@ class Downwind(Algorithm):
73
77
 
74
78
  Parameters
75
79
  ----------
76
- mbook: foxes.ModelBook
77
- The model book
78
80
  farm: foxes.WindFarm
79
81
  The wind farm
80
82
  states: foxes.core.States
@@ -88,9 +90,9 @@ class Downwind(Algorithm):
88
90
  wake_frame: str
89
91
  The wake frame. Will be looked up in the
90
92
  model book
91
- partial_wakes_model: str
92
- The partial wakes model. Will be
93
- looked up in the model book
93
+ partial_wakes: dict, list or str, optional
94
+ The partial wakes mapping. Key: wake model name,
95
+ value: partial wake model name
94
96
  farm_controller: str
95
97
  The farm controller. Will be
96
98
  looked up in the model book
@@ -100,12 +102,17 @@ class Downwind(Algorithm):
100
102
  wake_mirrors: dict
101
103
  Switch on wake mirrors for wake models.
102
104
  Key: wake model name, value: list of heights
105
+ mbook: foxes.ModelBook, optional
106
+ The model book
103
107
  dbook: foxes.DataBook, optional
104
108
  The data book, or None for default
105
109
  verbosity: int
106
110
  The verbosity level, 0 means silent
107
111
 
108
112
  """
113
+ if mbook is None:
114
+ mbook = fm.ModelBook()
115
+
109
116
  super().__init__(mbook, farm, chunks, verbosity, dbook)
110
117
 
111
118
  self.states = states
@@ -115,13 +122,10 @@ class Downwind(Algorithm):
115
122
  self.rotor_model = self.mbook.rotor_models[rotor_model]
116
123
  self.rotor_model.name = rotor_model
117
124
 
118
- self.partial_wakes_model = self.mbook.partial_wakes[partial_wakes_model]
119
- self.partial_wakes_model.name = partial_wakes_model
120
-
121
125
  self.wake_frame = self.mbook.wake_frames[wake_frame]
122
126
  self.wake_frame.name = wake_frame
123
127
 
124
- self.wake_models = []
128
+ self.wake_models = {}
125
129
  for w in wake_models:
126
130
  m = self.mbook.wake_models[w]
127
131
  m.name = w
@@ -135,14 +139,63 @@ class Downwind(Algorithm):
135
139
  f"Wake model '{w}' is mirrored with heights {m.heights}, cannot apply WakeMirror with heights {hts}"
136
140
  )
137
141
  else:
138
- self.wake_models.append(fm.wake_models.WakeMirror(m, heights=hts))
142
+ self.wake_models[w] = fm.wake_models.WakeMirror(m, heights=hts)
139
143
 
140
144
  else:
141
- self.wake_models.append(m)
145
+ self.wake_models[w] = m
146
+
147
+ self.partial_wakes = {}
148
+ if partial_wakes is None:
149
+ partial_wakes = {}
150
+ if isinstance(partial_wakes, list) and len(partial_wakes) == 1:
151
+ partial_wakes = partial_wakes[0]
152
+ if isinstance(partial_wakes, str):
153
+ for w in wake_models:
154
+ try:
155
+ pw = partial_wakes
156
+ except TypeError:
157
+ pw = mbook.default_partial_wakes(self.wake_models[w])
158
+ self.partial_wakes[w] = self.mbook.partial_wakes[pw]
159
+ self.partial_wakes[w].name = pw
160
+ elif isinstance(partial_wakes, list):
161
+ for i, w in enumerate(wake_models):
162
+ if i >= len(partial_wakes):
163
+ raise IndexError(
164
+ f"Not enough partial wakes in list {partial_wakes}, expecting {len(wake_models)}"
165
+ )
166
+ pw = partial_wakes[i]
167
+ self.partial_wakes[w] = self.mbook.partial_wakes[pw]
168
+ self.partial_wakes[w].name = pw
169
+ else:
170
+ for w in wake_models:
171
+ if w in partial_wakes:
172
+ pw = partial_wakes[w]
173
+ else:
174
+ pw = mbook.default_partial_wakes(self.wake_models[w])
175
+ self.partial_wakes[w] = self.mbook.partial_wakes[pw]
176
+ self.partial_wakes[w].name = pw
142
177
 
143
178
  self.farm_controller = self.mbook.farm_controllers[farm_controller]
144
179
  self.farm_controller.name = farm_controller
145
180
 
181
+ @classmethod
182
+ def get_model(cls, name):
183
+ """
184
+ Get the algorithm specific model
185
+
186
+ Parameters
187
+ ----------
188
+ name: str
189
+ The model name
190
+
191
+ Returns
192
+ -------
193
+ model: foxes.core.model
194
+ The model
195
+
196
+ """
197
+ return getattr(mdls, name)
198
+
146
199
  def _print_deco(self, func_name, n_points=None):
147
200
  """
148
201
  Helper function for printing model names
@@ -160,13 +213,16 @@ class Downwind(Algorithm):
160
213
  print(f" states : {self.states}")
161
214
  print(f" rotor : {self.rotor_model}")
162
215
  print(f" controller: {self.farm_controller}")
163
- print(f" partialwks: {self.partial_wakes_model}")
164
216
  print(f" wake frame: {self.wake_frame}")
165
217
  print(deco)
166
218
  print(f" wakes:")
167
- for i, w in enumerate(self.wake_models):
219
+ for i, w in enumerate(self.wake_models.values()):
168
220
  print(f" {i}) {w}")
169
221
  print(deco)
222
+ print(f" partial wakes:")
223
+ for i, (w, p) in enumerate(self.partial_wakes.items()):
224
+ print(f" {i}) {w}: {p}")
225
+ print(deco)
170
226
  print(f" turbine models:")
171
227
  for i, m in enumerate(self.farm_controller.pre_rotor_models.models):
172
228
  print(f" {i}) {m} [pre-rotor]")
@@ -226,8 +282,9 @@ class Downwind(Algorithm):
226
282
  self.rotor_model,
227
283
  self.farm_controller,
228
284
  self.wake_frame,
229
- self.partial_wakes_model,
230
- ] + self.wake_models
285
+ ]
286
+ mdls += list(self.wake_models.values())
287
+ mdls += list(self.partial_wakes.values())
231
288
 
232
289
  return mdls
233
290
 
@@ -245,6 +302,7 @@ class Downwind(Algorithm):
245
302
 
246
303
  def _collect_farm_models(
247
304
  self,
305
+ outputs,
248
306
  calc_parameters,
249
307
  ambient,
250
308
  ):
@@ -253,73 +311,68 @@ class Downwind(Algorithm):
253
311
  """
254
312
  # prepare:
255
313
  calc_pars = []
256
- t2f = fm.farm_models.Turbine2FarmModel
257
314
  mlist = FarmDataModelList(models=[])
258
315
  mlist.name = f"{self.name}_calc"
259
316
 
260
- # 0) set XHYD:
261
- m = fm.turbine_models.SetXYHD()
262
- mlist.models.append(t2f(m))
263
- calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
264
-
265
- # 1) run pre-rotor turbine models via farm controller:
317
+ # 0) run pre-rotor turbine models via farm controller:
266
318
  mlist.models.append(self.farm_controller)
267
319
  calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
268
320
  calc_pars[-1]["pre_rotor"] = True
269
321
 
270
- # 2) calculate yaw from wind direction at rotor centre:
271
- mlist.models.append(fm.rotor_models.CentreRotor(calc_vars=[FV.WD, FV.YAW]))
272
- mlist.models[-1].name = "calc_yaw_" + mlist.models[-1].name
322
+ # 1) set initial data:
323
+ mlist.models.append(self.get_model("InitFarmData")())
273
324
  calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
274
325
 
275
- # 3) calculate ambient rotor results:
326
+ # 2) calculate ambient rotor results:
276
327
  mlist.models.append(self.rotor_model)
277
328
  calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
278
329
  calc_pars[-1].update(
279
330
  {"store_rpoints": True, "store_rweights": True, "store_amb_res": True}
280
331
  )
281
332
 
282
- # 4) calculate turbine order:
283
- mlist.models.append(self.get_model("CalcOrder")())
284
- calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
285
-
286
- # 5) run post-rotor turbine models via farm controller:
333
+ # 3) run post-rotor turbine models via farm controller:
287
334
  mlist.models.append(self.farm_controller)
288
335
  calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
289
336
  calc_pars[-1]["pre_rotor"] = False
290
337
 
291
- # 6) copy results to ambient, requires self.farm_vars:
338
+ # 4) copy results to ambient, requires self.farm_vars:
292
339
  self.farm_vars = mlist.output_farm_vars(self)
293
340
  mlist.models.append(self.get_model("SetAmbFarmResults")())
294
341
  calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
295
342
 
296
- # 7) calculate wake effects:
343
+ # 5) calculate wake effects:
297
344
  if not ambient:
298
345
  mlist.models.append(self.get_model("FarmWakesCalculation")())
299
346
  calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
300
347
 
348
+ # 6) reorder back to state-turbine dimensions:
349
+ if outputs != False:
350
+ mlist.models.append(self.get_model("ReorderFarmOutput")(outputs))
351
+ calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
352
+
301
353
  return mlist, calc_pars
302
354
 
303
355
  def _calc_farm_vars(self, mlist):
304
356
  """Helper function that gathers the farm variables"""
305
357
  self.farm_vars = sorted(list(set([FV.WEIGHT] + mlist.output_farm_vars(self))))
306
358
 
307
- def _run_farm_calc(self, mlist, *data, **kwargs):
359
+ def _run_farm_calc(self, mlist, *data, outputs=None, **kwargs):
308
360
  """Helper function for running the main farm calculation"""
309
361
  self.print(
310
362
  f"\nCalculating {self.n_states} states for {self.n_turbines} turbines"
311
363
  )
312
- farm_results = mlist.run_calculation(
313
- self, *data, out_vars=self.farm_vars, **kwargs
314
- )
364
+ out_vars = self.farm_vars if outputs is None else outputs
365
+ farm_results = mlist.run_calculation(self, *data, out_vars=out_vars, **kwargs)
315
366
  farm_results[FC.TNAME] = ((FC.TURBINE,), self.farm.turbine_names)
316
- if FV.ORDER in farm_results:
317
- farm_results[FV.ORDER] = farm_results[FV.ORDER].astype(FC.ITYPE)
367
+ for v in [FV.ORDER, FV.ORDER_SSEL, FV.ORDER_INV]:
368
+ if v in farm_results:
369
+ farm_results[v] = farm_results[v].astype(FC.ITYPE)
318
370
 
319
371
  return farm_results
320
372
 
321
373
  def calc_farm(
322
374
  self,
375
+ outputs=None,
323
376
  calc_parameters={},
324
377
  persist=True,
325
378
  finalize=True,
@@ -335,6 +388,8 @@ class Downwind(Algorithm):
335
388
  calc_parameters: dict
336
389
  Parameters for model calculation.
337
390
  Key: model name str, value: parameter dict
391
+ outputs: list of str, optional
392
+ The output variables, or None for defaults
338
393
  persist: bool
339
394
  Switch for forcing dask to load all model data
340
395
  into memory
@@ -362,7 +417,9 @@ class Downwind(Algorithm):
362
417
  self._print_deco("calc_farm")
363
418
 
364
419
  # collect models:
365
- mlist, calc_pars = self._collect_farm_models(calc_parameters, ambient)
420
+ if outputs == "default":
421
+ outputs = self.DEFAULT_FARM_OUTPUTS
422
+ mlist, calc_pars = self._collect_farm_models(outputs, calc_parameters, ambient)
366
423
 
367
424
  # initialize models:
368
425
  if not mlist.initialized:
@@ -370,12 +427,19 @@ class Downwind(Algorithm):
370
427
  self._calc_farm_vars(mlist)
371
428
  self._print_model_oder(mlist, calc_pars)
372
429
 
430
+ # update outputs:
431
+ if outputs is None:
432
+ outputs = self.farm_vars
433
+ else:
434
+ outputs = sorted(list(set(outputs).intersection(self.farm_vars)))
435
+
373
436
  # get input model data:
374
437
  models_data = self.get_models_data()
375
438
  if persist:
376
439
  models_data = models_data.persist()
377
440
  self.print("\nInput data:\n\n", models_data, "\n")
378
- self.print(f"\nOutput farm variables:", ", ".join(self.farm_vars))
441
+ self.print(f"\nFarm variables:", ", ".join(self.farm_vars))
442
+ self.print(f"\nOutput variables:", ", ".join(outputs))
379
443
  self.print(f"\nChunks: {self.chunks}\n")
380
444
 
381
445
  # run main calculation:
@@ -383,6 +447,7 @@ class Downwind(Algorithm):
383
447
  mlist,
384
448
  models_data,
385
449
  parameters=calc_pars,
450
+ outputs=outputs,
386
451
  **kwargs,
387
452
  )
388
453
  del models_data
@@ -559,6 +624,7 @@ class Downwind(Algorithm):
559
624
  self.print(
560
625
  f"Calculating {len(ovars)} variables at {points.shape[1]} points in {self.n_states} states"
561
626
  )
627
+
562
628
  point_results = mlist.run_calculation(
563
629
  self,
564
630
  models_data,
@@ -2,4 +2,5 @@ from .set_amb_farm_results import SetAmbFarmResults
2
2
  from .set_amb_point_results import SetAmbPointResults
3
3
  from .farm_wakes_calc import FarmWakesCalculation
4
4
  from .point_wakes_calc import PointWakesCalculation
5
- from .calc_order import CalcOrder
5
+ from .init_farm_data import InitFarmData
6
+ from .reorder_farm_output import ReorderFarmOutput
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
+ from copy import deepcopy
2
3
 
3
- import foxes.variables as FV
4
- from foxes.core import FarmDataModel
4
+ from foxes.core import FarmDataModel, TData
5
5
 
6
6
 
7
7
  class FarmWakesCalculation(FarmDataModel):
@@ -12,12 +12,6 @@ class FarmWakesCalculation(FarmDataModel):
12
12
 
13
13
  """
14
14
 
15
- def __init__(self):
16
- """
17
- Constructor.
18
- """
19
- super().__init__()
20
-
21
15
  def output_farm_vars(self, algo):
22
16
  """
23
17
  The variables which are being modified by the model.
@@ -38,33 +32,6 @@ class FarmWakesCalculation(FarmDataModel):
38
32
  ) + algo.farm_controller.output_farm_vars(algo)
39
33
  return list(dict.fromkeys(ovars))
40
34
 
41
- def sub_models(self):
42
- """
43
- List of all sub-models
44
-
45
- Returns
46
- -------
47
- smdls: list of foxes.core.Model
48
- Names of all sub models
49
-
50
- """
51
- return [self.pwakes]
52
-
53
- def initialize(self, algo, verbosity=0):
54
- """
55
- Initializes the model.
56
-
57
- Parameters
58
- ----------
59
- algo: foxes.core.Algorithm
60
- The calculation algorithm
61
- verbosity: int
62
- The verbosity level, 0 = silent
63
-
64
- """
65
- self.pwakes = algo.partial_wakes_model
66
- super().initialize(algo, verbosity)
67
-
68
35
  def calculate(self, algo, mdata, fdata):
69
36
  """ "
70
37
  The main model calculation.
@@ -88,33 +55,98 @@ class FarmWakesCalculation(FarmDataModel):
88
55
  Values: numpy.ndarray with shape (n_states, n_turbines)
89
56
 
90
57
  """
91
- torder = fdata[FV.ORDER]
92
- n_order = torder.shape[1]
93
- n_states = mdata.n_states
94
-
95
- def _evaluate(algo, mdata, fdata, pdata, wdeltas, o):
96
- self.pwakes.evaluate_results(
97
- algo, mdata, fdata, pdata, wdeltas, states_turbine=o
58
+ # collect ambient rotor results and weights:
59
+ rotor = algo.rotor_model
60
+ weights = rotor.from_data_or_store(rotor.RWEIGHTS, algo, mdata)
61
+ amb_res = rotor.from_data_or_store(rotor.AMBRES, algo, mdata)
62
+
63
+ # generate all wake evaluation points
64
+ # (n_states, n_order, n_rpoints)
65
+ pwake2tdata = {}
66
+ for wname, wmodel in algo.wake_models.items():
67
+ pwake = algo.partial_wakes[wname]
68
+ if pwake.name not in pwake2tdata:
69
+ tpoints, tweights = pwake.get_wake_points(algo, mdata, fdata)
70
+ pwake2tdata[pwake.name] = TData.from_tpoints(tpoints, tweights)
71
+
72
+ def _get_wdata(tdatap, wdeltas, s):
73
+ """Helper function for wake data extraction"""
74
+ tdata = tdatap.get_slice(s, keep=True)
75
+ wdelta = {v: d[s] for v, d in wdeltas.items()}
76
+ return tdata, wdelta
77
+
78
+ def _evaluate(tdata, amb_res, weights, wake_res, wdeltas, oi, wmodel, pwake):
79
+ """Helper function for data evaluation at turbines"""
80
+ wres = pwake.finalize_wakes(
81
+ algo, mdata, fdata, tdata, amb_res, weights, wdeltas, wmodel, oi
98
82
  )
99
83
 
100
- trbs = np.zeros((n_states, algo.n_turbines), dtype=bool)
101
- np.put_along_axis(trbs, o[:, None], True, axis=1)
84
+ hres = {v: d[:, oi, None] for v, d in wake_res.items()}
85
+ for v, d in wres.items():
86
+ if v in wake_res:
87
+ hres[v] += d[:, None]
88
+
89
+ rotor.eval_rpoint_results(
90
+ algo, mdata, fdata, hres, weights, downwind_index=oi
91
+ )
102
92
 
103
93
  res = algo.farm_controller.calculate(
104
- algo, mdata, fdata, pre_rotor=False, st_sel=trbs
94
+ algo, mdata, fdata, pre_rotor=False, downwind_index=oi
105
95
  )
106
96
  fdata.update(res)
107
97
 
108
- wdeltas, pdata = self.pwakes.new_wake_deltas(algo, mdata, fdata)
109
- for oi in range(n_order):
110
- o = torder[:, oi]
111
-
112
- if oi > 0:
113
- _evaluate(algo, mdata, fdata, pdata, wdeltas, o)
114
-
115
- if oi < n_order - 1:
116
- self.pwakes.contribute_to_wake_deltas(
117
- algo, mdata, fdata, pdata, o, wdeltas
98
+ wake_res = deepcopy(amb_res)
99
+ n_turbines = mdata.n_turbines
100
+ run_up = None
101
+ run_down = None
102
+ for wname, wmodel in algo.wake_models.items():
103
+ pwake = algo.partial_wakes[wname]
104
+ tdatap = pwake2tdata[pwake.name]
105
+ wdeltas = pwake.new_wake_deltas(algo, mdata, fdata, tdatap, wmodel)
106
+
107
+ # downwind:
108
+ if wmodel.affects_downwind:
109
+ run_up = wname
110
+ for oi in range(n_turbines):
111
+ if oi > 0:
112
+ _evaluate(
113
+ tdatap,
114
+ amb_res,
115
+ weights,
116
+ wake_res,
117
+ wdeltas,
118
+ oi,
119
+ wmodel,
120
+ pwake,
121
+ )
122
+
123
+ if oi < n_turbines - 1:
124
+ tdata, wdelta = _get_wdata(tdatap, wdeltas, np.s_[:, oi + 1 :])
125
+ pwake.contribute(algo, mdata, fdata, tdata, oi, wdelta, wmodel)
126
+
127
+ # upwind:
128
+ else:
129
+ run_down = wname
130
+ for oi in range(n_turbines - 1, -1, -1):
131
+ if oi < n_turbines - 1:
132
+ _evaluate(
133
+ tdatap,
134
+ amb_res,
135
+ weights,
136
+ wake_res,
137
+ wdeltas,
138
+ oi,
139
+ wmodel,
140
+ pwake,
141
+ )
142
+
143
+ if oi > 0:
144
+ tdata, wdelta = _get_wdata(tdatap, wdeltas, np.s_[:, :oi])
145
+ pwake.contribute(algo, mdata, fdata, tdata, oi, wdelta, wmodel)
146
+
147
+ if run_up is not None and run_down is not None:
148
+ raise KeyError(
149
+ f"Wake model '{run_up}' is an upwind model, wake model '{run_down}' is a downwind model: Require iterative algorithm"
118
150
  )
119
151
 
120
152
  return {v: fdata[v] for v in self.output_farm_vars(algo)}