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
foxes/core/engine.py
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
from xarray import Dataset
|
|
6
|
+
|
|
7
|
+
from foxes.core import MData, FData, TData
|
|
8
|
+
from foxes.utils import all_subclasses
|
|
9
|
+
import foxes.constants as FC
|
|
10
|
+
|
|
11
|
+
__global_engine_data__ = dict(
|
|
12
|
+
engine=None,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Engine(ABC):
|
|
17
|
+
"""
|
|
18
|
+
Abstract base clas for foxes calculation engines
|
|
19
|
+
|
|
20
|
+
Attributes
|
|
21
|
+
----------
|
|
22
|
+
chunk_size_states: int
|
|
23
|
+
The size of a states chunk
|
|
24
|
+
chunk_size_points: int
|
|
25
|
+
The size of a points chunk
|
|
26
|
+
n_procs: int, optional
|
|
27
|
+
The number of processes to be used,
|
|
28
|
+
or None for automatic
|
|
29
|
+
verbosity: int
|
|
30
|
+
The verbosity level, 0 = silent
|
|
31
|
+
|
|
32
|
+
:group: core
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
chunk_size_states=None,
|
|
39
|
+
chunk_size_points=None,
|
|
40
|
+
n_procs=None,
|
|
41
|
+
verbosity=1,
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Constructor.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
chunk_size_states: int, optional
|
|
49
|
+
The size of a states chunk
|
|
50
|
+
chunk_size_points: int, optional
|
|
51
|
+
The size of a points chunk
|
|
52
|
+
n_procs: int, optional
|
|
53
|
+
The number of processes to be used,
|
|
54
|
+
or None for automatic
|
|
55
|
+
verbosity: int
|
|
56
|
+
The verbosity level, 0 = silent
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
self.chunk_size_states = chunk_size_states
|
|
60
|
+
self.chunk_size_points = chunk_size_points
|
|
61
|
+
try:
|
|
62
|
+
self.n_procs = n_procs if n_procs is not None else os.process_cpu_count()
|
|
63
|
+
except AttributeError:
|
|
64
|
+
self.n_procs = os.cpu_count()
|
|
65
|
+
self.verbosity = verbosity
|
|
66
|
+
self.__initialized = False
|
|
67
|
+
self.__entered = False
|
|
68
|
+
|
|
69
|
+
def __repr__(self):
|
|
70
|
+
s = f"n_procs={self.n_procs}, chunk_size_states={self.chunk_size_states}, chunk_size_points={self.chunk_size_points}"
|
|
71
|
+
return f"{type(self).__name__}({s})"
|
|
72
|
+
|
|
73
|
+
def __enter__(self):
|
|
74
|
+
if self.__entered:
|
|
75
|
+
raise ValueError(f"Enter called for already entered engine")
|
|
76
|
+
self.__entered = True
|
|
77
|
+
if not self.initialized:
|
|
78
|
+
self.initialize()
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def __exit__(self, *exit_args):
|
|
82
|
+
if not self.__entered:
|
|
83
|
+
raise ValueError(f"Exit called for not entered engine")
|
|
84
|
+
self.__entered = False
|
|
85
|
+
if self.initialized:
|
|
86
|
+
self.finalize(*exit_args)
|
|
87
|
+
|
|
88
|
+
def __del__(self):
|
|
89
|
+
if self.initialized:
|
|
90
|
+
self.finalize()
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def entered(self):
|
|
94
|
+
"""
|
|
95
|
+
Flag that this model has been entered.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
flag: bool
|
|
100
|
+
True if the model has been entered.
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
return self.__entered
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def initialized(self):
|
|
107
|
+
"""
|
|
108
|
+
Initialization flag.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
flag: bool
|
|
113
|
+
True if the model has been initialized.
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
return self.__initialized
|
|
117
|
+
|
|
118
|
+
def initialize(self):
|
|
119
|
+
"""
|
|
120
|
+
Initializes the engine.
|
|
121
|
+
"""
|
|
122
|
+
if not self.entered:
|
|
123
|
+
self.__enter__()
|
|
124
|
+
elif not self.initialized:
|
|
125
|
+
if get_engine(error=False, default=False) is not None:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"Cannot initialize engine '{type(self).__name__}', since engine already set to '{type(get_engine()).__name__}'"
|
|
128
|
+
)
|
|
129
|
+
global __global_engine_data__
|
|
130
|
+
__global_engine_data__["engine"] = self
|
|
131
|
+
self.__initialized = True
|
|
132
|
+
|
|
133
|
+
def finalize(self, type=None, value=None, traceback=None):
|
|
134
|
+
"""
|
|
135
|
+
Finalizes the engine.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
type: object, optional
|
|
140
|
+
Dummy argument for the exit function
|
|
141
|
+
value: object, optional
|
|
142
|
+
Dummy argument for the exit function
|
|
143
|
+
traceback: object, optional
|
|
144
|
+
Dummy argument for the exit function
|
|
145
|
+
|
|
146
|
+
"""
|
|
147
|
+
if self.entered:
|
|
148
|
+
self.__exit__(type, value, traceback)
|
|
149
|
+
elif self.initialized:
|
|
150
|
+
global __global_engine_data__
|
|
151
|
+
__global_engine_data__["engine"] = None
|
|
152
|
+
self.__initialized = False
|
|
153
|
+
|
|
154
|
+
def print(self, *args, level=1, **kwargs):
|
|
155
|
+
"""Prints based on verbosity"""
|
|
156
|
+
if self.verbosity >= level:
|
|
157
|
+
print(*args, **kwargs)
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def loop_dims(self):
|
|
161
|
+
"""
|
|
162
|
+
Gets the loop dimensions (possibly chunked)
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
dims: list of str
|
|
167
|
+
The loop dimensions (possibly chunked)
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
if self.chunk_size_states is None and self.chunk_size_states is None:
|
|
171
|
+
return []
|
|
172
|
+
elif self.chunk_size_states is None:
|
|
173
|
+
return [FC.TARGET]
|
|
174
|
+
elif self.chunk_size_points is None:
|
|
175
|
+
return [FC.STATE]
|
|
176
|
+
else:
|
|
177
|
+
return [FC.STATE, FC.TARGET]
|
|
178
|
+
|
|
179
|
+
def select_subsets(self, *datasets, sel=None, isel=None):
|
|
180
|
+
"""
|
|
181
|
+
Takes subsets of datasets
|
|
182
|
+
|
|
183
|
+
Parameters
|
|
184
|
+
----------
|
|
185
|
+
datasets: tuple
|
|
186
|
+
The xarray.Dataset or xarray.Dataarray objects
|
|
187
|
+
sel: dict, optional
|
|
188
|
+
The selection dictionary
|
|
189
|
+
isel: dict, optional
|
|
190
|
+
The index selection dictionary
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
subsets: list
|
|
195
|
+
The subsets of the input data
|
|
196
|
+
|
|
197
|
+
"""
|
|
198
|
+
if sel is not None:
|
|
199
|
+
new_datasets = []
|
|
200
|
+
for data in datasets:
|
|
201
|
+
if data is not None:
|
|
202
|
+
s = {c: u for c, u in sel.items() if c in data.coords}
|
|
203
|
+
new_datasets.append(data.sel(s) if len(s) else data)
|
|
204
|
+
else:
|
|
205
|
+
new_datasets.append(data)
|
|
206
|
+
datasets = new_datasets
|
|
207
|
+
|
|
208
|
+
if isel is not None:
|
|
209
|
+
new_datasets = []
|
|
210
|
+
for data in datasets:
|
|
211
|
+
if data is not None:
|
|
212
|
+
s = {c: u for c, u in isel.items() if c in data.coords}
|
|
213
|
+
new_datasets.append(data.isel(s) if len(s) else data)
|
|
214
|
+
else:
|
|
215
|
+
new_datasets.append(data)
|
|
216
|
+
datasets = new_datasets
|
|
217
|
+
|
|
218
|
+
return datasets
|
|
219
|
+
|
|
220
|
+
def calc_chunk_sizes(self, n_states, n_targets=1):
|
|
221
|
+
"""
|
|
222
|
+
Computes the sizes of states and points chunks
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
n_states: int
|
|
227
|
+
The number of states
|
|
228
|
+
n_targets: int
|
|
229
|
+
The number of point targets
|
|
230
|
+
|
|
231
|
+
Returns
|
|
232
|
+
-------
|
|
233
|
+
chunk_sizes_states: numpy.ndarray
|
|
234
|
+
The sizes of all states chunks, shape: (n_chunks_states,)
|
|
235
|
+
chunk_sizes_targets: numpy.ndarray
|
|
236
|
+
The sizes of all targets chunks, shape: (n_chunks_targets,)
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
# determine states chunks:
|
|
240
|
+
if self.chunk_size_states is None:
|
|
241
|
+
n_chunks_states = min(self.n_procs, n_states)
|
|
242
|
+
chunk_size_states = max(int(n_states / self.n_procs), 1)
|
|
243
|
+
else:
|
|
244
|
+
chunk_size_states = min(n_states, self.chunk_size_states)
|
|
245
|
+
n_chunks_states = max(int(n_states / chunk_size_states), 1)
|
|
246
|
+
if int(n_states / n_chunks_states) > chunk_size_states:
|
|
247
|
+
n_chunks_states += 1
|
|
248
|
+
chunk_size_states = int(n_states / n_chunks_states)
|
|
249
|
+
chunk_sizes_states = np.full(n_chunks_states, chunk_size_states)
|
|
250
|
+
extra = n_states - n_chunks_states * chunk_size_states
|
|
251
|
+
if extra > 0:
|
|
252
|
+
chunk_sizes_states[-extra:] += 1
|
|
253
|
+
|
|
254
|
+
s = np.sum(chunk_sizes_states)
|
|
255
|
+
assert (
|
|
256
|
+
s == n_states
|
|
257
|
+
), f"States count mismatch: Expecting {n_states}, chunks sum is {s}. Chunks: {[int(c) for c in chunk_sizes_states]}"
|
|
258
|
+
|
|
259
|
+
# determine points chunks:
|
|
260
|
+
chunk_sizes_targets = [n_targets]
|
|
261
|
+
if n_targets > 1:
|
|
262
|
+
if self.chunk_size_points is None:
|
|
263
|
+
if n_chunks_states == 1:
|
|
264
|
+
n_chunks_targets = min(self.n_procs, n_targets)
|
|
265
|
+
chunk_size_targets = max(int(n_targets / self.n_procs), 1)
|
|
266
|
+
else:
|
|
267
|
+
chunk_size_targets = n_targets
|
|
268
|
+
n_chunks_targets = 1
|
|
269
|
+
else:
|
|
270
|
+
chunk_size_targets = min(n_targets, self.chunk_size_points)
|
|
271
|
+
n_chunks_targets = max(int(n_targets / chunk_size_targets), 1)
|
|
272
|
+
if int(n_targets / n_chunks_targets) > chunk_size_targets:
|
|
273
|
+
n_chunks_targets += 1
|
|
274
|
+
chunk_size_targets = int(n_targets / n_chunks_targets)
|
|
275
|
+
chunk_sizes_targets = np.full(n_chunks_targets, chunk_size_targets)
|
|
276
|
+
extra = n_targets - n_chunks_targets * chunk_size_targets
|
|
277
|
+
if extra > 0:
|
|
278
|
+
chunk_sizes_targets[-extra:] += 1
|
|
279
|
+
|
|
280
|
+
s = np.sum(chunk_sizes_targets)
|
|
281
|
+
assert (
|
|
282
|
+
s == n_targets
|
|
283
|
+
), f"Targets count mismatch: Expecting {n_targets}, chunks sum is {s}. Chunks: {[int(c) for c in chunk_sizes_targets]}"
|
|
284
|
+
|
|
285
|
+
return chunk_sizes_states, chunk_sizes_targets
|
|
286
|
+
|
|
287
|
+
def get_chunk_input_data(
|
|
288
|
+
self,
|
|
289
|
+
algo,
|
|
290
|
+
model_data,
|
|
291
|
+
farm_data,
|
|
292
|
+
point_data,
|
|
293
|
+
states_i0_i1,
|
|
294
|
+
targets_i0_i1,
|
|
295
|
+
out_vars,
|
|
296
|
+
):
|
|
297
|
+
"""
|
|
298
|
+
Extracts the data for a single chunk calculation
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
algo: foxes.core.Algorithm
|
|
303
|
+
The algorithm object
|
|
304
|
+
model_data: xarray.Dataset
|
|
305
|
+
The initial model data
|
|
306
|
+
farm_data: xarray.Dataset
|
|
307
|
+
The initial farm data
|
|
308
|
+
point_data: xarray.Dataset
|
|
309
|
+
The initial point data
|
|
310
|
+
states_i0_i1: tuple
|
|
311
|
+
The (start, end) values of the states
|
|
312
|
+
targets_i0_i1: tuple
|
|
313
|
+
The (start, end) values of the targets
|
|
314
|
+
out_vars: list of str
|
|
315
|
+
Names of the output variables
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
data: list of foxes.core.Data
|
|
320
|
+
Either [mdata, fdata] or [mdata, fdata, tdata]
|
|
321
|
+
|
|
322
|
+
"""
|
|
323
|
+
# prepare:
|
|
324
|
+
i0_states, i1_states = states_i0_i1
|
|
325
|
+
i0_targets, i1_targets = targets_i0_i1
|
|
326
|
+
s_states = np.s_[i0_states:i1_states]
|
|
327
|
+
s_targets = np.s_[i0_targets:i1_targets]
|
|
328
|
+
|
|
329
|
+
# create mdata:
|
|
330
|
+
mdata = MData.from_dataset(
|
|
331
|
+
model_data,
|
|
332
|
+
s_states=s_states,
|
|
333
|
+
loop_dims=[FC.STATE],
|
|
334
|
+
states_i0=i0_states,
|
|
335
|
+
copy=True,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# create fdata:
|
|
339
|
+
if point_data is None:
|
|
340
|
+
|
|
341
|
+
def cb(data, dims):
|
|
342
|
+
n_states = i1_states - i0_states
|
|
343
|
+
for o in set(out_vars).difference(data.keys()):
|
|
344
|
+
data[o] = np.full(
|
|
345
|
+
(n_states, algo.n_turbines), np.nan, dtype=FC.DTYPE
|
|
346
|
+
)
|
|
347
|
+
dims[o] = (FC.STATE, FC.TURBINE)
|
|
348
|
+
|
|
349
|
+
else:
|
|
350
|
+
cb = None
|
|
351
|
+
fdata = FData.from_dataset(
|
|
352
|
+
farm_data,
|
|
353
|
+
mdata=mdata,
|
|
354
|
+
s_states=s_states,
|
|
355
|
+
callback=cb,
|
|
356
|
+
loop_dims=[FC.STATE],
|
|
357
|
+
states_i0=i0_states,
|
|
358
|
+
copy=True,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# create tdata:
|
|
362
|
+
tdata = None
|
|
363
|
+
if point_data is not None:
|
|
364
|
+
|
|
365
|
+
def cb(data, dims):
|
|
366
|
+
n_states = i1_states - i0_states
|
|
367
|
+
n_targets = i1_targets - i0_targets
|
|
368
|
+
for o in set(out_vars).difference(data.keys()):
|
|
369
|
+
data[o] = np.full((n_states, n_targets, 1), np.nan, dtype=FC.DTYPE)
|
|
370
|
+
dims[o] = (FC.STATE, FC.TARGET, FC.TPOINT)
|
|
371
|
+
|
|
372
|
+
tdata = TData.from_dataset(
|
|
373
|
+
point_data,
|
|
374
|
+
mdata=mdata,
|
|
375
|
+
s_states=s_states,
|
|
376
|
+
s_targets=s_targets,
|
|
377
|
+
callback=cb,
|
|
378
|
+
loop_dims=[FC.STATE, FC.TARGET],
|
|
379
|
+
states_i0=i0_states,
|
|
380
|
+
copy=True,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return [d for d in [mdata, fdata, tdata] if d is not None]
|
|
384
|
+
|
|
385
|
+
def combine_results(
|
|
386
|
+
self,
|
|
387
|
+
algo,
|
|
388
|
+
results,
|
|
389
|
+
model_data,
|
|
390
|
+
out_vars,
|
|
391
|
+
out_coords,
|
|
392
|
+
n_chunks_states,
|
|
393
|
+
n_chunks_targets,
|
|
394
|
+
goal_data,
|
|
395
|
+
iterative,
|
|
396
|
+
):
|
|
397
|
+
"""
|
|
398
|
+
Combines chunk results into final Dataset
|
|
399
|
+
|
|
400
|
+
Parameters
|
|
401
|
+
----------
|
|
402
|
+
algo: foxes.core.Algorithm
|
|
403
|
+
The algorithm object
|
|
404
|
+
results: dict
|
|
405
|
+
The results from the chunk calculations,
|
|
406
|
+
key: (chunki_states, chunki_targets),
|
|
407
|
+
value: dict with numpy.ndarray values
|
|
408
|
+
model_data: xarray.Dataset
|
|
409
|
+
The initial model data
|
|
410
|
+
out_vars: list of str
|
|
411
|
+
Names of the output variables
|
|
412
|
+
out_coords: list of str
|
|
413
|
+
Names of the output coordinates
|
|
414
|
+
n_chunks_states: int
|
|
415
|
+
The number of states chunks
|
|
416
|
+
n_chunks_targets: int
|
|
417
|
+
The number of targets chunks
|
|
418
|
+
goal_data: foxes.core.Data
|
|
419
|
+
Either fdata or tdata
|
|
420
|
+
iterative: bool
|
|
421
|
+
Flag for use within the iterative algorithm
|
|
422
|
+
|
|
423
|
+
Returns
|
|
424
|
+
-------
|
|
425
|
+
ds: xarray.Dataset
|
|
426
|
+
The final results dataset
|
|
427
|
+
|
|
428
|
+
"""
|
|
429
|
+
self.print(f"{type(self).__name__}: Combining results", level=2)
|
|
430
|
+
pbar = tqdm(total=len(out_vars)) if self.verbosity > 1 else None
|
|
431
|
+
data_vars = {}
|
|
432
|
+
for v in out_vars:
|
|
433
|
+
if v in results[(0, 0)][0]:
|
|
434
|
+
data_vars[v] = [out_coords, []]
|
|
435
|
+
|
|
436
|
+
if n_chunks_targets == 1:
|
|
437
|
+
alls = 0
|
|
438
|
+
for chunki_states in range(n_chunks_states):
|
|
439
|
+
r, cstore = results[(chunki_states, 0)]
|
|
440
|
+
data_vars[v][1].append(r[v])
|
|
441
|
+
alls += data_vars[v][1][-1].shape[0]
|
|
442
|
+
if iterative:
|
|
443
|
+
for k, c in cstore.items():
|
|
444
|
+
if k in algo.chunk_store:
|
|
445
|
+
algo.chunk_store[k].update(c)
|
|
446
|
+
else:
|
|
447
|
+
algo.chunk_store[k] = c
|
|
448
|
+
else:
|
|
449
|
+
for chunki_states in range(n_chunks_states):
|
|
450
|
+
tres = []
|
|
451
|
+
for chunki_points in range(n_chunks_targets):
|
|
452
|
+
r, cstore = results[(chunki_states, chunki_points)]
|
|
453
|
+
tres.append(r[v])
|
|
454
|
+
if iterative:
|
|
455
|
+
for k, c in cstore.items():
|
|
456
|
+
if k in algo.chunk_store:
|
|
457
|
+
algo.chunk_store[k].update(c)
|
|
458
|
+
else:
|
|
459
|
+
algo.chunk_store[k] = c
|
|
460
|
+
data_vars[v][1].append(np.concatenate(tres, axis=1))
|
|
461
|
+
del tres
|
|
462
|
+
del r, cstore
|
|
463
|
+
data_vars[v][1] = np.concatenate(data_vars[v][1], axis=0)
|
|
464
|
+
else:
|
|
465
|
+
data_vars[v] = (goal_data[v].dims, goal_data[v].to_numpy())
|
|
466
|
+
|
|
467
|
+
if pbar is not None:
|
|
468
|
+
pbar.update()
|
|
469
|
+
del results
|
|
470
|
+
if pbar is not None:
|
|
471
|
+
pbar.close()
|
|
472
|
+
|
|
473
|
+
# if not iterative or algo.final_iteration:
|
|
474
|
+
# algo.reset_chunk_store()
|
|
475
|
+
|
|
476
|
+
coords = {}
|
|
477
|
+
if FC.STATE in out_coords and FC.STATE in model_data.coords:
|
|
478
|
+
coords[FC.STATE] = model_data[FC.STATE].to_numpy()
|
|
479
|
+
|
|
480
|
+
return Dataset(
|
|
481
|
+
coords=coords,
|
|
482
|
+
data_vars={v: tuple(d) for v, d in data_vars.items()},
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
@abstractmethod
|
|
486
|
+
def run_calculation(self, algo, model, model_data, farm_data, point_data=None):
|
|
487
|
+
"""
|
|
488
|
+
Runs the model calculation
|
|
489
|
+
|
|
490
|
+
Parameters
|
|
491
|
+
----------
|
|
492
|
+
algo: foxes.core.Algorithm
|
|
493
|
+
The algorithm object
|
|
494
|
+
model: foxes.core.DataCalcModel
|
|
495
|
+
The model that whose calculate function
|
|
496
|
+
should be run
|
|
497
|
+
model_data: xarray.Dataset
|
|
498
|
+
The initial model data
|
|
499
|
+
farm_data: xarray.Dataset
|
|
500
|
+
The initial farm data
|
|
501
|
+
point_data: xarray.Dataset, optional
|
|
502
|
+
The initial point data
|
|
503
|
+
|
|
504
|
+
Returns
|
|
505
|
+
-------
|
|
506
|
+
results: xarray.Dataset
|
|
507
|
+
The model results
|
|
508
|
+
|
|
509
|
+
"""
|
|
510
|
+
n_states = model_data.sizes[FC.STATE]
|
|
511
|
+
if point_data is None:
|
|
512
|
+
self.print(
|
|
513
|
+
f"{type(self).__name__}: Calculating {n_states} states for {algo.n_turbines} turbines"
|
|
514
|
+
)
|
|
515
|
+
else:
|
|
516
|
+
self.print(
|
|
517
|
+
f"{type(self).__name__}: Calculating data at {point_data.sizes[FC.TARGET]} points for {n_states} states"
|
|
518
|
+
)
|
|
519
|
+
if not self.initialized:
|
|
520
|
+
raise ValueError(f"Engine '{type(self).__name__}' not initialized")
|
|
521
|
+
if not model.initialized:
|
|
522
|
+
raise ValueError(f"Model '{model.name}' not initialized")
|
|
523
|
+
|
|
524
|
+
@classmethod
|
|
525
|
+
def new(cls, engine_type, *args, **kwargs):
|
|
526
|
+
"""
|
|
527
|
+
Run-time engine factory.
|
|
528
|
+
|
|
529
|
+
Parameters
|
|
530
|
+
----------
|
|
531
|
+
engine_type: str
|
|
532
|
+
The selected derived class name
|
|
533
|
+
args: tuple, optional
|
|
534
|
+
Additional parameters for constructor
|
|
535
|
+
kwargs: dict, optional
|
|
536
|
+
Additional parameters for constructor
|
|
537
|
+
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
if engine_type is None:
|
|
541
|
+
return None
|
|
542
|
+
else:
|
|
543
|
+
engine_type = dict(
|
|
544
|
+
default="DefaultEngine",
|
|
545
|
+
threads="ThreadsEngine",
|
|
546
|
+
process="ProcessEngine",
|
|
547
|
+
xarray="XArrayEngine",
|
|
548
|
+
dask="DaskEngine",
|
|
549
|
+
multiprocess="MultiprocessEngine",
|
|
550
|
+
local_cluster="LocalClusterEngine",
|
|
551
|
+
slurm_cluster="SlurmClusterEngine",
|
|
552
|
+
mpi="MPIEngine",
|
|
553
|
+
ray="RayEngine",
|
|
554
|
+
numpy="NumpyEngine",
|
|
555
|
+
single="SingleChunkEngine",
|
|
556
|
+
).get(engine_type, engine_type)
|
|
557
|
+
|
|
558
|
+
allc = all_subclasses(cls)
|
|
559
|
+
found = engine_type in [scls.__name__ for scls in allc]
|
|
560
|
+
|
|
561
|
+
if found:
|
|
562
|
+
for scls in allc:
|
|
563
|
+
if scls.__name__ == engine_type:
|
|
564
|
+
return scls(*args, **kwargs)
|
|
565
|
+
|
|
566
|
+
else:
|
|
567
|
+
estr = "engine type '{}' is not defined, available types are \n {}".format(
|
|
568
|
+
engine_type, sorted([i.__name__ for i in allc])
|
|
569
|
+
)
|
|
570
|
+
raise KeyError(estr)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def get_engine(error=True, default=True):
|
|
574
|
+
"""
|
|
575
|
+
Gets the global calculation engine
|
|
576
|
+
|
|
577
|
+
Parameters
|
|
578
|
+
----------
|
|
579
|
+
error: bool
|
|
580
|
+
Flag for raising ValueError if no
|
|
581
|
+
engine is found
|
|
582
|
+
default: bool or dict or Engine
|
|
583
|
+
Set default engine if not set yet
|
|
584
|
+
|
|
585
|
+
Returns
|
|
586
|
+
-------
|
|
587
|
+
engine: foxes.core.Engine
|
|
588
|
+
The foxes calculation engine
|
|
589
|
+
|
|
590
|
+
:group: core
|
|
591
|
+
|
|
592
|
+
"""
|
|
593
|
+
engine = __global_engine_data__["engine"]
|
|
594
|
+
if engine is None:
|
|
595
|
+
if isinstance(default, dict):
|
|
596
|
+
engine = Engine.new(**default)
|
|
597
|
+
print(f"Selecting default engine '{engine}'")
|
|
598
|
+
engine.initialize()
|
|
599
|
+
return engine
|
|
600
|
+
elif isinstance(default, Engine):
|
|
601
|
+
print(f"Selecting default engine '{default}'")
|
|
602
|
+
default.initialize()
|
|
603
|
+
return default
|
|
604
|
+
elif isinstance(default, bool) and default:
|
|
605
|
+
engine = Engine.new(
|
|
606
|
+
engine_type="DefaultEngine", chunk_size_points=20000, verbosity=0
|
|
607
|
+
)
|
|
608
|
+
print(f"Selecting '{engine}'")
|
|
609
|
+
engine.initialize()
|
|
610
|
+
return engine
|
|
611
|
+
elif error:
|
|
612
|
+
raise ValueError("Engine not found.")
|
|
613
|
+
return engine
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def has_engine():
|
|
617
|
+
"""
|
|
618
|
+
Flag that checks if engine has been set
|
|
619
|
+
|
|
620
|
+
Returns
|
|
621
|
+
-------
|
|
622
|
+
flag: bool
|
|
623
|
+
True if engine has been set
|
|
624
|
+
|
|
625
|
+
:group: core
|
|
626
|
+
|
|
627
|
+
"""
|
|
628
|
+
return __global_engine_data__["engine"] is not None
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def reset_engine():
|
|
632
|
+
"""
|
|
633
|
+
Resets the global calculation engine
|
|
634
|
+
|
|
635
|
+
:group: core
|
|
636
|
+
|
|
637
|
+
"""
|
|
638
|
+
engine = get_engine(error=False, default=False)
|
|
639
|
+
if engine is not None:
|
|
640
|
+
engine.finalize(type=None, value=None, traceback=None)
|