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