foxes 0.8.2__py3-none-any.whl → 1.1.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 (215) 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/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 +190 -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 +247 -111
  27. foxes/algorithms/downwind/models/farm_wakes_calc.py +12 -7
  28. foxes/algorithms/downwind/models/init_farm_data.py +2 -2
  29. foxes/algorithms/downwind/models/point_wakes_calc.py +6 -7
  30. foxes/algorithms/downwind/models/reorder_farm_output.py +1 -2
  31. foxes/algorithms/downwind/models/set_amb_farm_results.py +1 -1
  32. foxes/algorithms/downwind/models/set_amb_point_results.py +5 -3
  33. foxes/algorithms/iterative/iterative.py +74 -34
  34. foxes/algorithms/iterative/models/farm_wakes_calc.py +12 -7
  35. foxes/algorithms/iterative/models/urelax.py +3 -3
  36. foxes/algorithms/sequential/models/plugin.py +5 -5
  37. foxes/algorithms/sequential/models/seq_state.py +1 -1
  38. foxes/algorithms/sequential/sequential.py +126 -255
  39. foxes/constants.py +22 -7
  40. foxes/core/__init__.py +1 -0
  41. foxes/core/algorithm.py +632 -147
  42. foxes/core/data.py +252 -20
  43. foxes/core/data_calc_model.py +15 -291
  44. foxes/core/engine.py +640 -0
  45. foxes/core/farm_controller.py +38 -10
  46. foxes/core/farm_data_model.py +16 -1
  47. foxes/core/ground_model.py +2 -2
  48. foxes/core/model.py +249 -182
  49. foxes/core/partial_wakes_model.py +1 -1
  50. foxes/core/point_data_model.py +17 -2
  51. foxes/core/rotor_model.py +27 -21
  52. foxes/core/states.py +17 -1
  53. foxes/core/turbine_type.py +28 -0
  54. foxes/core/wake_frame.py +30 -34
  55. foxes/core/wake_model.py +5 -5
  56. foxes/core/wake_superposition.py +1 -1
  57. foxes/data/windio/windio_5turbines_timeseries.yaml +31 -15
  58. foxes/engines/__init__.py +17 -0
  59. foxes/engines/dask.py +982 -0
  60. foxes/engines/default.py +75 -0
  61. foxes/engines/futures.py +72 -0
  62. foxes/engines/mpi.py +38 -0
  63. foxes/engines/multiprocess.py +71 -0
  64. foxes/engines/numpy.py +167 -0
  65. foxes/engines/pool.py +249 -0
  66. foxes/engines/ray.py +79 -0
  67. foxes/engines/single.py +141 -0
  68. foxes/input/farm_layout/__init__.py +1 -0
  69. foxes/input/farm_layout/from_csv.py +4 -0
  70. foxes/input/farm_layout/from_json.py +2 -2
  71. foxes/input/farm_layout/grid.py +2 -2
  72. foxes/input/farm_layout/ring.py +65 -0
  73. foxes/input/farm_layout/row.py +2 -2
  74. foxes/input/states/__init__.py +7 -0
  75. foxes/input/states/create/random_abl_states.py +1 -1
  76. foxes/input/states/field_data_nc.py +158 -33
  77. foxes/input/states/multi_height.py +128 -14
  78. foxes/input/states/one_point_flow.py +577 -0
  79. foxes/input/states/scan_ws.py +74 -3
  80. foxes/input/states/single.py +1 -1
  81. foxes/input/states/slice_data_nc.py +681 -0
  82. foxes/input/states/states_table.py +204 -35
  83. foxes/input/windio/__init__.py +2 -2
  84. foxes/input/windio/get_states.py +44 -23
  85. foxes/input/windio/read_attributes.py +48 -17
  86. foxes/input/windio/read_farm.py +116 -102
  87. foxes/input/windio/read_fields.py +16 -6
  88. foxes/input/windio/read_outputs.py +71 -24
  89. foxes/input/windio/runner.py +31 -17
  90. foxes/input/windio/windio.py +41 -23
  91. foxes/models/farm_models/turbine2farm.py +1 -1
  92. foxes/models/ground_models/wake_mirror.py +10 -6
  93. foxes/models/model_book.py +58 -20
  94. foxes/models/partial_wakes/axiwake.py +3 -3
  95. foxes/models/partial_wakes/rotor_points.py +3 -3
  96. foxes/models/partial_wakes/top_hat.py +2 -2
  97. foxes/models/point_models/set_uniform_data.py +1 -1
  98. foxes/models/point_models/tke2ti.py +1 -1
  99. foxes/models/point_models/wake_deltas.py +1 -1
  100. foxes/models/rotor_models/centre.py +4 -0
  101. foxes/models/rotor_models/grid.py +24 -25
  102. foxes/models/rotor_models/levels.py +4 -5
  103. foxes/models/turbine_models/calculator.py +4 -6
  104. foxes/models/turbine_models/kTI_model.py +22 -6
  105. foxes/models/turbine_models/lookup_table.py +30 -4
  106. foxes/models/turbine_models/rotor_centre_calc.py +4 -3
  107. foxes/models/turbine_models/set_farm_vars.py +103 -34
  108. foxes/models/turbine_types/PCt_file.py +27 -3
  109. foxes/models/turbine_types/PCt_from_two.py +27 -3
  110. foxes/models/turbine_types/TBL_file.py +80 -0
  111. foxes/models/turbine_types/__init__.py +2 -0
  112. foxes/models/turbine_types/lookup.py +316 -0
  113. foxes/models/turbine_types/null_type.py +51 -1
  114. foxes/models/turbine_types/wsrho2PCt_from_two.py +29 -5
  115. foxes/models/turbine_types/wsti2PCt_from_two.py +31 -7
  116. foxes/models/vertical_profiles/__init__.py +1 -1
  117. foxes/models/vertical_profiles/data_profile.py +1 -1
  118. foxes/models/wake_frames/__init__.py +1 -0
  119. foxes/models/wake_frames/dynamic_wakes.py +424 -0
  120. foxes/models/wake_frames/farm_order.py +25 -5
  121. foxes/models/wake_frames/rotor_wd.py +6 -4
  122. foxes/models/wake_frames/seq_dynamic_wakes.py +61 -74
  123. foxes/models/wake_frames/streamlines.py +21 -22
  124. foxes/models/wake_frames/timelines.py +330 -129
  125. foxes/models/wake_frames/yawed_wakes.py +7 -4
  126. foxes/models/wake_models/dist_sliced.py +2 -4
  127. foxes/models/wake_models/induction/rankine_half_body.py +5 -5
  128. foxes/models/wake_models/induction/rathmann.py +78 -24
  129. foxes/models/wake_models/induction/self_similar.py +78 -28
  130. foxes/models/wake_models/induction/vortex_sheet.py +86 -48
  131. foxes/models/wake_models/ti/crespo_hernandez.py +6 -4
  132. foxes/models/wake_models/ti/iec_ti.py +40 -21
  133. foxes/models/wake_models/top_hat.py +1 -1
  134. foxes/models/wake_models/wind/bastankhah14.py +8 -6
  135. foxes/models/wake_models/wind/bastankhah16.py +17 -16
  136. foxes/models/wake_models/wind/jensen.py +4 -3
  137. foxes/models/wake_models/wind/turbopark.py +16 -13
  138. foxes/models/wake_superpositions/ti_linear.py +1 -1
  139. foxes/models/wake_superpositions/ti_max.py +1 -1
  140. foxes/models/wake_superpositions/ti_pow.py +1 -1
  141. foxes/models/wake_superpositions/ti_quadratic.py +1 -1
  142. foxes/models/wake_superpositions/ws_linear.py +8 -7
  143. foxes/models/wake_superpositions/ws_max.py +8 -7
  144. foxes/models/wake_superpositions/ws_pow.py +8 -7
  145. foxes/models/wake_superpositions/ws_product.py +5 -5
  146. foxes/models/wake_superpositions/ws_quadratic.py +8 -7
  147. foxes/output/__init__.py +4 -1
  148. foxes/output/farm_layout.py +16 -12
  149. foxes/output/farm_results_eval.py +1 -1
  150. foxes/output/flow_plots_2d/__init__.py +0 -1
  151. foxes/output/flow_plots_2d/flow_plots.py +70 -30
  152. foxes/output/grids.py +92 -22
  153. foxes/output/results_writer.py +2 -2
  154. foxes/output/rose_plot.py +3 -3
  155. foxes/output/seq_plugins/__init__.py +2 -0
  156. foxes/output/{flow_plots_2d → seq_plugins}/seq_flow_ani_plugin.py +64 -22
  157. foxes/output/seq_plugins/seq_wake_debug_plugin.py +145 -0
  158. foxes/output/slice_data.py +131 -111
  159. foxes/output/state_turbine_map.py +19 -14
  160. foxes/output/state_turbine_table.py +19 -19
  161. foxes/utils/__init__.py +1 -1
  162. foxes/utils/abl/neutral.py +2 -2
  163. foxes/utils/abl/stable.py +2 -2
  164. foxes/utils/abl/unstable.py +2 -2
  165. foxes/utils/data_book.py +1 -1
  166. foxes/utils/dev_utils.py +42 -0
  167. foxes/utils/dict.py +24 -1
  168. foxes/utils/exec_python.py +1 -1
  169. foxes/utils/factory.py +176 -53
  170. foxes/utils/geom2d/circle.py +1 -1
  171. foxes/utils/geom2d/polygon.py +1 -1
  172. foxes/utils/geopandas_utils.py +2 -2
  173. foxes/utils/load.py +2 -2
  174. foxes/utils/pandas_helpers.py +3 -2
  175. foxes/utils/wind_dir.py +0 -2
  176. foxes/utils/xarray_utils.py +24 -14
  177. foxes/variables.py +39 -2
  178. {foxes-0.8.2.dist-info → foxes-1.1.0.2.dist-info}/METADATA +75 -33
  179. foxes-1.1.0.2.dist-info/RECORD +309 -0
  180. {foxes-0.8.2.dist-info → foxes-1.1.0.2.dist-info}/WHEEL +1 -1
  181. foxes-1.1.0.2.dist-info/top_level.txt +4 -0
  182. tests/0_consistency/iterative/test_iterative.py +92 -0
  183. tests/0_consistency/partial_wakes/test_partial_wakes.py +90 -0
  184. tests/1_verification/flappy_0_6/PCt_files/flappy/run.py +85 -0
  185. tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +103 -0
  186. tests/1_verification/flappy_0_6/abl_states/flappy/run.py +85 -0
  187. tests/1_verification/flappy_0_6/abl_states/test_abl_states.py +87 -0
  188. tests/1_verification/flappy_0_6/partial_top_hat/flappy/run.py +82 -0
  189. tests/1_verification/flappy_0_6/partial_top_hat/test_partial_top_hat.py +82 -0
  190. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/flappy/run.py +92 -0
  191. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +93 -0
  192. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/flappy/run.py +92 -0
  193. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +96 -0
  194. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/flappy/run.py +94 -0
  195. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +122 -0
  196. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/flappy/run.py +94 -0
  197. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +122 -0
  198. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/flappy/run.py +92 -0
  199. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +93 -0
  200. tests/1_verification/flappy_0_6_2/grid_rotors/flappy/run.py +85 -0
  201. tests/1_verification/flappy_0_6_2/grid_rotors/test_grid_rotors.py +130 -0
  202. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/flappy/run.py +96 -0
  203. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +116 -0
  204. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/flappy/run.py +93 -0
  205. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +99 -0
  206. tests/3_examples/test_examples.py +34 -0
  207. foxes/VERSION +0 -1
  208. foxes/output/flow_plots_2d.py +0 -0
  209. foxes/utils/geopandas_helpers.py +0 -294
  210. foxes/utils/runners/__init__.py +0 -1
  211. foxes/utils/runners/runners.py +0 -280
  212. foxes-0.8.2.dist-info/RECORD +0 -247
  213. foxes-0.8.2.dist-info/top_level.txt +0 -1
  214. foxes-0.8.2.dist-info/zip-safe +0 -1
  215. {foxes-0.8.2.dist-info → foxes-1.1.0.2.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 downwind 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,13 +63,13 @@ 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]
50
70
 
51
71
  def calc_order(self, algo, mdata, fdata):
52
- """ "
72
+ """
53
73
  Calculates the order of turbine evaluation.
54
74
 
55
75
  This function is executed on a single chunk of data,
@@ -98,7 +118,7 @@ class FarmOrder(WakeFrame):
98
118
  The target point data
99
119
  downwind_index: int
100
120
  The index of the wake causing turbine
101
- in the downwnd order
121
+ in the downwind order
102
122
 
103
123
  Returns
104
124
  -------
@@ -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,13 +28,15 @@ 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):
37
- """ "
39
+ """
38
40
  Calculates the order of turbine evaluation.
39
41
 
40
42
  This function is executed on a single chunk of data,
@@ -84,7 +86,7 @@ class RotorWD(WakeFrame):
84
86
  The target point data
85
87
  downwind_index: int
86
88
  The index of the wake causing turbine
87
- in the downwnd order
89
+ in the downwind order
88
90
 
89
91
  Returns
90
92
  -------