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
@@ -1,8 +1,9 @@
1
1
  import numpy as np
2
+ from xarray import Dataset
2
3
 
3
- from foxes.core import WakeFrame
4
+ from foxes.core import WakeFrame, MData, FData, TData
4
5
  from foxes.utils import wd2uv
5
- from foxes.core.data import MData, FData, TData
6
+ from foxes.algorithms.iterative import Iterative
6
7
  import foxes.variables as FV
7
8
  import foxes.constants as FC
8
9
 
@@ -13,12 +14,12 @@ class Timelines(WakeFrame):
13
14
 
14
15
  Attributes
15
16
  ----------
16
- max_wake_length: float
17
- The maximal wake length
17
+ max_length_km: float
18
+ The maximal wake length in km
18
19
  cl_ipars: dict
19
20
  Interpolation parameters for centre line
20
21
  point interpolation
21
- dt_min: float, optional
22
+ dt_min: float
22
23
  The delta t value in minutes,
23
24
  if not from timeseries data
24
25
 
@@ -26,49 +27,39 @@ class Timelines(WakeFrame):
26
27
 
27
28
  """
28
29
 
29
- def __init__(self, max_wake_length=2e4, cl_ipars={}, dt_min=None):
30
+ def __init__(self, max_length_km=2e4, cl_ipars={}, dt_min=None, **kwargs):
30
31
  """
31
32
  Constructor.
32
33
 
33
34
  Parameters
34
35
  ----------
35
- max_wake_length: float
36
- The maximal wake length
36
+ max_length_km: float
37
+ The maximal wake length in km
37
38
  cl_ipars: dict
38
39
  Interpolation parameters for centre line
39
40
  point interpolation
40
41
  dt_min: float, optional
41
42
  The delta t value in minutes,
42
43
  if not from timeseries data
44
+ kwargs: dict, optional
45
+ Additional parameters for the base class
43
46
 
44
47
  """
45
- super().__init__()
46
- self.max_wake_length = max_wake_length
48
+ super().__init__(max_length_km=max_length_km, **kwargs)
47
49
  self.cl_ipars = cl_ipars
48
50
  self.dt_min = dt_min
49
51
 
50
52
  def __repr__(self):
51
- return f"{type(self).__name__}(dt_min={self.dt_min})"
53
+ return f"{type(self).__name__}(dt_min={self.dt_min}, max_length_km={self.max_length_km})"
52
54
 
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
- super().initialize(algo, verbosity)
55
+ def _precalc_data(self, algo, states, heights, verbosity, needs_res=False):
56
+ """Helper function for pre-calculation of ambient wind vectors"""
66
57
 
67
58
  if verbosity > 0:
68
59
  print(f"{self.name}: Pre-calculating ambient wind vectors")
69
60
 
70
61
  # get and check times:
71
- times = np.asarray(algo.states.index())
62
+ times = np.asarray(states.index())
72
63
  if self.dt_min is None:
73
64
  if not np.issubdtype(times.dtype, np.datetime64):
74
65
  raise TypeError(
@@ -83,50 +74,197 @@ class Timelines(WakeFrame):
83
74
  n = max(len(times) - 1, 1)
84
75
  dt = np.full(n, self.dt_min * 60, dtype="timedelta64[s]").astype(FC.ITYPE)
85
76
 
86
- # calculate horizontal wind vector in all states:
87
- self._uv = np.zeros((algo.n_states, 1, 3), dtype=FC.DTYPE)
88
-
89
77
  # prepare mdata:
90
- mdata = algo.get_model_data(algo.states)["data_vars"]
91
- mdict = {v: d[1] for v, d in mdata.items()}
92
- mdims = {v: d[0] for v, d in mdata.items()}
93
- mdata = MData(mdict, mdims, loop_dims=[FC.STATE])
94
- del mdict, mdims
78
+ data = algo.get_model_data(states)["coords"]
79
+ mdict = {v: np.array(d) for v, d in data.items()}
80
+ mdims = {v: (v,) for v in data.keys()}
81
+ data = algo.get_model_data(states)["data_vars"]
82
+ mdict.update({v: d[1] for v, d in data.items()})
83
+ mdims.update({v: d[0] for v, d in data.items()})
84
+ mdata = MData(mdict, mdims, loop_dims=[FC.STATE], states_i0=0)
85
+ del mdict, mdims, data
95
86
 
96
87
  # prepare fdata:
97
88
  fdata = FData({}, {}, loop_dims=[FC.STATE])
98
89
 
99
90
  # prepare tdata:
100
- tdata = {
101
- v: np.zeros((algo.n_states, 1, 1), dtype=FC.DTYPE)
102
- for v in algo.states.output_point_vars(algo)
91
+ n_states = states.size()
92
+ data = {
93
+ v: np.zeros((n_states, 1, 1), dtype=FC.DTYPE)
94
+ for v in states.output_point_vars(algo)
103
95
  }
104
- pdims = {v: (FC.STATE, FC.TARGET, FC.TPOINT) for v in tdata.keys()}
105
- tdata = TData.from_points(
106
- points=np.zeros((algo.n_states, 1, 3), dtype=FC.DTYPE),
107
- data=tdata,
108
- dims=pdims,
96
+ pdims = {v: (FC.STATE, FC.TARGET, FC.TPOINT) for v in data.keys()}
97
+ points = np.zeros((n_states, 1, 3), dtype=FC.DTYPE)
98
+
99
+ # calculate all heights:
100
+ self.timelines_data = {"dxy": (("height", FC.STATE, "dir"), [])}
101
+ for h in heights:
102
+
103
+ if verbosity > 0:
104
+ print(f" Height: {h} m")
105
+
106
+ points[..., 2] = h
107
+ tdata = TData.from_points(
108
+ points=points,
109
+ data=data,
110
+ dims=pdims,
111
+ )
112
+
113
+ res = states.calculate(algo, mdata, fdata, tdata)
114
+ del tdata
115
+
116
+ uv = wd2uv(res[FV.WD], res[FV.WS])[:, 0, 0, :2]
117
+ if len(dt) == 1:
118
+ dxy = uv * dt[0]
119
+ else:
120
+ dxy = uv[:-1] * dt[:, None]
121
+ dxy = np.append(dxy, uv[-1, None, :] * dt[-1], axis=0)
122
+ self.timelines_data["dxy"][1].append(dxy)
123
+ """ DEBUG
124
+ import matplotlib.pyplot as plt
125
+ xy = np.array([np.sum(self.timelines_data[h][:n], axis=0) for n in range(len(self.timelines_data[h]))])
126
+ print(xy)
127
+ plt.plot(xy[:, 0], xy[:, 1])
128
+ plt.title(f"Height {h} m")
129
+ plt.show()
130
+ quit()
131
+ """
132
+
133
+ if needs_res:
134
+ if "U" not in self.timelines_data:
135
+ self.timelines_data["U"] = (("height", FC.STATE), [])
136
+ self.timelines_data["V"] = (("height", FC.STATE), [])
137
+ self.timelines_data["U"][1].append(uv[:, 0])
138
+ self.timelines_data["V"][1].append(uv[:, 1])
139
+
140
+ for v in states.output_point_vars(algo):
141
+ if v not in [FV.WS, FV.WD]:
142
+ if v not in self.timelines_data:
143
+ self.timelines_data[v] = (("height", FC.STATE), [])
144
+ self.timelines_data[v][1].append(res[v][:, 0, 0])
145
+
146
+ del res, uv, dxy
147
+
148
+ self.timelines_data = Dataset(
149
+ coords={
150
+ FC.STATE: states.index(),
151
+ "height": heights,
152
+ },
153
+ data_vars={
154
+ v: (d[0], np.stack(d[1], axis=0))
155
+ for v, d in self.timelines_data.items()
156
+ },
109
157
  )
110
158
 
111
- # calculate:
112
- res = algo.states.calculate(algo, mdata, fdata, tdata)
113
- if len(dt) == 1:
114
- self._dxy = wd2uv(res[FV.WD], res[FV.WS])[:, 0, 0, :2] * dt[:, None]
159
+ def initialize(self, algo, verbosity=0):
160
+ """
161
+ Initializes the model.
162
+
163
+ Parameters
164
+ ----------
165
+ algo: foxes.core.Algorithm
166
+ The calculation algorithm
167
+ verbosity: int
168
+ The verbosity level, 0 = silent
169
+
170
+ """
171
+ if not isinstance(algo, Iterative):
172
+ raise TypeError(
173
+ f"Incompatible algorithm type {type(algo).__name__}, expecting {Iterative.__name__}"
174
+ )
175
+ super().initialize(algo, verbosity)
176
+
177
+ # find turbine hub heights:
178
+ t2h = np.zeros(algo.n_turbines, dtype=FC.DTYPE)
179
+ for ti, t in enumerate(algo.farm.turbines):
180
+ t2h[ti] = (
181
+ t.H if t.H is not None else algo.farm_controller.turbine_types[ti].H
182
+ )
183
+ heights = np.unique(t2h)
184
+
185
+ # pre-calc data:
186
+ from foxes.input.states import OnePointFlowTimeseries
187
+
188
+ if isinstance(algo.states, OnePointFlowTimeseries):
189
+ self._precalc_data(algo, algo.states.base_states, heights, verbosity)
115
190
  else:
116
- self._dxy = wd2uv(res[FV.WD], res[FV.WS])[:-1, 0, 0, :2] * dt[:, None]
117
- self._dxy = np.insert(self._dxy, 0, self._dxy[0], axis=0)
118
-
119
- """ DEBUG
120
- import matplotlib.pyplot as plt
121
- xy = np.array([np.sum(self._dxy[:n], axis=0) for n in range(len(self._dxy))])
122
- print(xy)
123
- plt.plot(xy[:, 0], xy[:, 1])
124
- plt.show()
125
- quit()
191
+ self._precalc_data(algo, algo.states, heights, verbosity)
192
+
193
+ def set_running(
194
+ self,
195
+ algo,
196
+ data_stash,
197
+ sel=None,
198
+ isel=None,
199
+ verbosity=0,
200
+ ):
126
201
  """
202
+ Sets this model status to running, and moves
203
+ all large data to stash.
204
+
205
+ The stashed data will be returned by the
206
+ unset_running() function after running calculations.
207
+
208
+ Parameters
209
+ ----------
210
+ algo: foxes.core.Algorithm
211
+ The calculation algorithm
212
+ data_stash: dict
213
+ Large data stash, this function adds data here.
214
+ Key: model name. Value: dict, large model data
215
+ sel: dict, optional
216
+ The subset selection dictionary
217
+ isel: dict, optional
218
+ The index subset selection dictionary
219
+ verbosity: int
220
+ The verbosity level, 0 = silent
221
+
222
+ """
223
+ super().set_running(algo, data_stash, sel, isel, verbosity)
224
+
225
+ if sel is not None or isel is not None:
226
+ data_stash[self.name]["data"] = self.timelines_data
227
+
228
+ if isel is not None:
229
+ self.timelines_data = self.timelines_data.isel(isel)
230
+ if sel is not None:
231
+ self.timelines_data = self.timelines_data.sel(sel)
232
+
233
+ def unset_running(
234
+ self,
235
+ algo,
236
+ data_stash,
237
+ sel=None,
238
+ isel=None,
239
+ verbosity=0,
240
+ ):
241
+ """
242
+ Sets this model status to not running, recovering large data
243
+ from stash
244
+
245
+ Parameters
246
+ ----------
247
+ algo: foxes.core.Algorithm
248
+ The calculation algorithm
249
+ data_stash: dict
250
+ Large data stash, this function adds data here.
251
+ Key: model name. Value: dict, large model data
252
+ sel: dict, optional
253
+ The subset selection dictionary
254
+ isel: dict, optional
255
+ The index subset selection dictionary
256
+ verbosity: int
257
+ The verbosity level, 0 = silent
258
+
259
+ """
260
+ super().unset_running(algo, data_stash, sel, isel, verbosity)
261
+
262
+ data = data_stash[self.name]
263
+ if "data" in data:
264
+ self.timelines_data = data.pop("data")
127
265
 
128
266
  def calc_order(self, algo, mdata, fdata):
129
- """ "
267
+ """
130
268
  Calculates the order of turbine evaluation.
131
269
 
132
270
  This function is executed on a single chunk of data,
@@ -147,25 +285,8 @@ class Timelines(WakeFrame):
147
285
  The turbine order, shape: (n_states, n_turbines)
148
286
 
149
287
  """
150
- # prepare:
151
- n_states = fdata.n_states
152
- n_turbines = algo.n_turbines
153
- tdata = TData.from_points(points=fdata[FV.TXYH])
154
-
155
- # calculate streamline x coordinates for turbines rotor centre points:
156
- # n_states, n_turbines_source, n_turbines_target
157
- coosx = np.zeros((n_states, n_turbines, n_turbines), dtype=FC.DTYPE)
158
- for ti in range(n_turbines):
159
- coosx[:, ti, :] = self.get_wake_coos(algo, mdata, fdata, tdata, ti)[
160
- :, :, 0, 0
161
- ]
162
-
163
- # derive turbine order:
164
- # TODO: Remove loop over states
165
- order = np.zeros((n_states, n_turbines), dtype=FC.ITYPE)
166
- for si in range(n_states):
167
- order[si] = np.lexsort(keys=coosx[si])
168
-
288
+ order = np.zeros((fdata.n_states, fdata.n_turbines), dtype=FC.ITYPE)
289
+ order[:] = np.arange(fdata.n_turbines)[None, :]
169
290
  return order
170
291
 
171
292
  def get_wake_coos(
@@ -191,7 +312,7 @@ class Timelines(WakeFrame):
191
312
  The target point data
192
313
  downwind_index: int
193
314
  The index of the wake causing turbine
194
- in the downwnd order
315
+ in the downwind order
195
316
 
196
317
  Returns
197
318
  -------
@@ -206,69 +327,86 @@ class Timelines(WakeFrame):
206
327
  n_points = n_targets * n_tpoints
207
328
  points = targets.reshape(n_states, n_points, 3)
208
329
  rxyz = fdata[FV.TXYH][:, downwind_index]
330
+ theights = fdata[FV.H][:, downwind_index]
331
+ heights = self.timelines_data["height"].to_numpy()
332
+ data_dxy = self.timelines_data["dxy"].to_numpy()
209
333
 
210
334
  D = np.zeros((n_states, n_points), dtype=FC.DTYPE)
211
335
  D[:] = fdata[FV.D][:, downwind_index, None]
212
336
 
213
- i0 = mdata.states_i0(counter=True, algo=algo)
214
- i1 = i0 + mdata.n_states
215
- dxy = self._dxy[:i1]
216
-
217
- trace_p = np.zeros((n_states, n_points, 2), dtype=FC.DTYPE)
218
- trace_p[:] = points[:, :, :2] - rxyz[:, None, :2]
219
- trace_l = np.zeros((n_states, n_points), dtype=FC.DTYPE)
220
- trace_d = np.full((n_states, n_points), np.inf, dtype=FC.DTYPE)
221
- trace_si = np.zeros((n_states, n_points), dtype=FC.ITYPE)
222
- trace_si[:] = i0 + np.arange(n_states)[:, None] + 1
223
-
224
337
  wcoos = np.full((n_states, n_points, 3), 1e20, dtype=FC.DTYPE)
225
338
  wcoosx = wcoos[:, :, 0]
226
339
  wcoosy = wcoos[:, :, 1]
227
340
  wcoos[:, :, 2] = points[:, :, 2] - rxyz[:, None, 2]
228
- del rxyz
229
-
230
- while True:
231
- sel = (trace_si > 0) & (trace_l < self.max_wake_length)
232
- if np.any(sel):
233
- trace_si[sel] -= 1
234
-
235
- delta = dxy[trace_si[sel]]
236
- dmag = np.linalg.norm(delta, axis=-1)
237
-
238
- trace_p[sel] -= delta
239
- trace_l[sel] += dmag
240
-
241
- trp = trace_p[sel]
242
- d0 = trace_d[sel]
243
- d = np.linalg.norm(trp, axis=-1)
244
- trace_d[sel] = d
245
-
246
- seln = d <= np.minimum(d0, 2 * dmag)
247
- if np.any(seln):
248
- htrp = trp[seln]
249
- raxis = delta[seln]
250
- raxis = raxis / np.linalg.norm(raxis, axis=-1)[:, None]
251
- saxis = np.concatenate(
252
- [-raxis[:, 1, None], raxis[:, 0, None]], axis=1
253
- )
254
-
255
- wcx = wcoosx[sel]
256
- wcx[seln] = np.einsum("sd,sd->s", htrp, raxis) + trace_l[sel][seln]
257
- wcoosx[sel] = wcx
258
- del wcx, raxis
259
-
260
- wcy = wcoosy[sel]
261
- wcy[seln] = np.einsum("sd,sd->s", htrp, saxis)
262
- wcoosy[sel] = wcy
263
- del wcy, saxis, htrp
264
-
265
- else:
266
- break
267
341
 
268
- # turbines that cause wake:
342
+ i0 = mdata.states_i0(counter=True)
343
+ i1 = i0 + mdata.n_states
344
+ trace_si = np.zeros((n_states, n_points), dtype=FC.ITYPE)
345
+ trace_si[:] = i0 + np.arange(n_states)[:, None]
346
+ for hi, h in enumerate(heights):
347
+ dxy = data_dxy[hi][:i1]
348
+ precond = theights[:, None] == h
349
+
350
+ trace_p = np.zeros((n_states, n_points, 2), dtype=FC.DTYPE)
351
+ trace_p[:] = points[:, :, :2] - rxyz[:, None, :2]
352
+ trace_l = np.zeros((n_states, n_points), dtype=FC.DTYPE)
353
+ trace_d = np.full((n_states, n_points), np.inf, dtype=FC.DTYPE)
354
+ h_trace_si = trace_si.copy()
355
+
356
+ # flake8: noqa: F821
357
+ def _update_wcoos(sel):
358
+ """Local function that updates coordinates and source times"""
359
+ nonlocal wcoosx, wcoosy, trace_si
360
+ d = np.linalg.norm(trace_p, axis=-1)
361
+ sel = sel & (d <= trace_d)
362
+ if np.any(sel):
363
+ trace_d[sel] = d[sel]
364
+
365
+ nx = dxy[h_trace_si[sel]]
366
+ dx = np.linalg.norm(nx, axis=-1)
367
+ nx /= dx[:, None]
368
+ trp = trace_p[sel]
369
+ projx = np.einsum("sd,sd->s", trp, nx)
370
+
371
+ seln = (projx > -dx) & (projx < dx)
372
+ if np.any(seln):
373
+ wcoosx[sel] = np.where(seln, projx + trace_l[sel], wcoosx[sel])
374
+
375
+ ny = np.concatenate([-nx[:, 1, None], nx[:, 0, None]], axis=1)
376
+ projy = np.einsum("sd,sd->s", trp, ny)
377
+ wcoosy[sel] = np.where(seln, projy, wcoosy[sel])
378
+ del ny, projy
379
+
380
+ trace_si[sel] = np.where(seln, h_trace_si[sel], trace_si[sel])
381
+
382
+ # step backwards in time, until wake source turbine is hit:
383
+ _update_wcoos(precond)
384
+ while True:
385
+ sel = precond & (h_trace_si > 0) & (trace_l < self.max_length_km * 1e3)
386
+ if np.any(sel):
387
+ h_trace_si[sel] -= 1
388
+
389
+ delta = dxy[h_trace_si[sel]]
390
+ dmag = np.linalg.norm(delta, axis=-1)
391
+ trace_p[sel] -= delta
392
+ trace_l[sel] += dmag
393
+ del delta, dmag
394
+
395
+ # check if this is closer to turbine:
396
+ _update_wcoos(sel)
397
+ del sel
398
+
399
+ else:
400
+ del sel
401
+ break
402
+ del trace_p, trace_l, trace_d, h_trace_si, dxy, precond
403
+
404
+ # store turbines that cause wake:
405
+ trace_si = np.minimum(trace_si, i0 + np.arange(n_states)[:, None])
269
406
  tdata[FC.STATE_SOURCE_ORDERI] = downwind_index
270
407
 
271
- # states that cause wake for each target point:
408
+ # store states that cause wake for each target point,
409
+ # will be used by model.get_data() during wake calculation:
272
410
  tdata.add(
273
411
  FC.STATES_SEL,
274
412
  trace_si.reshape(n_states, n_targets, n_tpoints),
@@ -301,4 +439,67 @@ class Timelines(WakeFrame):
301
439
  The centreline points, shape: (n_states, n_points, 3)
302
440
 
303
441
  """
304
- raise NotImplementedError
442
+ # prepare:
443
+ n_states, n_points = x.shape
444
+ rxyz = fdata[FV.TXYH][:, downwind_index]
445
+ theights = fdata[FV.H][:, downwind_index]
446
+ heights = self.timelines_data["height"].to_numpy()
447
+ data_dxy = self.timelines_data["dxy"].to_numpy()
448
+
449
+ points = np.zeros((n_states, n_points, 3), dtype=FC.DTYPE)
450
+ points[:] = rxyz[:, None, :]
451
+
452
+ trace_dp = np.zeros_like(points[..., :2])
453
+ trace_l = x.copy()
454
+ trace_si = np.zeros((n_states, n_points), dtype=FC.ITYPE)
455
+ trace_si[:] = np.arange(n_states)[:, None]
456
+
457
+ for hi, h in enumerate(heights):
458
+ precond = theights == h
459
+ if np.any(precond):
460
+ sel = precond[:, None] & (trace_l > 0)
461
+ while np.any(sel):
462
+ dxy = data_dxy[hi][trace_si[sel]]
463
+
464
+ trl = trace_l[sel]
465
+ trp = trace_dp[sel]
466
+ dl = np.linalg.norm(dxy, axis=-1)
467
+ cl = np.abs(trl - dl) < np.abs(trl)
468
+ if np.any(cl):
469
+ trace_l[sel] = np.where(cl, trl - dl, trl)
470
+ trace_dp[sel] = np.where(cl[:, None], trp + dxy, trp)
471
+ del trl, trp, dl, cl, dxy
472
+
473
+ trace_si[sel] -= 1
474
+ sel = precond[:, None] & (trace_l > 0) & (trace_si >= 0)
475
+
476
+ si = trace_si[precond] + 1
477
+ dxy = data_dxy[hi][si]
478
+ dl = np.linalg.norm(dxy, axis=-1)[:, :, None]
479
+ trl = trace_l[precond][:, :, None]
480
+ trp = trace_dp[precond]
481
+ sel = np.abs(trl) < 2 * dl
482
+ trace_dp[precond] = np.where(sel, trp - trl / dl * dxy, np.nan)
483
+
484
+ del si, dxy, dl, trl, trp, sel
485
+ del precond
486
+ del trace_si, trace_l
487
+
488
+ points[..., :2] += trace_dp
489
+
490
+ return points
491
+
492
+ def finalize(self, algo, verbosity=0):
493
+ """
494
+ Finalizes the model.
495
+
496
+ Parameters
497
+ ----------
498
+ algo: foxes.core.Algorithm
499
+ The calculation algorithm
500
+ verbosity: int
501
+ The verbosity level, 0 = silent
502
+
503
+ """
504
+ super().finalize(algo, verbosity=verbosity)
505
+ self.timelines_data = None
@@ -44,6 +44,7 @@ class YawedWakes(WakeFrame):
44
44
  alpha=0.58,
45
45
  beta=0.07,
46
46
  induction="Madsen",
47
+ max_length_km=30,
47
48
  **wake_k,
48
49
  ):
49
50
  """
@@ -63,9 +64,11 @@ class YawedWakes(WakeFrame):
63
64
  The induction model, if not found in wake model
64
65
  wake_k: dict, optional
65
66
  Parameters for the WakeK class, if not found in wake model
67
+ max_length_km: float
68
+ The maximal wake length in km
66
69
 
67
70
  """
68
- super().__init__()
71
+ super().__init__(max_length_km=max_length_km)
69
72
 
70
73
  self.base_frame = base_frame
71
74
  self.model = None
@@ -134,7 +137,7 @@ class YawedWakes(WakeFrame):
134
137
  super().initialize(algo, verbosity, force)
135
138
 
136
139
  def calc_order(self, algo, mdata, fdata):
137
- """ "
140
+ """
138
141
  Calculates the order of turbine evaluation.
139
142
 
140
143
  This function is executed on a single chunk of data,
@@ -174,7 +177,7 @@ class YawedWakes(WakeFrame):
174
177
  downwind_index=downwind_index,
175
178
  accept_nan=False,
176
179
  )
177
- gamma *= np.pi / 180
180
+ gamma = gamma * np.pi / 180
178
181
 
179
182
  # get k:
180
183
  k = self.wake_k(
@@ -245,7 +248,7 @@ class YawedWakes(WakeFrame):
245
248
  The target point data
246
249
  downwind_index: int
247
250
  The index of the wake causing turbine
248
- in the downwnd order
251
+ in the downwind order
249
252
 
250
253
  Returns
251
254
  -------
@@ -1,5 +1,4 @@
1
1
  from abc import abstractmethod
2
- import numpy as np
3
2
 
4
3
  from foxes.core import WakeModel
5
4
 
@@ -143,7 +142,7 @@ class DistSlicedWakeModel(WakeModel):
143
142
  The target point data
144
143
  downwind_index: int
145
144
  The index of the wake causing turbine
146
- in the downwnd order
145
+ in the downwind order
147
146
  wake_coos: numpy.ndarray
148
147
  The wake frame coordinates of the evaluation
149
148
  points, shape: (n_states, n_targets, n_tpoints, 3)
@@ -153,8 +152,7 @@ class DistSlicedWakeModel(WakeModel):
153
152
  (n_states, n_targets, n_tpoints, ...)
154
153
 
155
154
  """
156
- # rounding for safe x > 0 conditions
157
- x = np.round(wake_coos[:, :, 0, 0], 12)
155
+ x = wake_coos[:, :, 0, 0]
158
156
  yz = wake_coos[..., 1:3]
159
157
 
160
158
  wdeltas, st_sel = self.calc_wakes_x_yz(
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
2
 
3
3
  from foxes.core import TurbineInductionModel
4
- from foxes.utils import uv2wd, wd2uv
4
+ from foxes.utils import uv2wd, wd2uv, delta_wd
5
5
  import foxes.variables as FV
6
6
  import foxes.constants as FC
7
7
 
@@ -134,7 +134,7 @@ class RankineHalfBody(TurbineInductionModel):
134
134
  The target point data
135
135
  downwind_index: int
136
136
  The index of the wake causing turbine
137
- in the downwnd order
137
+ in the downwind order
138
138
  wake_coos: numpy.ndarray
139
139
  The wake frame coordinates of the evaluation
140
140
  points, shape: (n_states, n_targets, n_tpoints, 3)
@@ -198,7 +198,7 @@ class RankineHalfBody(TurbineInductionModel):
198
198
  xs = -np.sqrt(m / (4 * ws + 1e-15))
199
199
 
200
200
  # set values out of body shape
201
- st_sel = (ct > 0) & ((RHB_shape < -1) | (x < xs))
201
+ st_sel = (ct > 1e-8) & ((RHB_shape < -1) | (x < xs))
202
202
  if np.any(st_sel):
203
203
  # apply selection
204
204
  xyz = wake_coos[st_sel]
@@ -209,7 +209,7 @@ class RankineHalfBody(TurbineInductionModel):
209
209
  wake_deltas["V"][st_sel] += vel_factor * xyz[:, 1]
210
210
 
211
211
  # set values inside body shape
212
- st_sel = (ct > 0) & (RHB_shape >= -1) & (x >= xs) & (x <= 0)
212
+ st_sel = (ct > 1e-8) & (RHB_shape >= -1) & (x >= xs) & (x <= 0)
213
213
  if np.any(st_sel):
214
214
  # apply selection
215
215
  xyz = np.zeros_like(wake_coos[st_sel])
@@ -270,4 +270,4 @@ class RankineHalfBody(TurbineInductionModel):
270
270
  new_wd = uv2wd(wind_vec)
271
271
  new_ws = np.linalg.norm(wind_vec, axis=-1)
272
272
  wake_deltas[FV.WS] += new_ws - amb_results[FV.WS]
273
- wake_deltas[FV.WD] += new_wd - amb_results[FV.WD]
273
+ wake_deltas[FV.WD] += delta_wd(amb_results[FV.WD], new_wd)