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
@@ -16,8 +16,6 @@ class Streamlines2D(WakeFrame):
16
16
  ----------
17
17
  step: float
18
18
  The streamline step size in m
19
- max_length: float
20
- The maximal streamline length
21
19
  cl_ipars: dict
22
20
  Interpolation parameters for centre line
23
21
  point interpolation
@@ -26,7 +24,7 @@ class Streamlines2D(WakeFrame):
26
24
 
27
25
  """
28
26
 
29
- def __init__(self, step, max_length=1e4, cl_ipars={}):
27
+ def __init__(self, step, max_length_km=20, cl_ipars={}, **kwargs):
30
28
  """
31
29
  Constructor.
32
30
 
@@ -34,16 +32,17 @@ class Streamlines2D(WakeFrame):
34
32
  ----------
35
33
  step: float
36
34
  The streamline step size in m
37
- max_length: float
38
- The maximal streamline length
35
+ max_length_km: float
36
+ The maximal streamline length in km
39
37
  cl_ipars: dict
40
38
  Interpolation parameters for centre line
41
39
  point interpolation
40
+ kwargs: dict, optional
41
+ Additional parameters for the base class
42
42
 
43
43
  """
44
- super().__init__()
44
+ super().__init__(max_length_km=max_length_km, **kwargs)
45
45
  self.step = step
46
- self.max_length = max_length
47
46
  self.cl_ipars = cl_ipars
48
47
 
49
48
  self.DATA = self.var("DATA")
@@ -51,7 +50,9 @@ class Streamlines2D(WakeFrame):
51
50
  self.SDAT = self.var("SDAT")
52
51
 
53
52
  def __repr__(self):
54
- return f"{type(self).__name__}(step={self.step})"
53
+ return (
54
+ f"{type(self).__name__}(step={self.step}, max_length={self.max_length_km})"
55
+ )
55
56
 
56
57
  def _calc_streamlines(self, algo, mdata, fdata):
57
58
  """
@@ -60,7 +61,7 @@ class Streamlines2D(WakeFrame):
60
61
  # prepare:
61
62
  n_states = mdata.n_states
62
63
  n_turbines = mdata.n_turbines
63
- N = int(self.max_length / self.step)
64
+ N = int(self.max_length_km * 1e3 / self.step)
64
65
 
65
66
  # calc data: x, y, z, wd
66
67
  data = np.zeros((n_states, n_turbines, N, 4), dtype=FC.DTYPE)
@@ -134,7 +135,6 @@ class Streamlines2D(WakeFrame):
134
135
  Helper function, calculates streamline coordinates
135
136
  for given points and given turbine
136
137
  """
137
-
138
138
  # prepare:
139
139
  n_states, n_targets, n_tpoints = targets.shape[:3]
140
140
  n_points = n_targets * n_tpoints
@@ -151,13 +151,16 @@ class Streamlines2D(WakeFrame):
151
151
  del dists, selp
152
152
 
153
153
  # calculate coordinates:
154
- coos = np.zeros((n_states, n_points, 3), dtype=FC.DTYPE)
155
- nx = wd2uv(data[:, :, 3])
156
- ny = np.stack([-nx[:, :, 1], nx[:, :, 0]], axis=2)
157
- delta = points[:, :, :2] - data[:, :, :2]
158
- coos[:, :, 0] = slen + np.einsum("spd,spd->sp", delta, nx)
159
- coos[:, :, 1] = np.einsum("spd,spd->sp", delta, ny)
154
+ coos = np.full((n_states, n_points, 3), np.nan, dtype=FC.DTYPE)
160
155
  coos[:, :, 2] = points[:, :, 2] - data[:, :, 2]
156
+ delta = points[:, :, :2] - data[:, :, :2]
157
+ nx = wd2uv(data[:, :, 3])
158
+ projx = np.einsum("spd,spd->sp", delta, nx)
159
+ sel = (projx > -self.step) & (projx < self.step)
160
+ if np.any(sel):
161
+ ny = np.stack([-nx[:, :, 1], nx[:, :, 0]], axis=2)
162
+ coos[sel, 0] = slen[sel] + projx[sel]
163
+ coos[sel, 1] = np.einsum("spd,spd->sp", delta, ny)[sel]
161
164
 
162
165
  return coos.reshape(n_states, n_targets, n_tpoints, 3)
163
166
 
@@ -262,10 +265,6 @@ class Streamlines2D(WakeFrame):
262
265
  The centreline points, shape: (n_states, n_points, 3)
263
266
 
264
267
  """
265
- # calculate long enough streamlines:
266
- xmax = np.max(x)
267
- self._ensure_min_length(algo, mdata, fdata, xmax)
268
-
269
268
  # get streamline points:
270
269
  n_states, n_points = x.shape
271
270
  data = self.get_streamline_data(algo, mdata, fdata)[:, downwind_index]
@@ -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,47 +74,194 @@ 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
+ ):
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
+ ):
126
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
  """ "
@@ -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(
@@ -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
@@ -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
 
@@ -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(