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
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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(
|
|
62
|
+
times = np.asarray(states.index())
|
|
72
63
|
if self.dt_min is None:
|
|
73
64
|
if not np.issubdtype(times.dtype, np.datetime64):
|
|
74
65
|
raise TypeError(
|
|
@@ -83,50 +74,197 @@ class Timelines(WakeFrame):
|
|
|
83
74
|
n = max(len(times) - 1, 1)
|
|
84
75
|
dt = np.full(n, self.dt_min * 60, dtype="timedelta64[s]").astype(FC.ITYPE)
|
|
85
76
|
|
|
86
|
-
# calculate horizontal wind vector in all states:
|
|
87
|
-
self._uv = np.zeros((algo.n_states, 1, 3), dtype=FC.DTYPE)
|
|
88
|
-
|
|
89
77
|
# prepare mdata:
|
|
90
|
-
|
|
91
|
-
mdict = {v: d
|
|
92
|
-
mdims = {v:
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
191
|
+
self._precalc_data(algo, algo.states, heights, verbosity)
|
|
192
|
+
|
|
193
|
+
def set_running(
|
|
194
|
+
self,
|
|
195
|
+
algo,
|
|
196
|
+
data_stash,
|
|
197
|
+
sel=None,
|
|
198
|
+
isel=None,
|
|
199
|
+
verbosity=0,
|
|
200
|
+
):
|
|
126
201
|
"""
|
|
202
|
+
Sets this model status to running, and moves
|
|
203
|
+
all large data to stash.
|
|
204
|
+
|
|
205
|
+
The stashed data will be returned by the
|
|
206
|
+
unset_running() function after running calculations.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
algo: foxes.core.Algorithm
|
|
211
|
+
The calculation algorithm
|
|
212
|
+
data_stash: dict
|
|
213
|
+
Large data stash, this function adds data here.
|
|
214
|
+
Key: model name. Value: dict, large model data
|
|
215
|
+
sel: dict, optional
|
|
216
|
+
The subset selection dictionary
|
|
217
|
+
isel: dict, optional
|
|
218
|
+
The index subset selection dictionary
|
|
219
|
+
verbosity: int
|
|
220
|
+
The verbosity level, 0 = silent
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
super().set_running(algo, data_stash, sel, isel, verbosity)
|
|
224
|
+
|
|
225
|
+
if sel is not None or isel is not None:
|
|
226
|
+
data_stash[self.name]["data"] = self.timelines_data
|
|
227
|
+
|
|
228
|
+
if isel is not None:
|
|
229
|
+
self.timelines_data = self.timelines_data.isel(isel)
|
|
230
|
+
if sel is not None:
|
|
231
|
+
self.timelines_data = self.timelines_data.sel(sel)
|
|
232
|
+
|
|
233
|
+
def unset_running(
|
|
234
|
+
self,
|
|
235
|
+
algo,
|
|
236
|
+
data_stash,
|
|
237
|
+
sel=None,
|
|
238
|
+
isel=None,
|
|
239
|
+
verbosity=0,
|
|
240
|
+
):
|
|
241
|
+
"""
|
|
242
|
+
Sets this model status to not running, recovering large data
|
|
243
|
+
from stash
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
algo: foxes.core.Algorithm
|
|
248
|
+
The calculation algorithm
|
|
249
|
+
data_stash: dict
|
|
250
|
+
Large data stash, this function adds data here.
|
|
251
|
+
Key: model name. Value: dict, large model data
|
|
252
|
+
sel: dict, optional
|
|
253
|
+
The subset selection dictionary
|
|
254
|
+
isel: dict, optional
|
|
255
|
+
The index subset selection dictionary
|
|
256
|
+
verbosity: int
|
|
257
|
+
The verbosity level, 0 = silent
|
|
258
|
+
|
|
259
|
+
"""
|
|
260
|
+
super().unset_running(algo, data_stash, sel, isel, verbosity)
|
|
261
|
+
|
|
262
|
+
data = data_stash[self.name]
|
|
263
|
+
if "data" in data:
|
|
264
|
+
self.timelines_data = data.pop("data")
|
|
127
265
|
|
|
128
266
|
def calc_order(self, algo, mdata, fdata):
|
|
129
|
-
"""
|
|
267
|
+
"""
|
|
130
268
|
Calculates the order of turbine evaluation.
|
|
131
269
|
|
|
132
270
|
This function is executed on a single chunk of data,
|
|
@@ -147,25 +285,8 @@ class Timelines(WakeFrame):
|
|
|
147
285
|
The turbine order, shape: (n_states, n_turbines)
|
|
148
286
|
|
|
149
287
|
"""
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
n_turbines = algo.n_turbines
|
|
153
|
-
tdata = TData.from_points(points=fdata[FV.TXYH])
|
|
154
|
-
|
|
155
|
-
# calculate streamline x coordinates for turbines rotor centre points:
|
|
156
|
-
# n_states, n_turbines_source, n_turbines_target
|
|
157
|
-
coosx = np.zeros((n_states, n_turbines, n_turbines), dtype=FC.DTYPE)
|
|
158
|
-
for ti in range(n_turbines):
|
|
159
|
-
coosx[:, ti, :] = self.get_wake_coos(algo, mdata, fdata, tdata, ti)[
|
|
160
|
-
:, :, 0, 0
|
|
161
|
-
]
|
|
162
|
-
|
|
163
|
-
# derive turbine order:
|
|
164
|
-
# TODO: Remove loop over states
|
|
165
|
-
order = np.zeros((n_states, n_turbines), dtype=FC.ITYPE)
|
|
166
|
-
for si in range(n_states):
|
|
167
|
-
order[si] = np.lexsort(keys=coosx[si])
|
|
168
|
-
|
|
288
|
+
order = np.zeros((fdata.n_states, fdata.n_turbines), dtype=FC.ITYPE)
|
|
289
|
+
order[:] = np.arange(fdata.n_turbines)[None, :]
|
|
169
290
|
return order
|
|
170
291
|
|
|
171
292
|
def get_wake_coos(
|
|
@@ -191,7 +312,7 @@ class Timelines(WakeFrame):
|
|
|
191
312
|
The target point data
|
|
192
313
|
downwind_index: int
|
|
193
314
|
The index of the wake causing turbine
|
|
194
|
-
in the
|
|
315
|
+
in the downwind order
|
|
195
316
|
|
|
196
317
|
Returns
|
|
197
318
|
-------
|
|
@@ -206,69 +327,86 @@ class Timelines(WakeFrame):
|
|
|
206
327
|
n_points = n_targets * n_tpoints
|
|
207
328
|
points = targets.reshape(n_states, n_points, 3)
|
|
208
329
|
rxyz = fdata[FV.TXYH][:, downwind_index]
|
|
330
|
+
theights = fdata[FV.H][:, downwind_index]
|
|
331
|
+
heights = self.timelines_data["height"].to_numpy()
|
|
332
|
+
data_dxy = self.timelines_data["dxy"].to_numpy()
|
|
209
333
|
|
|
210
334
|
D = np.zeros((n_states, n_points), dtype=FC.DTYPE)
|
|
211
335
|
D[:] = fdata[FV.D][:, downwind_index, None]
|
|
212
336
|
|
|
213
|
-
i0 = mdata.states_i0(counter=True, algo=algo)
|
|
214
|
-
i1 = i0 + mdata.n_states
|
|
215
|
-
dxy = self._dxy[:i1]
|
|
216
|
-
|
|
217
|
-
trace_p = np.zeros((n_states, n_points, 2), dtype=FC.DTYPE)
|
|
218
|
-
trace_p[:] = points[:, :, :2] - rxyz[:, None, :2]
|
|
219
|
-
trace_l = np.zeros((n_states, n_points), dtype=FC.DTYPE)
|
|
220
|
-
trace_d = np.full((n_states, n_points), np.inf, dtype=FC.DTYPE)
|
|
221
|
-
trace_si = np.zeros((n_states, n_points), dtype=FC.ITYPE)
|
|
222
|
-
trace_si[:] = i0 + np.arange(n_states)[:, None] + 1
|
|
223
|
-
|
|
224
337
|
wcoos = np.full((n_states, n_points, 3), 1e20, dtype=FC.DTYPE)
|
|
225
338
|
wcoosx = wcoos[:, :, 0]
|
|
226
339
|
wcoosy = wcoos[:, :, 1]
|
|
227
340
|
wcoos[:, :, 2] = points[:, :, 2] - rxyz[:, None, 2]
|
|
228
|
-
del rxyz
|
|
229
|
-
|
|
230
|
-
while True:
|
|
231
|
-
sel = (trace_si > 0) & (trace_l < self.max_wake_length)
|
|
232
|
-
if np.any(sel):
|
|
233
|
-
trace_si[sel] -= 1
|
|
234
|
-
|
|
235
|
-
delta = dxy[trace_si[sel]]
|
|
236
|
-
dmag = np.linalg.norm(delta, axis=-1)
|
|
237
|
-
|
|
238
|
-
trace_p[sel] -= delta
|
|
239
|
-
trace_l[sel] += dmag
|
|
240
|
-
|
|
241
|
-
trp = trace_p[sel]
|
|
242
|
-
d0 = trace_d[sel]
|
|
243
|
-
d = np.linalg.norm(trp, axis=-1)
|
|
244
|
-
trace_d[sel] = d
|
|
245
|
-
|
|
246
|
-
seln = d <= np.minimum(d0, 2 * dmag)
|
|
247
|
-
if np.any(seln):
|
|
248
|
-
htrp = trp[seln]
|
|
249
|
-
raxis = delta[seln]
|
|
250
|
-
raxis = raxis / np.linalg.norm(raxis, axis=-1)[:, None]
|
|
251
|
-
saxis = np.concatenate(
|
|
252
|
-
[-raxis[:, 1, None], raxis[:, 0, None]], axis=1
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
wcx = wcoosx[sel]
|
|
256
|
-
wcx[seln] = np.einsum("sd,sd->s", htrp, raxis) + trace_l[sel][seln]
|
|
257
|
-
wcoosx[sel] = wcx
|
|
258
|
-
del wcx, raxis
|
|
259
|
-
|
|
260
|
-
wcy = wcoosy[sel]
|
|
261
|
-
wcy[seln] = np.einsum("sd,sd->s", htrp, saxis)
|
|
262
|
-
wcoosy[sel] = wcy
|
|
263
|
-
del wcy, saxis, htrp
|
|
264
|
-
|
|
265
|
-
else:
|
|
266
|
-
break
|
|
267
341
|
|
|
268
|
-
|
|
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
|
-
|
|
442
|
+
# prepare:
|
|
443
|
+
n_states, n_points = x.shape
|
|
444
|
+
rxyz = fdata[FV.TXYH][:, downwind_index]
|
|
445
|
+
theights = fdata[FV.H][:, downwind_index]
|
|
446
|
+
heights = self.timelines_data["height"].to_numpy()
|
|
447
|
+
data_dxy = self.timelines_data["dxy"].to_numpy()
|
|
448
|
+
|
|
449
|
+
points = np.zeros((n_states, n_points, 3), dtype=FC.DTYPE)
|
|
450
|
+
points[:] = rxyz[:, None, :]
|
|
451
|
+
|
|
452
|
+
trace_dp = np.zeros_like(points[..., :2])
|
|
453
|
+
trace_l = x.copy()
|
|
454
|
+
trace_si = np.zeros((n_states, n_points), dtype=FC.ITYPE)
|
|
455
|
+
trace_si[:] = np.arange(n_states)[:, None]
|
|
456
|
+
|
|
457
|
+
for hi, h in enumerate(heights):
|
|
458
|
+
precond = theights == h
|
|
459
|
+
if np.any(precond):
|
|
460
|
+
sel = precond[:, None] & (trace_l > 0)
|
|
461
|
+
while np.any(sel):
|
|
462
|
+
dxy = data_dxy[hi][trace_si[sel]]
|
|
463
|
+
|
|
464
|
+
trl = trace_l[sel]
|
|
465
|
+
trp = trace_dp[sel]
|
|
466
|
+
dl = np.linalg.norm(dxy, axis=-1)
|
|
467
|
+
cl = np.abs(trl - dl) < np.abs(trl)
|
|
468
|
+
if np.any(cl):
|
|
469
|
+
trace_l[sel] = np.where(cl, trl - dl, trl)
|
|
470
|
+
trace_dp[sel] = np.where(cl[:, None], trp + dxy, trp)
|
|
471
|
+
del trl, trp, dl, cl, dxy
|
|
472
|
+
|
|
473
|
+
trace_si[sel] -= 1
|
|
474
|
+
sel = precond[:, None] & (trace_l > 0) & (trace_si >= 0)
|
|
475
|
+
|
|
476
|
+
si = trace_si[precond] + 1
|
|
477
|
+
dxy = data_dxy[hi][si]
|
|
478
|
+
dl = np.linalg.norm(dxy, axis=-1)[:, :, None]
|
|
479
|
+
trl = trace_l[precond][:, :, None]
|
|
480
|
+
trp = trace_dp[precond]
|
|
481
|
+
sel = np.abs(trl) < 2 * dl
|
|
482
|
+
trace_dp[precond] = np.where(sel, trp - trl / dl * dxy, np.nan)
|
|
483
|
+
|
|
484
|
+
del si, dxy, dl, trl, trp, sel
|
|
485
|
+
del precond
|
|
486
|
+
del trace_si, trace_l
|
|
487
|
+
|
|
488
|
+
points[..., :2] += trace_dp
|
|
489
|
+
|
|
490
|
+
return points
|
|
491
|
+
|
|
492
|
+
def finalize(self, algo, verbosity=0):
|
|
493
|
+
"""
|
|
494
|
+
Finalizes the model.
|
|
495
|
+
|
|
496
|
+
Parameters
|
|
497
|
+
----------
|
|
498
|
+
algo: foxes.core.Algorithm
|
|
499
|
+
The calculation algorithm
|
|
500
|
+
verbosity: int
|
|
501
|
+
The verbosity level, 0 = silent
|
|
502
|
+
|
|
503
|
+
"""
|
|
504
|
+
super().finalize(algo, verbosity=verbosity)
|
|
505
|
+
self.timelines_data = None
|
|
@@ -44,6 +44,7 @@ class YawedWakes(WakeFrame):
|
|
|
44
44
|
alpha=0.58,
|
|
45
45
|
beta=0.07,
|
|
46
46
|
induction="Madsen",
|
|
47
|
+
max_length_km=30,
|
|
47
48
|
**wake_k,
|
|
48
49
|
):
|
|
49
50
|
"""
|
|
@@ -63,9 +64,11 @@ class YawedWakes(WakeFrame):
|
|
|
63
64
|
The induction model, if not found in wake model
|
|
64
65
|
wake_k: dict, optional
|
|
65
66
|
Parameters for the WakeK class, if not found in wake model
|
|
67
|
+
max_length_km: float
|
|
68
|
+
The maximal wake length in km
|
|
66
69
|
|
|
67
70
|
"""
|
|
68
|
-
super().__init__()
|
|
71
|
+
super().__init__(max_length_km=max_length_km)
|
|
69
72
|
|
|
70
73
|
self.base_frame = base_frame
|
|
71
74
|
self.model = None
|
|
@@ -134,7 +137,7 @@ class YawedWakes(WakeFrame):
|
|
|
134
137
|
super().initialize(algo, verbosity, force)
|
|
135
138
|
|
|
136
139
|
def calc_order(self, algo, mdata, fdata):
|
|
137
|
-
"""
|
|
140
|
+
"""
|
|
138
141
|
Calculates the order of turbine evaluation.
|
|
139
142
|
|
|
140
143
|
This function is executed on a single chunk of data,
|
|
@@ -174,7 +177,7 @@ class YawedWakes(WakeFrame):
|
|
|
174
177
|
downwind_index=downwind_index,
|
|
175
178
|
accept_nan=False,
|
|
176
179
|
)
|
|
177
|
-
gamma
|
|
180
|
+
gamma = gamma * np.pi / 180
|
|
178
181
|
|
|
179
182
|
# get k:
|
|
180
183
|
k = self.wake_k(
|
|
@@ -245,7 +248,7 @@ class YawedWakes(WakeFrame):
|
|
|
245
248
|
The target point data
|
|
246
249
|
downwind_index: int
|
|
247
250
|
The index of the wake causing turbine
|
|
248
|
-
in the
|
|
251
|
+
in the downwind order
|
|
249
252
|
|
|
250
253
|
Returns
|
|
251
254
|
-------
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
-
import numpy as np
|
|
3
2
|
|
|
4
3
|
from foxes.core import WakeModel
|
|
5
4
|
|
|
@@ -143,7 +142,7 @@ class DistSlicedWakeModel(WakeModel):
|
|
|
143
142
|
The target point data
|
|
144
143
|
downwind_index: int
|
|
145
144
|
The index of the wake causing turbine
|
|
146
|
-
in the
|
|
145
|
+
in the downwind order
|
|
147
146
|
wake_coos: numpy.ndarray
|
|
148
147
|
The wake frame coordinates of the evaluation
|
|
149
148
|
points, shape: (n_states, n_targets, n_tpoints, 3)
|
|
@@ -153,8 +152,7 @@ class DistSlicedWakeModel(WakeModel):
|
|
|
153
152
|
(n_states, n_targets, n_tpoints, ...)
|
|
154
153
|
|
|
155
154
|
"""
|
|
156
|
-
|
|
157
|
-
x = np.round(wake_coos[:, :, 0, 0], 12)
|
|
155
|
+
x = wake_coos[:, :, 0, 0]
|
|
158
156
|
yz = wake_coos[..., 1:3]
|
|
159
157
|
|
|
160
158
|
wdeltas, st_sel = self.calc_wakes_x_yz(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
|
|
3
3
|
from foxes.core import TurbineInductionModel
|
|
4
|
-
from foxes.utils import uv2wd, wd2uv
|
|
4
|
+
from foxes.utils import uv2wd, wd2uv, delta_wd
|
|
5
5
|
import foxes.variables as FV
|
|
6
6
|
import foxes.constants as FC
|
|
7
7
|
|
|
@@ -134,7 +134,7 @@ class RankineHalfBody(TurbineInductionModel):
|
|
|
134
134
|
The target point data
|
|
135
135
|
downwind_index: int
|
|
136
136
|
The index of the wake causing turbine
|
|
137
|
-
in the
|
|
137
|
+
in the downwind order
|
|
138
138
|
wake_coos: numpy.ndarray
|
|
139
139
|
The wake frame coordinates of the evaluation
|
|
140
140
|
points, shape: (n_states, n_targets, n_tpoints, 3)
|
|
@@ -198,7 +198,7 @@ class RankineHalfBody(TurbineInductionModel):
|
|
|
198
198
|
xs = -np.sqrt(m / (4 * ws + 1e-15))
|
|
199
199
|
|
|
200
200
|
# set values out of body shape
|
|
201
|
-
st_sel = (ct >
|
|
201
|
+
st_sel = (ct > 1e-8) & ((RHB_shape < -1) | (x < xs))
|
|
202
202
|
if np.any(st_sel):
|
|
203
203
|
# apply selection
|
|
204
204
|
xyz = wake_coos[st_sel]
|
|
@@ -209,7 +209,7 @@ class RankineHalfBody(TurbineInductionModel):
|
|
|
209
209
|
wake_deltas["V"][st_sel] += vel_factor * xyz[:, 1]
|
|
210
210
|
|
|
211
211
|
# set values inside body shape
|
|
212
|
-
st_sel = (ct >
|
|
212
|
+
st_sel = (ct > 1e-8) & (RHB_shape >= -1) & (x >= xs) & (x <= 0)
|
|
213
213
|
if np.any(st_sel):
|
|
214
214
|
# apply selection
|
|
215
215
|
xyz = np.zeros_like(wake_coos[st_sel])
|
|
@@ -270,4 +270,4 @@ class RankineHalfBody(TurbineInductionModel):
|
|
|
270
270
|
new_wd = uv2wd(wind_vec)
|
|
271
271
|
new_ws = np.linalg.norm(wind_vec, axis=-1)
|
|
272
272
|
wake_deltas[FV.WS] += new_ws - amb_results[FV.WS]
|
|
273
|
-
wake_deltas[FV.WD] +=
|
|
273
|
+
wake_deltas[FV.WD] += delta_wd(amb_results[FV.WD], new_wd)
|