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