foxes 0.8.1__py3-none-any.whl → 1.0__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 (175) hide show
  1. docs/source/conf.py +353 -0
  2. examples/abl_states/run.py +160 -0
  3. examples/compare_rotors_pwakes/run.py +217 -0
  4. examples/compare_wakes/run.py +241 -0
  5. examples/dyn_wakes/run.py +311 -0
  6. examples/field_data_nc/run.py +121 -0
  7. examples/induction_RHB/run.py +201 -0
  8. examples/multi_height/run.py +113 -0
  9. examples/power_mask/run.py +249 -0
  10. examples/random_timeseries/run.py +210 -0
  11. examples/scan_row/run.py +193 -0
  12. examples/sector_management/run.py +162 -0
  13. examples/sequential/run.py +209 -0
  14. examples/single_state/run.py +201 -0
  15. examples/states_lookup_table/run.py +137 -0
  16. examples/streamline_wakes/run.py +138 -0
  17. examples/tab_file/run.py +142 -0
  18. examples/timelines/run.py +267 -0
  19. examples/timeseries/run.py +183 -0
  20. examples/timeseries_slurm/run.py +185 -0
  21. examples/wind_rose/run.py +141 -0
  22. examples/windio/run.py +29 -0
  23. examples/yawed_wake/run.py +196 -0
  24. foxes/__init__.py +4 -8
  25. foxes/algorithms/__init__.py +1 -1
  26. foxes/algorithms/downwind/downwind.py +232 -101
  27. foxes/algorithms/downwind/models/farm_wakes_calc.py +11 -6
  28. foxes/algorithms/downwind/models/init_farm_data.py +1 -1
  29. foxes/algorithms/downwind/models/point_wakes_calc.py +5 -6
  30. foxes/algorithms/downwind/models/reorder_farm_output.py +0 -1
  31. foxes/algorithms/downwind/models/set_amb_point_results.py +4 -2
  32. foxes/algorithms/iterative/iterative.py +73 -33
  33. foxes/algorithms/iterative/models/farm_wakes_calc.py +11 -6
  34. foxes/algorithms/sequential/models/plugin.py +1 -1
  35. foxes/algorithms/sequential/sequential.py +126 -255
  36. foxes/constants.py +17 -2
  37. foxes/core/__init__.py +1 -0
  38. foxes/core/algorithm.py +631 -146
  39. foxes/core/data.py +252 -20
  40. foxes/core/data_calc_model.py +13 -289
  41. foxes/core/engine.py +630 -0
  42. foxes/core/farm_controller.py +37 -9
  43. foxes/core/farm_data_model.py +15 -0
  44. foxes/core/model.py +133 -80
  45. foxes/core/point_data_model.py +15 -0
  46. foxes/core/rotor_model.py +27 -21
  47. foxes/core/states.py +16 -0
  48. foxes/core/turbine_type.py +28 -0
  49. foxes/core/wake_frame.py +22 -4
  50. foxes/core/wake_model.py +2 -3
  51. foxes/data/windio/windio_5turbines_timeseries.yaml +23 -1
  52. foxes/engines/__init__.py +16 -0
  53. foxes/engines/dask.py +975 -0
  54. foxes/engines/default.py +75 -0
  55. foxes/engines/futures.py +72 -0
  56. foxes/engines/mpi.py +38 -0
  57. foxes/engines/multiprocess.py +74 -0
  58. foxes/engines/numpy.py +185 -0
  59. foxes/engines/pool.py +263 -0
  60. foxes/engines/single.py +139 -0
  61. foxes/input/farm_layout/__init__.py +1 -0
  62. foxes/input/farm_layout/from_csv.py +4 -0
  63. foxes/input/farm_layout/from_json.py +1 -1
  64. foxes/input/farm_layout/grid.py +2 -2
  65. foxes/input/farm_layout/ring.py +65 -0
  66. foxes/input/farm_layout/row.py +2 -2
  67. foxes/input/states/__init__.py +6 -0
  68. foxes/input/states/create/random_abl_states.py +1 -1
  69. foxes/input/states/field_data_nc.py +157 -32
  70. foxes/input/states/multi_height.py +127 -13
  71. foxes/input/states/one_point_flow.py +577 -0
  72. foxes/input/states/scan_ws.py +73 -2
  73. foxes/input/states/states_table.py +204 -35
  74. foxes/input/windio/__init__.py +1 -1
  75. foxes/input/windio/get_states.py +44 -23
  76. foxes/input/windio/read_attributes.py +41 -16
  77. foxes/input/windio/read_farm.py +116 -102
  78. foxes/input/windio/read_fields.py +13 -6
  79. foxes/input/windio/read_outputs.py +63 -22
  80. foxes/input/windio/runner.py +31 -17
  81. foxes/input/windio/windio.py +36 -22
  82. foxes/models/ground_models/wake_mirror.py +8 -4
  83. foxes/models/model_book.py +29 -18
  84. foxes/models/partial_wakes/rotor_points.py +3 -3
  85. foxes/models/rotor_models/centre.py +4 -0
  86. foxes/models/rotor_models/grid.py +22 -23
  87. foxes/models/rotor_models/levels.py +4 -5
  88. foxes/models/turbine_models/calculator.py +0 -2
  89. foxes/models/turbine_models/lookup_table.py +27 -2
  90. foxes/models/turbine_models/rotor_centre_calc.py +4 -3
  91. foxes/models/turbine_models/set_farm_vars.py +103 -34
  92. foxes/models/turbine_types/PCt_file.py +24 -0
  93. foxes/models/turbine_types/PCt_from_two.py +24 -0
  94. foxes/models/turbine_types/__init__.py +1 -0
  95. foxes/models/turbine_types/lookup.py +316 -0
  96. foxes/models/turbine_types/null_type.py +50 -0
  97. foxes/models/turbine_types/wsrho2PCt_from_two.py +24 -0
  98. foxes/models/turbine_types/wsti2PCt_from_two.py +24 -0
  99. foxes/models/vertical_profiles/data_profile.py +1 -1
  100. foxes/models/wake_frames/__init__.py +1 -0
  101. foxes/models/wake_frames/dynamic_wakes.py +424 -0
  102. foxes/models/wake_frames/farm_order.py +23 -3
  103. foxes/models/wake_frames/rotor_wd.py +4 -2
  104. foxes/models/wake_frames/seq_dynamic_wakes.py +56 -63
  105. foxes/models/wake_frames/streamlines.py +19 -20
  106. foxes/models/wake_frames/timelines.py +328 -127
  107. foxes/models/wake_frames/yawed_wakes.py +4 -1
  108. foxes/models/wake_models/dist_sliced.py +1 -3
  109. foxes/models/wake_models/induction/rankine_half_body.py +4 -4
  110. foxes/models/wake_models/induction/rathmann.py +2 -2
  111. foxes/models/wake_models/induction/self_similar.py +2 -2
  112. foxes/models/wake_models/induction/vortex_sheet.py +2 -2
  113. foxes/models/wake_models/ti/iec_ti.py +34 -17
  114. foxes/models/wake_models/top_hat.py +1 -1
  115. foxes/models/wake_models/wind/bastankhah14.py +2 -2
  116. foxes/models/wake_models/wind/bastankhah16.py +8 -7
  117. foxes/models/wake_models/wind/jensen.py +1 -1
  118. foxes/models/wake_models/wind/turbopark.py +2 -2
  119. foxes/output/__init__.py +4 -1
  120. foxes/output/farm_layout.py +2 -2
  121. foxes/output/flow_plots_2d/__init__.py +0 -1
  122. foxes/output/flow_plots_2d/flow_plots.py +70 -30
  123. foxes/output/grids.py +91 -21
  124. foxes/output/seq_plugins/__init__.py +2 -0
  125. foxes/output/{flow_plots_2d → seq_plugins}/seq_flow_ani_plugin.py +62 -20
  126. foxes/output/seq_plugins/seq_wake_debug_plugin.py +145 -0
  127. foxes/output/slice_data.py +131 -111
  128. foxes/output/state_turbine_map.py +18 -13
  129. foxes/output/state_turbine_table.py +19 -19
  130. foxes/utils/__init__.py +1 -1
  131. foxes/utils/dev_utils.py +42 -0
  132. foxes/utils/dict.py +1 -1
  133. foxes/utils/factory.py +147 -52
  134. foxes/utils/pandas_helpers.py +4 -3
  135. foxes/utils/wind_dir.py +0 -2
  136. foxes/utils/xarray_utils.py +25 -13
  137. foxes/variables.py +37 -0
  138. {foxes-0.8.1.dist-info → foxes-1.0.dist-info}/METADATA +72 -34
  139. foxes-1.0.dist-info/RECORD +307 -0
  140. {foxes-0.8.1.dist-info → foxes-1.0.dist-info}/WHEEL +1 -1
  141. foxes-1.0.dist-info/top_level.txt +4 -0
  142. tests/0_consistency/iterative/test_iterative.py +92 -0
  143. tests/0_consistency/partial_wakes/test_partial_wakes.py +90 -0
  144. tests/1_verification/flappy_0_6/PCt_files/flappy/run.py +85 -0
  145. tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +103 -0
  146. tests/1_verification/flappy_0_6/abl_states/flappy/run.py +85 -0
  147. tests/1_verification/flappy_0_6/abl_states/test_abl_states.py +87 -0
  148. tests/1_verification/flappy_0_6/partial_top_hat/flappy/run.py +82 -0
  149. tests/1_verification/flappy_0_6/partial_top_hat/test_partial_top_hat.py +82 -0
  150. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/flappy/run.py +92 -0
  151. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +93 -0
  152. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/flappy/run.py +92 -0
  153. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +96 -0
  154. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/flappy/run.py +94 -0
  155. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +122 -0
  156. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/flappy/run.py +94 -0
  157. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +122 -0
  158. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/flappy/run.py +92 -0
  159. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +93 -0
  160. tests/1_verification/flappy_0_6_2/grid_rotors/flappy/run.py +85 -0
  161. tests/1_verification/flappy_0_6_2/grid_rotors/test_grid_rotors.py +130 -0
  162. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/flappy/run.py +96 -0
  163. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +116 -0
  164. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/flappy/run.py +93 -0
  165. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +99 -0
  166. tests/3_examples/test_examples.py +34 -0
  167. foxes/VERSION +0 -1
  168. foxes/output/flow_plots_2d.py +0 -0
  169. foxes/utils/plotly_helpers.py +0 -19
  170. foxes/utils/runners/__init__.py +0 -1
  171. foxes/utils/runners/runners.py +0 -280
  172. foxes-0.8.1.dist-info/RECORD +0 -248
  173. foxes-0.8.1.dist-info/top_level.txt +0 -1
  174. foxes-0.8.1.dist-info/zip-safe +0 -1
  175. {foxes-0.8.1.dist-info → foxes-1.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,424 @@
1
+ import numpy as np
2
+ from scipy.spatial.distance import cdist
3
+
4
+ from foxes.core import WakeFrame, TData
5
+ from foxes.utils import wd2uv
6
+ from foxes.algorithms.iterative import Iterative
7
+ import foxes.variables as FV
8
+ import foxes.constants as FC
9
+
10
+
11
+ class DynamicWakes(WakeFrame):
12
+ """
13
+ Dynamic wakes for any kind of timeseries states.
14
+
15
+ Attributes
16
+ ----------
17
+ max_age: int
18
+ The maximal number of wake steps
19
+ cl_ipars: dict
20
+ Interpolation parameters for centre line
21
+ point interpolation
22
+ dt_min: float
23
+ The delta t value in minutes,
24
+ if not from timeseries data
25
+
26
+ :group: models.wake_frames
27
+
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ max_length_km=20,
33
+ max_age=None,
34
+ max_age_mean_ws=5,
35
+ cl_ipars={},
36
+ dt_min=None,
37
+ **kwargs,
38
+ ):
39
+ """
40
+ Constructor.
41
+
42
+ Parameters
43
+ ----------
44
+ max_length_km: float
45
+ The maximal wake length in km
46
+ max_age: int, optional
47
+ The maximal number of wake steps
48
+ max_age_mean_ws: float
49
+ The mean wind speed for the max_age calculation,
50
+ if the latter is not given
51
+ cl_ipars: dict
52
+ Interpolation parameters for centre line
53
+ point interpolation
54
+ dt_min: float, optional
55
+ The delta t value in minutes,
56
+ if not from timeseries data
57
+ kwargs: dict, optional
58
+ Additional parameters for the base class
59
+
60
+ """
61
+ super().__init__(max_length_km=max_length_km, **kwargs)
62
+
63
+ self.max_age = max_age
64
+ self.cl_ipars = cl_ipars
65
+ self.dt_min = dt_min
66
+ self._mage_ws = max_age_mean_ws
67
+
68
+ def __repr__(self):
69
+ return f"{type(self).__name__}(dt_min={self.dt_min}, max_length_km={self.max_length_km}, max_age={self.max_age})"
70
+
71
+ def initialize(self, algo, verbosity=0):
72
+ """
73
+ Initializes the model.
74
+
75
+ Parameters
76
+ ----------
77
+ algo: foxes.core.Algorithm
78
+ The calculation algorithm
79
+ verbosity: int
80
+ The verbosity level, 0 = silent
81
+
82
+ """
83
+ if not isinstance(algo, Iterative):
84
+ raise TypeError(
85
+ f"Incompatible algorithm type {type(algo).__name__}, expecting {Iterative.__name__}"
86
+ )
87
+ super().initialize(algo, verbosity)
88
+
89
+ # get and check times:
90
+ times = np.asarray(algo.states.index())
91
+ if self.dt_min is None:
92
+ if not np.issubdtype(times.dtype, np.datetime64):
93
+ raise TypeError(
94
+ f"{self.name}: Expecting state index of type np.datetime64, found {times.dtype}"
95
+ )
96
+ elif len(times) == 1:
97
+ raise KeyError(
98
+ f"{self.name}: Expecting 'dt_min' for single step timeseries"
99
+ )
100
+ self._dt = (
101
+ (times[1:] - times[:-1]).astype("timedelta64[s]").astype(FC.ITYPE)
102
+ )
103
+ else:
104
+ n = max(len(times) - 1, 1)
105
+ self._dt = np.full(n, self.dt_min * 60, dtype="timedelta64[s]").astype(
106
+ FC.ITYPE
107
+ )
108
+ self._dt = np.append(self._dt, self._dt[-1, None], axis=0)
109
+
110
+ # find max age if not given:
111
+ if self.max_age is None:
112
+ step = np.mean(self._mage_ws * self._dt)
113
+ self.max_age = max(int(self.max_length_km * 1e3 / step), 1)
114
+ if verbosity > 0:
115
+ print(
116
+ f"{self.name}: Assumed mean step = {step} m, setting max_age = {self.max_age}"
117
+ )
118
+
119
+ self.DATA = self.var("data")
120
+ self.UPDATE = self.var("update")
121
+
122
+ def calc_order(self, algo, mdata, fdata):
123
+ """ "
124
+ Calculates the order of turbine evaluation.
125
+
126
+ This function is executed on a single chunk of data,
127
+ all computations should be based on numpy arrays.
128
+
129
+ Parameters
130
+ ----------
131
+ algo: foxes.core.Algorithm
132
+ The calculation algorithm
133
+ mdata: foxes.core.MData
134
+ The model data
135
+ fdata: foxes.core.FData
136
+ The farm data
137
+
138
+ Returns
139
+ -------
140
+ order: numpy.ndarray
141
+ The turbine order, shape: (n_states, n_turbines)
142
+
143
+ """
144
+ order = np.zeros((fdata.n_states, fdata.n_turbines), dtype=FC.ITYPE)
145
+ order[:] = np.arange(fdata.n_turbines)[None, :]
146
+ return order
147
+
148
+ def _calc_wakes(self, algo, mdata, fdata, downwind_index):
149
+ """Helper function that computes the dynamic wakes"""
150
+ # prepare:
151
+ n_states = mdata.n_states
152
+ rxyh = fdata[FV.TXYH][:, downwind_index]
153
+ i0 = mdata.states_i0(counter=True)
154
+ i1 = i0 + n_states
155
+ dt = self._dt[i0:i1]
156
+ tdi = {
157
+ v: (FC.STATE, FC.TARGET, FC.TPOINT)
158
+ for v in algo.states.output_point_vars(algo)
159
+ }
160
+ key = f"{self.DATA}_{downwind_index}"
161
+ ukey_fun = lambda fr, to: f"{self.UPDATE}_dw{downwind_index}_from_{fr}_to_{to}"
162
+
163
+ # compute wakes that start within this chunk: x, y, z, length
164
+ data = algo.get_from_chunk_store(name=key, mdata=mdata, error=False)
165
+ if data is None:
166
+ data = np.full((n_states, self.max_age, 4), np.nan, dtype=FC.DTYPE)
167
+ data[:, 0, :3] = rxyh
168
+ data[:, 0, 3] = 0
169
+ tdt = {v: np.zeros((n_states, 1, 1), dtype=FC.DTYPE) for v in tdi.keys()}
170
+ pts = data[:, 0, :3].copy()
171
+ for age in range(self.max_age - 1):
172
+ if age == n_states:
173
+ break
174
+ elif age == 0:
175
+ hmdata = mdata
176
+ hfdata = fdata
177
+ htdata = TData.from_points(points=pts[:, None], data=tdt, dims=tdi)
178
+ hdt = dt[:, None]
179
+ else:
180
+ s = np.s_[age:]
181
+ pts = pts[:-1]
182
+ hmdata = mdata.get_slice(FC.STATE, s)
183
+ hfdata = fdata.get_slice(FC.STATE, s)
184
+ htdt = {v: d[s] for v, d in tdt.items()}
185
+ htdata = TData.from_points(points=pts[:, None], data=htdt, dims=tdi)
186
+ hdt = dt[s, None]
187
+ del htdt, s
188
+
189
+ res = algo.states.calculate(algo, hmdata, hfdata, htdata)
190
+ del hmdata, hfdata, htdata
191
+
192
+ uv = wd2uv(res[FV.WD], res[FV.WS])[:, 0, 0]
193
+ dxy = uv * hdt
194
+ pts[:, :2] += dxy
195
+ s = np.s_[:-age] if age > 0 else np.s_[:]
196
+ data[s, age + 1, :3] = pts
197
+ data[s, age + 1, 3] = data[s, age, 3] + np.linalg.norm(dxy, axis=-1)
198
+
199
+ if age < self.max_age - 2:
200
+ s = ~np.isnan(data[:, age + 1, 3])
201
+ if np.min(data[s, age + 1, 3]) >= self.max_length_km * 1e3:
202
+ break
203
+
204
+ del res, uv, s, hdt, dxy
205
+ del pts, tdt
206
+
207
+ # store this chunk's results:
208
+ algo.add_to_chunk_store(key, data, mdata, copy=False)
209
+ algo.block_convergence(mdata=mdata)
210
+
211
+ # apply updates from future chunks:
212
+ for (j, t), cdict in algo.chunk_store.items():
213
+ uname = ukey_fun(j, i0)
214
+ if j > i0 and t == 0 and uname in cdict:
215
+ u = cdict[uname]
216
+ if u is not None:
217
+ sel = np.isnan(data) & ~np.isnan(u)
218
+ if np.any(sel):
219
+ data[:] = np.where(sel, u, data)
220
+ algo.block_convergence(mdata=mdata)
221
+ cdict[uname] = None
222
+ del sel
223
+ del u
224
+
225
+ # compute wakes from previous chunks:
226
+ prev = 0
227
+ wi0 = i0
228
+ data = [data]
229
+ while True:
230
+ prev += 1
231
+
232
+ # read data from previous chunk:
233
+ hdata, (h_i0, h_n_states, __, __) = algo.get_from_chunk_store(
234
+ name=key, mdata=mdata, prev_s=prev, ret_inds=True, error=False
235
+ )
236
+ if hdata is None:
237
+ break
238
+ else:
239
+ hdata = hdata.copy()
240
+ wi0 = h_i0
241
+
242
+ # select points with index+age=i0:
243
+ sts = np.arange(h_n_states)
244
+ ags = i0 - (h_i0 + sts)
245
+ sel = ags < self.max_age - 1
246
+ if np.any(sel):
247
+ sts = sts[sel]
248
+ ags = ags[sel]
249
+ pts = hdata[sts, ags, :3]
250
+ sel = (
251
+ np.all(~np.isnan(pts[:, :2]), axis=-1)
252
+ & np.any(np.isnan(hdata[sts, ags + 1, :2]), axis=-1)
253
+ & (hdata[sts, ags, 3] <= self.max_length_km * 1e3)
254
+ )
255
+ if np.any(sel):
256
+ sts = sts[sel]
257
+ ags = ags[sel]
258
+ pts = pts[sel]
259
+ n_pts = len(pts)
260
+
261
+ tdt = {
262
+ v: np.zeros((n_states, n_pts, 1), dtype=FC.DTYPE)
263
+ for v in algo.states.output_point_vars(algo)
264
+ }
265
+
266
+ # compute single state wake propagation:
267
+ isnan0 = np.isnan(hdata)
268
+ for si in range(n_states):
269
+
270
+ s = slice(si, si + 1, None)
271
+ hmdata = mdata.get_slice(FC.STATE, s)
272
+ hfdata = fdata.get_slice(FC.STATE, s)
273
+ htdt = {v: d[s] for v, d in tdt.items()}
274
+ htdata = TData.from_points(
275
+ points=pts[None, :], data=htdt, dims=tdi
276
+ )
277
+ hdt = dt[s, None]
278
+ del htdt, s
279
+
280
+ res = algo.states.calculate(algo, hmdata, hfdata, htdata)
281
+ del hmdata, hfdata, htdata
282
+
283
+ uv = wd2uv(res[FV.WD], res[FV.WS])[0, :, 0]
284
+ dxy = uv * hdt
285
+ pts[:, :2] += dxy
286
+ del res, uv, hdt
287
+
288
+ ags += 1
289
+ hdata[sts, ags, :3] = pts
290
+ hdata[sts, ags, 3] = hdata[
291
+ sts, ags - 1, 3
292
+ ] + np.linalg.norm(dxy, axis=-1)
293
+ del dxy
294
+
295
+ hsel = (h_i0 + sts + ags < i1) & (ags < self.max_age - 1)
296
+ if np.any(hsel):
297
+ sts = sts[hsel]
298
+ ags = ags[hsel]
299
+ pts = pts[hsel]
300
+ tdt = {v: d[:, hsel] for v, d in tdt.items()}
301
+ del hsel
302
+ else:
303
+ del hsel
304
+ break
305
+
306
+ # store update:
307
+ sel = isnan0 & (~np.isnan(hdata))
308
+ if np.any(sel):
309
+ udata = np.full_like(hdata, np.nan)
310
+ udata[sel] = hdata[sel]
311
+ algo.add_to_chunk_store(
312
+ ukey_fun(i0, h_i0), udata, mdata=mdata, copy=False
313
+ )
314
+ algo.block_convergence(mdata=mdata)
315
+
316
+ del udata, tdt
317
+ del pts
318
+
319
+ # store prev chunk's results:
320
+ data.insert(0, hdata)
321
+
322
+ del sts, ags, sel
323
+ del hdata
324
+
325
+ return np.concatenate(data, axis=0), wi0
326
+
327
+ def get_wake_coos(
328
+ self,
329
+ algo,
330
+ mdata,
331
+ fdata,
332
+ tdata,
333
+ downwind_index,
334
+ ):
335
+ """
336
+ Calculate wake coordinates of rotor points.
337
+
338
+ Parameters
339
+ ----------
340
+ algo: foxes.core.Algorithm
341
+ The calculation algorithm
342
+ mdata: foxes.core.MData
343
+ The model data
344
+ fdata: foxes.core.FData
345
+ The farm data
346
+ tdata: foxes.core.TData
347
+ The target point data
348
+ downwind_index: int
349
+ The index of the wake causing turbine
350
+ in the downwnd order
351
+
352
+ Returns
353
+ -------
354
+ wake_coos: numpy.ndarray
355
+ The wake frame coordinates of the evaluation
356
+ points, shape: (n_states, n_targets, n_tpoints, 3)
357
+
358
+ """
359
+ # first compute dynamic wakes:
360
+ wdata, wi0 = self._calc_wakes(algo, mdata, fdata, downwind_index)
361
+
362
+ # prepare:
363
+ targets = tdata[FC.TARGETS]
364
+ n_states, n_targets, n_tpoints = targets.shape[:3]
365
+ n_points = n_targets * n_tpoints
366
+ points = targets.reshape(n_states, n_points, 3)
367
+ rxyh = fdata[FV.TXYH][:, downwind_index]
368
+ i0 = mdata.states_i0(counter=True)
369
+
370
+ # initialize:
371
+ wcoos = np.full((n_states, n_points, 3), 1e20, dtype=FC.DTYPE)
372
+ wcoos[:, :, 2] = points[:, :, 2] - rxyh[:, None, 2]
373
+ wake_si = np.zeros((n_states, n_points), dtype=FC.ITYPE)
374
+ wake_si[:] = i0 + np.arange(n_states)[:, None]
375
+
376
+ # find nearest wake point:
377
+ for si in range(n_states):
378
+ ags = np.arange(self.max_age)
379
+ sts = i0 + si - ags - wi0
380
+ sel = (sts >= 0) & (sts < len(wdata))
381
+ if np.any(sel):
382
+ sts = sts[sel]
383
+ ags = ags[sel]
384
+ sel = np.all(~np.isnan(wdata[sts, ags]), axis=-1)
385
+ if np.any(sel):
386
+ sts = sts[sel]
387
+ ags = ags[sel]
388
+
389
+ dists = cdist(points[si, :, :2], wdata[sts, ags, :2])
390
+ j = np.argmin(dists, axis=1)
391
+ sts = sts[j]
392
+ ags = ags[j]
393
+ wake_si[si] = sts + wi0
394
+
395
+ nx = wdata[sts, ags, :2]
396
+ dp = points[si, :, :2] - nx
397
+ sel = ags < self.max_age - 1
398
+ if np.any(sel):
399
+ nx[sel] = wdata[sts[sel], ags[sel] + 1, :2] - nx[sel]
400
+ if np.any(~sel):
401
+ nx[~sel] -= wdata[sts[~sel], ags[~sel] - 1, :2]
402
+ dx = np.linalg.norm(nx, axis=-1)
403
+ nx /= dx[:, None]
404
+
405
+ projx = np.einsum("sd,sd->s", dp, nx)
406
+ sel = (projx > -dx) & (projx < dx)
407
+ if np.any(sel):
408
+ ny = np.concatenate([-nx[:, 1, None], nx[:, 0, None]], axis=1)
409
+
410
+ wcoos[si, sel, 0] = projx[sel] + wdata[sts[sel], ags[sel], 3]
411
+ wcoos[si, sel, 1] = np.einsum("sd,sd->s", dp[sel], ny[sel])
412
+
413
+ # store turbines that cause wake:
414
+ tdata[FC.STATE_SOURCE_ORDERI] = downwind_index
415
+
416
+ # store states that cause wake for each target point,
417
+ # will be used by model.get_data() during wake calculation:
418
+ tdata.add(
419
+ FC.STATES_SEL,
420
+ wake_si.reshape(n_states, n_targets, n_tpoints),
421
+ (FC.STATE, FC.TARGET, FC.TPOINT),
422
+ )
423
+
424
+ return wcoos.reshape(n_states, n_targets, n_tpoints, 3)
@@ -23,7 +23,7 @@ class FarmOrder(WakeFrame):
23
23
 
24
24
  """
25
25
 
26
- def __init__(self, base_frame=RotorWD()):
26
+ def __init__(self, base_frame=None, **kwargs):
27
27
  """
28
28
  Constructor.
29
29
 
@@ -31,11 +31,31 @@ class FarmOrder(WakeFrame):
31
31
  ----------
32
32
  base_frame: foxes.core.WakeFrame
33
33
  The wake frame from which to start
34
+ kwargs: dict, optional
35
+ Additional parameters for the base class
34
36
 
35
37
  """
36
- super().__init__()
38
+ super().__init__(**kwargs)
37
39
  self.base_frame = base_frame
38
40
 
41
+ def initialize(self, algo, verbosity=0, force=False):
42
+ """
43
+ Initializes the model.
44
+
45
+ Parameters
46
+ ----------
47
+ algo: foxes.core.Algorithm
48
+ The calculation algorithm
49
+ verbosity: int
50
+ The verbosity level, 0 = silent
51
+ force: bool
52
+ Overwrite existing data
53
+
54
+ """
55
+ if self.base_frame is None:
56
+ self.base_frame = RotorWD()
57
+ super().initialize(algo, verbosity, force)
58
+
39
59
  def sub_models(self):
40
60
  """
41
61
  List of all sub-models
@@ -43,7 +63,7 @@ class FarmOrder(WakeFrame):
43
63
  Returns
44
64
  -------
45
65
  smdls: list of foxes.core.Model
46
- Names of all sub models
66
+ All sub models
47
67
 
48
68
  """
49
69
  return [self.base_frame]
@@ -20,7 +20,7 @@ class RotorWD(WakeFrame):
20
20
 
21
21
  """
22
22
 
23
- def __init__(self, var_wd=FV.WD):
23
+ def __init__(self, var_wd=FV.WD, **kwargs):
24
24
  """
25
25
  Constructor.
26
26
 
@@ -28,9 +28,11 @@ class RotorWD(WakeFrame):
28
28
  ----------
29
29
  var_wd: str
30
30
  The wind direction variable
31
+ kwargs: dict, optional
32
+ Additional parameters for the base class
31
33
 
32
34
  """
33
- super().__init__()
35
+ super().__init__(**kwargs)
34
36
  self.var_wd = var_wd
35
37
 
36
38
  def calc_order(self, algo, mdata, fdata):
@@ -1,15 +1,16 @@
1
1
  import numpy as np
2
2
  from scipy.spatial.distance import cdist
3
3
 
4
- from foxes.core import WakeFrame
5
4
  from foxes.utils import wd2uv
6
5
  from foxes.core.data import TData
7
6
  import foxes.variables as FV
8
7
  import foxes.constants as FC
9
- from foxes.algorithms import Sequential
8
+ from foxes.algorithms.sequential import Sequential
10
9
 
10
+ from .farm_order import FarmOrder
11
11
 
12
- class SeqDynamicWakes(WakeFrame):
12
+
13
+ class SeqDynamicWakes(FarmOrder):
13
14
  """
14
15
  Dynamic wakes for the sequential algorithm.
15
16
 
@@ -26,7 +27,7 @@ class SeqDynamicWakes(WakeFrame):
26
27
 
27
28
  """
28
29
 
29
- def __init__(self, cl_ipars={}, dt_min=None):
30
+ def __init__(self, cl_ipars={}, dt_min=None, **kwargs):
30
31
  """
31
32
  Constructor.
32
33
 
@@ -38,9 +39,11 @@ class SeqDynamicWakes(WakeFrame):
38
39
  dt_min: float, optional
39
40
  The delta t value in minutes,
40
41
  if not from timeseries data
42
+ kwargs: dict, optional
43
+ Additional parameters for the base class
41
44
 
42
45
  """
43
- super().__init__()
46
+ super().__init__(**kwargs)
44
47
  self.cl_ipars = cl_ipars
45
48
  self.dt_min = dt_min
46
49
 
@@ -88,7 +91,9 @@ class SeqDynamicWakes(WakeFrame):
88
91
  # init wake traces data:
89
92
  self._traces_p = np.zeros((algo.n_states, algo.n_turbines, 3), dtype=FC.DTYPE)
90
93
  self._traces_v = np.zeros((algo.n_states, algo.n_turbines, 3), dtype=FC.DTYPE)
91
- self._traces_l = np.zeros((algo.n_states, algo.n_turbines), dtype=FC.DTYPE)
94
+ self._traces_l = np.full(
95
+ (algo.n_states, algo.n_turbines), np.nan, dtype=FC.DTYPE
96
+ )
92
97
 
93
98
  def calc_order(self, algo, mdata, fdata):
94
99
  """ "
@@ -112,26 +117,7 @@ class SeqDynamicWakes(WakeFrame):
112
117
  The turbine order, shape: (n_states, n_turbines)
113
118
 
114
119
  """
115
- # prepare:
116
- n_states = fdata.n_states
117
- n_turbines = algo.n_turbines
118
- tdata = TData.from_points(points=fdata[FV.TXYH])
119
-
120
- # calculate streamline x coordinates for turbines rotor centre points:
121
- # n_states, n_turbines_source, n_turbines_target
122
- coosx = np.zeros((n_states, n_turbines, n_turbines), dtype=FC.DTYPE)
123
- for ti in range(n_turbines):
124
- coosx[:, ti, :] = self.get_wake_coos(algo, mdata, fdata, tdata, ti)[
125
- :, :, 0, 0
126
- ]
127
-
128
- # derive turbine order:
129
- # TODO: Remove loop over states
130
- order = np.zeros((n_states, n_turbines), dtype=FC.ITYPE)
131
- for si in range(n_states):
132
- order[si] = np.lexsort(keys=coosx[si])
133
-
134
- return order
120
+ return super().calc_order(algo, mdata, fdata)
135
121
 
136
122
  def get_wake_coos(
137
123
  self,
@@ -174,48 +160,54 @@ class SeqDynamicWakes(WakeFrame):
174
160
  counter = algo.states.counter
175
161
  N = counter + 1
176
162
 
177
- # new wake starts at turbine:
178
- self._traces_p[counter, downwind_index] = fdata[FV.TXYH][0, downwind_index]
179
- self._traces_l[counter, downwind_index] = 0
180
-
181
- # transport wakes that originate from previous time steps:
182
- if counter > 0:
183
- dxyz = self._traces_v[:counter, downwind_index] * self._dt[:counter, None]
184
- self._traces_p[:counter, downwind_index] += dxyz
185
- self._traces_l[:counter, downwind_index] += np.linalg.norm(dxyz, axis=-1)
186
-
187
- # compute wind vectors at wake traces:
188
- # TODO: dz from U_z is missing here
189
- hpdata = {
190
- v: np.zeros((1, N, 1), dtype=FC.DTYPE)
191
- for v in algo.states.output_point_vars(algo)
192
- }
193
- hpdims = {v: (FC.STATE, FC.TARGET, FC.TPOINT) for v in hpdata.keys()}
194
- hpdata = TData.from_points(
195
- points=self._traces_p[None, :N, downwind_index],
196
- data=hpdata,
197
- dims=hpdims,
198
- )
199
- res = algo.states.calculate(algo, mdata, fdata, hpdata)
200
- self._traces_v[:N, downwind_index, :2] = wd2uv(
201
- res[FV.WD][0, :, 0], res[FV.WS][0, :, 0]
202
- )
203
- del hpdata, hpdims, res
163
+ if np.isnan(self._traces_l[counter, downwind_index]):
204
164
 
205
- # project:
165
+ # new wake starts at turbine:
166
+ self._traces_p[counter, downwind_index][:] = fdata[FV.TXYH][
167
+ 0, downwind_index
168
+ ]
169
+ self._traces_l[counter, downwind_index] = 0
170
+
171
+ # transport wakes that originate from previous time steps:
172
+ if counter > 0:
173
+ dxyz = self._traces_v[:counter, downwind_index] * self._dt[counter - 1]
174
+ self._traces_p[:counter, downwind_index] += dxyz
175
+ self._traces_l[:counter, downwind_index] += np.linalg.norm(
176
+ dxyz, axis=-1
177
+ )
178
+ del dxyz
179
+
180
+ # compute wind vectors at wake traces:
181
+ # TODO: dz from U_z is missing here
182
+ hpdata = TData.from_points(points=self._traces_p[None, :N, downwind_index])
183
+ res = algo.states.calculate(algo, mdata, fdata, hpdata)
184
+ self._traces_v[:N, downwind_index, :2] = wd2uv(
185
+ res[FV.WD][0, :, 0], res[FV.WS][0, :, 0]
186
+ )
187
+ del hpdata, res
188
+
189
+ # find nearest wake point:
206
190
  dists = cdist(points[0], self._traces_p[:N, downwind_index])
207
191
  tri = np.argmin(dists, axis=1)
208
192
  del dists
193
+
194
+ # project:
209
195
  wcoos = np.full((n_states, n_points, 3), 1e20, dtype=FC.DTYPE)
210
- wcoos[0, :, 2] = points[0, :, 2] - fdata[FV.TXYH][:, downwind_index][0, None, 2]
211
- delp = points[0, :, :2] - self._traces_p[tri, downwind_index, :2]
196
+ wcoos[0, :, 2] = points[0, :, 2] - fdata[FV.TXYH][0, downwind_index, None, 2]
212
197
  nx = self._traces_v[tri, downwind_index, :2]
213
- nx /= np.linalg.norm(nx, axis=1)[:, None]
214
- ny = np.concatenate([-nx[:, 1, None], nx[:, 0, None]], axis=1)
215
- wcoos[0, :, 0] = (
216
- np.einsum("pd,pd->p", delp, nx) + self._traces_l[tri, downwind_index]
217
- )
218
- wcoos[0, :, 1] = np.einsum("pd,pd->p", delp, ny)
198
+ mv = np.linalg.norm(nx, axis=-1)
199
+ nx /= mv[:, None]
200
+ delp = points[0, :, :2] - self._traces_p[tri, downwind_index, :2]
201
+ projx = np.einsum("pd,pd->p", delp, nx)
202
+ dt = self._dt[counter] if counter < len(self._dt) else self._dt[-1]
203
+ dx = mv * dt
204
+ sel = (projx > -dx) & (projx < dx)
205
+ if np.any(sel):
206
+ ny = np.concatenate([-nx[:, 1, None], nx[:, 0, None]], axis=1)
207
+ wcoos[0, sel, 0] = projx[sel] + self._traces_l[tri[sel], downwind_index]
208
+ wcoos[0, sel, 1] = np.einsum("pd,pd->p", delp, ny)[sel]
209
+ del ny
210
+ del delp, projx, mv, dx, nx, sel
219
211
 
220
212
  # turbines that cause wake:
221
213
  tdata[FC.STATE_SOURCE_ORDERI] = downwind_index
@@ -287,7 +279,8 @@ class SeqDynamicWakes(WakeFrame):
287
279
  n_points = n_targets * n_tpoints
288
280
 
289
281
  s = tdata[FC.STATES_SEL][0].reshape(n_points)
290
- data = algo.farm_results[variable].to_numpy()
282
+ data = algo.farm_results_downwind[variable].to_numpy()
283
+ data[algo.counter] = fdata[variable][0]
291
284
  data = data[s, downwind_index].reshape(n_states, n_targets, n_tpoints)
292
285
 
293
286
  if target == FC.STATE_TARGET: