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.

Files changed (215) hide show
  1. docs/source/conf.py +353 -0
  2. examples/abl_states/run.py +160 -0
  3. examples/compare_rotors_pwakes/run.py +217 -0
  4. examples/compare_wakes/run.py +241 -0
  5. examples/dyn_wakes/run.py +311 -0
  6. examples/field_data_nc/run.py +121 -0
  7. examples/induction/run.py +201 -0
  8. examples/multi_height/run.py +113 -0
  9. examples/power_mask/run.py +249 -0
  10. examples/random_timeseries/run.py +210 -0
  11. examples/scan_row/run.py +193 -0
  12. examples/sector_management/run.py +162 -0
  13. examples/sequential/run.py +209 -0
  14. examples/single_state/run.py +201 -0
  15. examples/states_lookup_table/run.py +137 -0
  16. examples/streamline_wakes/run.py +138 -0
  17. examples/tab_file/run.py +142 -0
  18. examples/timelines/run.py +267 -0
  19. examples/timeseries/run.py +190 -0
  20. examples/timeseries_slurm/run.py +185 -0
  21. examples/wind_rose/run.py +141 -0
  22. examples/windio/run.py +29 -0
  23. examples/yawed_wake/run.py +196 -0
  24. foxes/__init__.py +4 -8
  25. foxes/algorithms/__init__.py +1 -1
  26. foxes/algorithms/downwind/downwind.py +247 -111
  27. foxes/algorithms/downwind/models/farm_wakes_calc.py +12 -7
  28. foxes/algorithms/downwind/models/init_farm_data.py +2 -2
  29. foxes/algorithms/downwind/models/point_wakes_calc.py +6 -7
  30. foxes/algorithms/downwind/models/reorder_farm_output.py +1 -2
  31. foxes/algorithms/downwind/models/set_amb_farm_results.py +1 -1
  32. foxes/algorithms/downwind/models/set_amb_point_results.py +5 -3
  33. foxes/algorithms/iterative/iterative.py +74 -34
  34. foxes/algorithms/iterative/models/farm_wakes_calc.py +12 -7
  35. foxes/algorithms/iterative/models/urelax.py +3 -3
  36. foxes/algorithms/sequential/models/plugin.py +5 -5
  37. foxes/algorithms/sequential/models/seq_state.py +1 -1
  38. foxes/algorithms/sequential/sequential.py +126 -255
  39. foxes/constants.py +22 -7
  40. foxes/core/__init__.py +1 -0
  41. foxes/core/algorithm.py +632 -147
  42. foxes/core/data.py +252 -20
  43. foxes/core/data_calc_model.py +15 -291
  44. foxes/core/engine.py +640 -0
  45. foxes/core/farm_controller.py +38 -10
  46. foxes/core/farm_data_model.py +16 -1
  47. foxes/core/ground_model.py +2 -2
  48. foxes/core/model.py +249 -182
  49. foxes/core/partial_wakes_model.py +1 -1
  50. foxes/core/point_data_model.py +17 -2
  51. foxes/core/rotor_model.py +27 -21
  52. foxes/core/states.py +17 -1
  53. foxes/core/turbine_type.py +28 -0
  54. foxes/core/wake_frame.py +30 -34
  55. foxes/core/wake_model.py +5 -5
  56. foxes/core/wake_superposition.py +1 -1
  57. foxes/data/windio/windio_5turbines_timeseries.yaml +31 -15
  58. foxes/engines/__init__.py +17 -0
  59. foxes/engines/dask.py +982 -0
  60. foxes/engines/default.py +75 -0
  61. foxes/engines/futures.py +72 -0
  62. foxes/engines/mpi.py +38 -0
  63. foxes/engines/multiprocess.py +71 -0
  64. foxes/engines/numpy.py +167 -0
  65. foxes/engines/pool.py +249 -0
  66. foxes/engines/ray.py +79 -0
  67. foxes/engines/single.py +141 -0
  68. foxes/input/farm_layout/__init__.py +1 -0
  69. foxes/input/farm_layout/from_csv.py +4 -0
  70. foxes/input/farm_layout/from_json.py +2 -2
  71. foxes/input/farm_layout/grid.py +2 -2
  72. foxes/input/farm_layout/ring.py +65 -0
  73. foxes/input/farm_layout/row.py +2 -2
  74. foxes/input/states/__init__.py +7 -0
  75. foxes/input/states/create/random_abl_states.py +1 -1
  76. foxes/input/states/field_data_nc.py +158 -33
  77. foxes/input/states/multi_height.py +128 -14
  78. foxes/input/states/one_point_flow.py +577 -0
  79. foxes/input/states/scan_ws.py +74 -3
  80. foxes/input/states/single.py +1 -1
  81. foxes/input/states/slice_data_nc.py +681 -0
  82. foxes/input/states/states_table.py +204 -35
  83. foxes/input/windio/__init__.py +2 -2
  84. foxes/input/windio/get_states.py +44 -23
  85. foxes/input/windio/read_attributes.py +48 -17
  86. foxes/input/windio/read_farm.py +116 -102
  87. foxes/input/windio/read_fields.py +16 -6
  88. foxes/input/windio/read_outputs.py +71 -24
  89. foxes/input/windio/runner.py +31 -17
  90. foxes/input/windio/windio.py +41 -23
  91. foxes/models/farm_models/turbine2farm.py +1 -1
  92. foxes/models/ground_models/wake_mirror.py +10 -6
  93. foxes/models/model_book.py +58 -20
  94. foxes/models/partial_wakes/axiwake.py +3 -3
  95. foxes/models/partial_wakes/rotor_points.py +3 -3
  96. foxes/models/partial_wakes/top_hat.py +2 -2
  97. foxes/models/point_models/set_uniform_data.py +1 -1
  98. foxes/models/point_models/tke2ti.py +1 -1
  99. foxes/models/point_models/wake_deltas.py +1 -1
  100. foxes/models/rotor_models/centre.py +4 -0
  101. foxes/models/rotor_models/grid.py +24 -25
  102. foxes/models/rotor_models/levels.py +4 -5
  103. foxes/models/turbine_models/calculator.py +4 -6
  104. foxes/models/turbine_models/kTI_model.py +22 -6
  105. foxes/models/turbine_models/lookup_table.py +30 -4
  106. foxes/models/turbine_models/rotor_centre_calc.py +4 -3
  107. foxes/models/turbine_models/set_farm_vars.py +103 -34
  108. foxes/models/turbine_types/PCt_file.py +27 -3
  109. foxes/models/turbine_types/PCt_from_two.py +27 -3
  110. foxes/models/turbine_types/TBL_file.py +80 -0
  111. foxes/models/turbine_types/__init__.py +2 -0
  112. foxes/models/turbine_types/lookup.py +316 -0
  113. foxes/models/turbine_types/null_type.py +51 -1
  114. foxes/models/turbine_types/wsrho2PCt_from_two.py +29 -5
  115. foxes/models/turbine_types/wsti2PCt_from_two.py +31 -7
  116. foxes/models/vertical_profiles/__init__.py +1 -1
  117. foxes/models/vertical_profiles/data_profile.py +1 -1
  118. foxes/models/wake_frames/__init__.py +1 -0
  119. foxes/models/wake_frames/dynamic_wakes.py +424 -0
  120. foxes/models/wake_frames/farm_order.py +25 -5
  121. foxes/models/wake_frames/rotor_wd.py +6 -4
  122. foxes/models/wake_frames/seq_dynamic_wakes.py +61 -74
  123. foxes/models/wake_frames/streamlines.py +21 -22
  124. foxes/models/wake_frames/timelines.py +330 -129
  125. foxes/models/wake_frames/yawed_wakes.py +7 -4
  126. foxes/models/wake_models/dist_sliced.py +2 -4
  127. foxes/models/wake_models/induction/rankine_half_body.py +5 -5
  128. foxes/models/wake_models/induction/rathmann.py +78 -24
  129. foxes/models/wake_models/induction/self_similar.py +78 -28
  130. foxes/models/wake_models/induction/vortex_sheet.py +86 -48
  131. foxes/models/wake_models/ti/crespo_hernandez.py +6 -4
  132. foxes/models/wake_models/ti/iec_ti.py +40 -21
  133. foxes/models/wake_models/top_hat.py +1 -1
  134. foxes/models/wake_models/wind/bastankhah14.py +8 -6
  135. foxes/models/wake_models/wind/bastankhah16.py +17 -16
  136. foxes/models/wake_models/wind/jensen.py +4 -3
  137. foxes/models/wake_models/wind/turbopark.py +16 -13
  138. foxes/models/wake_superpositions/ti_linear.py +1 -1
  139. foxes/models/wake_superpositions/ti_max.py +1 -1
  140. foxes/models/wake_superpositions/ti_pow.py +1 -1
  141. foxes/models/wake_superpositions/ti_quadratic.py +1 -1
  142. foxes/models/wake_superpositions/ws_linear.py +8 -7
  143. foxes/models/wake_superpositions/ws_max.py +8 -7
  144. foxes/models/wake_superpositions/ws_pow.py +8 -7
  145. foxes/models/wake_superpositions/ws_product.py +5 -5
  146. foxes/models/wake_superpositions/ws_quadratic.py +8 -7
  147. foxes/output/__init__.py +4 -1
  148. foxes/output/farm_layout.py +16 -12
  149. foxes/output/farm_results_eval.py +1 -1
  150. foxes/output/flow_plots_2d/__init__.py +0 -1
  151. foxes/output/flow_plots_2d/flow_plots.py +70 -30
  152. foxes/output/grids.py +92 -22
  153. foxes/output/results_writer.py +2 -2
  154. foxes/output/rose_plot.py +3 -3
  155. foxes/output/seq_plugins/__init__.py +2 -0
  156. foxes/output/{flow_plots_2d → seq_plugins}/seq_flow_ani_plugin.py +64 -22
  157. foxes/output/seq_plugins/seq_wake_debug_plugin.py +145 -0
  158. foxes/output/slice_data.py +131 -111
  159. foxes/output/state_turbine_map.py +19 -14
  160. foxes/output/state_turbine_table.py +19 -19
  161. foxes/utils/__init__.py +1 -1
  162. foxes/utils/abl/neutral.py +2 -2
  163. foxes/utils/abl/stable.py +2 -2
  164. foxes/utils/abl/unstable.py +2 -2
  165. foxes/utils/data_book.py +1 -1
  166. foxes/utils/dev_utils.py +42 -0
  167. foxes/utils/dict.py +24 -1
  168. foxes/utils/exec_python.py +1 -1
  169. foxes/utils/factory.py +176 -53
  170. foxes/utils/geom2d/circle.py +1 -1
  171. foxes/utils/geom2d/polygon.py +1 -1
  172. foxes/utils/geopandas_utils.py +2 -2
  173. foxes/utils/load.py +2 -2
  174. foxes/utils/pandas_helpers.py +3 -2
  175. foxes/utils/wind_dir.py +0 -2
  176. foxes/utils/xarray_utils.py +24 -14
  177. foxes/variables.py +39 -2
  178. {foxes-0.8.2.dist-info → foxes-1.1.0.2.dist-info}/METADATA +75 -33
  179. foxes-1.1.0.2.dist-info/RECORD +309 -0
  180. {foxes-0.8.2.dist-info → foxes-1.1.0.2.dist-info}/WHEEL +1 -1
  181. foxes-1.1.0.2.dist-info/top_level.txt +4 -0
  182. tests/0_consistency/iterative/test_iterative.py +92 -0
  183. tests/0_consistency/partial_wakes/test_partial_wakes.py +90 -0
  184. tests/1_verification/flappy_0_6/PCt_files/flappy/run.py +85 -0
  185. tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +103 -0
  186. tests/1_verification/flappy_0_6/abl_states/flappy/run.py +85 -0
  187. tests/1_verification/flappy_0_6/abl_states/test_abl_states.py +87 -0
  188. tests/1_verification/flappy_0_6/partial_top_hat/flappy/run.py +82 -0
  189. tests/1_verification/flappy_0_6/partial_top_hat/test_partial_top_hat.py +82 -0
  190. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/flappy/run.py +92 -0
  191. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +93 -0
  192. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/flappy/run.py +92 -0
  193. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +96 -0
  194. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/flappy/run.py +94 -0
  195. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +122 -0
  196. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/flappy/run.py +94 -0
  197. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +122 -0
  198. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/flappy/run.py +92 -0
  199. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +93 -0
  200. tests/1_verification/flappy_0_6_2/grid_rotors/flappy/run.py +85 -0
  201. tests/1_verification/flappy_0_6_2/grid_rotors/test_grid_rotors.py +130 -0
  202. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/flappy/run.py +96 -0
  203. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +116 -0
  204. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/flappy/run.py +93 -0
  205. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +99 -0
  206. tests/3_examples/test_examples.py +34 -0
  207. foxes/VERSION +0 -1
  208. foxes/output/flow_plots_2d.py +0 -0
  209. foxes/utils/geopandas_helpers.py +0 -294
  210. foxes/utils/runners/__init__.py +0 -1
  211. foxes/utils/runners/runners.py +0 -280
  212. foxes-0.8.2.dist-info/RECORD +0 -247
  213. foxes-0.8.2.dist-info/top_level.txt +0 -1
  214. foxes-0.8.2.dist-info/zip-safe +0 -1
  215. {foxes-0.8.2.dist-info → foxes-1.1.0.2.dist-info}/LICENSE +0 -0
@@ -0,0 +1,681 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import xarray as xr
4
+ from scipy.interpolate import interpn
5
+ from pathlib import Path
6
+
7
+ from foxes.core import States
8
+ from foxes.utils import wd2uv, uv2wd, import_module
9
+ from foxes.data import STATES, StaticData
10
+ import foxes.variables as FV
11
+ import foxes.constants as FC
12
+
13
+
14
+ class SliceDataNC(States):
15
+ """
16
+ Heterogeneous ambient states on a regular
17
+ horizontal grid in NetCDF format, independent
18
+ of height.
19
+
20
+ Attributes
21
+ ----------
22
+ data_source: str or xarray.Dataset
23
+ The data or the file search pattern, should end with
24
+ suffix '.nc'. One or many files.
25
+ ovars: list of str
26
+ The output variables
27
+ var2ncvar: dict
28
+ Mapping from variable names to variable names
29
+ in the nc file
30
+ fixed_vars: dict
31
+ Uniform values for output variables, instead
32
+ of reading from data
33
+ states_coord: str
34
+ The states coordinate name in the data
35
+ x_coord: str
36
+ The x coordinate name in the data
37
+ y_coord: str
38
+ The y coordinate name in the data
39
+ pre_load: bool
40
+ Flag for loading all data into memory during
41
+ initialization
42
+ weight_ncvar: str
43
+ Name of the weight data variable in the nc file(s)
44
+ bounds_error: bool
45
+ Flag for raising errors if bounds are exceeded
46
+ fill_value: number
47
+ Fill value in case of exceeding bounds, if no bounds error
48
+ time_format: str
49
+ The datetime parsing format string
50
+ interp_nans: bool
51
+ Linearly interpolate nan values
52
+ interpn_pars: dict, optional
53
+ Additional parameters for scipy.interpolate.interpn
54
+
55
+ :group: input.states
56
+
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ data_source,
62
+ output_vars,
63
+ var2ncvar={},
64
+ fixed_vars={},
65
+ states_coord="Time",
66
+ x_coord="UTMX",
67
+ y_coord="UTMY",
68
+ pre_load=True,
69
+ weight_ncvar=None,
70
+ time_format="%Y-%m-%d_%H:%M:%S",
71
+ sel=None,
72
+ isel=None,
73
+ interp_nans=False,
74
+ verbosity=1,
75
+ **interpn_pars,
76
+ ):
77
+ """
78
+ Constructor.
79
+
80
+ Parameters
81
+ ----------
82
+ data_source: str or xarray.Dataset
83
+ The data or the file search pattern, should end with
84
+ suffix '.nc'. One or many files.
85
+ output_vars: list of str
86
+ The output variables
87
+ var2ncvar: dict, optional
88
+ Mapping from variable names to variable names
89
+ in the nc file
90
+ fixed_vars: dict, optional
91
+ Uniform values for output variables, instead
92
+ of reading from data
93
+ states_coord: str
94
+ The states coordinate name in the data
95
+ x_coord: str
96
+ The x coordinate name in the data
97
+ y_coord: str
98
+ The y coordinate name in the data
99
+ pre_load: bool
100
+ Flag for loading all data into memory during
101
+ initialization
102
+ weight_ncvar: str, optional
103
+ Name of the weight data variable in the nc file(s)
104
+ time_format: str
105
+ The datetime parsing format string
106
+ sel: dict, optional
107
+ Subset selection via xr.Dataset.sel()
108
+ isel: dict, optional
109
+ Subset selection via xr.Dataset.isel()
110
+ interp_nans: bool
111
+ Linearly interpolate nan values
112
+ verbosity: int
113
+ Verbosity level for pre_load file reading
114
+ interpn_pars: dict, optional
115
+ Additional parameters for scipy.interpolate.interpn
116
+
117
+ """
118
+ super().__init__()
119
+
120
+ self.states_coord = states_coord
121
+ self.ovars = output_vars
122
+ self.fixed_vars = fixed_vars
123
+ self.x_coord = x_coord
124
+ self.y_coord = y_coord
125
+ self.weight_ncvar = weight_ncvar
126
+ self.pre_load = pre_load
127
+ self.time_format = time_format
128
+ self.sel = sel
129
+ self.isel = isel
130
+ self.interpn_pars = interpn_pars
131
+ self.interp_nans = interp_nans
132
+
133
+ self.var2ncvar = {
134
+ v: var2ncvar.get(v, v) for v in output_vars if v not in fixed_vars
135
+ }
136
+
137
+ self._N = None
138
+
139
+ self.__data_source = data_source
140
+ self.__weights = None
141
+ self.__inds = None
142
+
143
+ # pre-load file reading:
144
+ if not isinstance(self.data_source, xr.Dataset):
145
+ if "*" in str(self.data_source):
146
+ pass
147
+ else:
148
+ self.__data_source = StaticData().get_file_path(
149
+ STATES, self.data_source, check_raw=True
150
+ )
151
+ if verbosity:
152
+ if pre_load:
153
+ print(
154
+ f"States '{self.name}': Reading data from '{self.data_source}'"
155
+ )
156
+ else:
157
+ print(
158
+ f"States '{self.name}': Reading index from '{self.data_source}'"
159
+ )
160
+
161
+ def _read_ds():
162
+ if Path(self.data_source).is_file():
163
+ return xr.open_dataset(self.data_source)
164
+ else:
165
+ # try to read multiple files, needs dask:
166
+ try:
167
+ return xr.open_mfdataset(
168
+ str(self.data_source),
169
+ parallel=False,
170
+ concat_dim=self.states_coord,
171
+ combine="nested",
172
+ data_vars="minimal",
173
+ coords="minimal",
174
+ compat="override",
175
+ )
176
+ except ValueError as e:
177
+ import_module("dask", hint="pip install dask")
178
+ raise e
179
+
180
+ with _read_ds() as ds:
181
+ self.__data_source = ds
182
+
183
+ if sel is not None:
184
+ self.__data_source = self.data_source.sel(self.sel)
185
+ if isel is not None:
186
+ self.__data_source = self.data_source.isel(self.isel)
187
+ if pre_load:
188
+ self.__data_source.load()
189
+
190
+ self._get_inds(self.data_source)
191
+
192
+ @property
193
+ def data_source(self):
194
+ """
195
+ The data source
196
+
197
+ Returns
198
+ -------
199
+ s: object
200
+ The data source
201
+
202
+ """
203
+ if self.pre_load and self.running:
204
+ raise ValueError(
205
+ f"States '{self.name}': Cannot acces data_source while running"
206
+ )
207
+ return self.__data_source
208
+
209
+ def _get_inds(self, ds):
210
+ """
211
+ Helper function for index and weights
212
+ reading
213
+ """
214
+ for c in [self.states_coord, self.x_coord, self.y_coord]:
215
+ if not c in ds:
216
+ raise KeyError(
217
+ f"States '{self.name}': Missing coordinate '{c}' in data"
218
+ )
219
+
220
+ self.__inds = ds[self.states_coord].to_numpy()
221
+ if self.time_format is not None:
222
+ self.__inds = pd.to_datetime(
223
+ self.__inds, format=self.time_format
224
+ ).to_numpy()
225
+ self._N = len(self.__inds)
226
+
227
+ if self.weight_ncvar is not None:
228
+ self.__weights = ds[self.weight_ncvar].to_numpy()
229
+
230
+ for v in self.ovars:
231
+ if v in self.var2ncvar:
232
+ ncv = self.var2ncvar[v]
233
+ if not ncv in ds:
234
+ raise KeyError(
235
+ f"States '{self.name}': nc variable '{ncv}' not found in data, found: {sorted(list(ds.keys()))}"
236
+ )
237
+ elif v not in self.fixed_vars:
238
+ raise ValueError(
239
+ f"States '{self.name}': Variable '{v}' neither found in var2ncvar not in fixed_vars"
240
+ )
241
+
242
+ def _get_data(self, ds, verbosity):
243
+ """
244
+ Helper function for data extraction
245
+ """
246
+ x = ds[self.x_coord].to_numpy()
247
+ y = ds[self.y_coord].to_numpy()
248
+ n_x = len(x)
249
+ n_y = len(y)
250
+ n_sts = ds.sizes[self.states_coord]
251
+
252
+ cor_sxy = (self.states_coord, self.x_coord, self.y_coord)
253
+ cor_syx = (self.states_coord, self.y_coord, self.x_coord)
254
+ cor_s = (self.states_coord,)
255
+ vars_syx = []
256
+ vars_s = []
257
+ for v, ncv in self.var2ncvar.items():
258
+ if ds[ncv].dims == cor_syx or ds[ncv].dims == cor_sxy:
259
+ vars_syx.append(v)
260
+ elif ds[ncv].dims == cor_s:
261
+ vars_s.append(v)
262
+ else:
263
+ raise ValueError(
264
+ f"States '{self.name}': Wrong coordinate order for variable '{ncv}': Found {ds[ncv].dims}, expecting {cor_sxy}, {cor_syx}, or {cor_s}"
265
+ )
266
+
267
+ data = np.zeros((n_sts, n_y, n_x, len(self.var2ncvar)), dtype=FC.DTYPE)
268
+ for v in vars_syx:
269
+ ncv = self.var2ncvar[v]
270
+ if ds[ncv].dims == cor_syx:
271
+ data[..., self._dkys[v]] = ds[ncv][:]
272
+ else:
273
+ data[..., self._dkys[v]] = np.swapaxes(ds[ncv].to_numpy(), 1, 2)
274
+ for v in vars_s:
275
+ ncv = self.var2ncvar[v]
276
+ data[..., self._dkys[v]] = ds[ncv].to_numpy()[:, None, None]
277
+ if FV.WD in self.fixed_vars:
278
+ data[..., self._dkys[FV.WD]] = np.full(
279
+ (n_sts, n_y, n_x), self.fixed_vars[FV.WD], dtype=FC.DTYPE
280
+ )
281
+
282
+ if verbosity > 1:
283
+ print(f"\n{self.name}: Data ranges")
284
+ for v, i in self._dkys.items():
285
+ d = data[..., i]
286
+ nn = np.sum(np.isnan(d))
287
+ print(
288
+ f" {v}: {np.nanmin(d)} --> {np.nanmax(d)}, nans: {nn} ({100*nn/len(d.flat):.2f}%)"
289
+ )
290
+
291
+ return data
292
+
293
+ def output_point_vars(self, algo):
294
+ """
295
+ The variables which are being modified by the model.
296
+
297
+ Parameters
298
+ ----------
299
+ algo: foxes.core.Algorithm
300
+ The calculation algorithm
301
+
302
+ Returns
303
+ -------
304
+ output_vars: list of str
305
+ The output variable names
306
+
307
+ """
308
+ return self.ovars
309
+
310
+ def load_data(self, algo, verbosity=0):
311
+ """
312
+ Load and/or create all model data that is subject to chunking.
313
+
314
+ Such data should not be stored under self, for memory reasons. The
315
+ data returned here will automatically be chunked and then provided
316
+ as part of the mdata object during calculations.
317
+
318
+ Parameters
319
+ ----------
320
+ algo: foxes.core.Algorithm
321
+ The calculation algorithm
322
+ verbosity: int
323
+ The verbosity level, 0 = silent
324
+
325
+ Returns
326
+ -------
327
+ idata: dict
328
+ The dict has exactly two entries: `data_vars`,
329
+ a dict with entries `name_str -> (dim_tuple, data_ndarray)`;
330
+ and `coords`, a dict with entries `dim_name_str -> dim_array`
331
+
332
+ """
333
+
334
+ if (FV.WS in self.ovars and FV.WD not in self.ovars) or (
335
+ FV.WS not in self.ovars and FV.WD in self.ovars
336
+ ):
337
+ raise KeyError(
338
+ f"States '{self.name}': Missing '{FV.WS}' or '{FV.WD}' in output variables {self.ovars}"
339
+ )
340
+
341
+ # ensure WD and WS get the first two slots of data:
342
+ self._dkys = {}
343
+ if FV.WS in self.ovars:
344
+ self._dkys[FV.WD] = 0
345
+ if FV.WS in self.var2ncvar:
346
+ self._dkys[FV.WS] = 1
347
+ for v in self.var2ncvar:
348
+ if v not in self._dkys:
349
+ self._dkys[v] = len(self._dkys)
350
+ self._n_dvars = len(self._dkys)
351
+
352
+ if self.__weights is None:
353
+ self.__weights = np.full(
354
+ (self._N, algo.n_turbines), 1.0 / self._N, dtype=FC.DTYPE
355
+ )
356
+
357
+ idata = super().load_data(algo, verbosity)
358
+
359
+ if self.pre_load:
360
+ self.X = self.var(FV.X)
361
+ self.Y = self.var(FV.Y)
362
+ self.VARS = self.var("vars")
363
+ self.DATA = self.var("data")
364
+
365
+ ds = self.data_source
366
+
367
+ y = ds[self.y_coord].to_numpy()
368
+ x = ds[self.x_coord].to_numpy()
369
+ v = list(self._dkys.keys())
370
+ coos = (FC.STATE, self.Y, self.X, self.VARS)
371
+ data = self._get_data(ds, verbosity)
372
+ data = (coos, data)
373
+
374
+ idata["coords"][self.Y] = y
375
+ idata["coords"][self.X] = x
376
+ idata["coords"][self.VARS] = v
377
+ idata["data_vars"][self.DATA] = data
378
+
379
+ return idata
380
+
381
+ def set_running(
382
+ self,
383
+ algo,
384
+ data_stash,
385
+ sel=None,
386
+ isel=None,
387
+ verbosity=0,
388
+ ):
389
+ """
390
+ Sets this model status to running, and moves
391
+ all large data to stash.
392
+
393
+ The stashed data will be returned by the
394
+ unset_running() function after running calculations.
395
+
396
+ Parameters
397
+ ----------
398
+ algo: foxes.core.Algorithm
399
+ The calculation algorithm
400
+ data_stash: dict
401
+ Large data stash, this function adds data here.
402
+ Key: model name. Value: dict, large model data
403
+ sel: dict, optional
404
+ The subset selection dictionary
405
+ isel: dict, optional
406
+ The index subset selection dictionary
407
+ verbosity: int
408
+ The verbosity level, 0 = silent
409
+
410
+ """
411
+ super().set_running(algo, data_stash, sel, isel, verbosity)
412
+
413
+ data_stash[self.name] = dict(
414
+ weights=self.__weights,
415
+ inds=self.__inds,
416
+ )
417
+ del self.__weights, self.__inds
418
+
419
+ if self.pre_load:
420
+ data_stash[self.name]["data_source"] = self.__data_source
421
+ del self.__data_source
422
+
423
+ def unset_running(
424
+ self,
425
+ algo,
426
+ data_stash,
427
+ sel=None,
428
+ isel=None,
429
+ verbosity=0,
430
+ ):
431
+ """
432
+ Sets this model status to not running, recovering large data
433
+ from stash
434
+
435
+ Parameters
436
+ ----------
437
+ algo: foxes.core.Algorithm
438
+ The calculation algorithm
439
+ data_stash: dict
440
+ Large data stash, this function adds data here.
441
+ Key: model name. Value: dict, large model data
442
+ sel: dict, optional
443
+ The subset selection dictionary
444
+ isel: dict, optional
445
+ The index subset selection dictionary
446
+ verbosity: int
447
+ The verbosity level, 0 = silent
448
+
449
+ """
450
+ super().unset_running(algo, data_stash, sel, isel, verbosity)
451
+
452
+ data = data_stash[self.name]
453
+ self.__weights = data.pop("weights")
454
+ self.__inds = data.pop("inds")
455
+
456
+ if self.pre_load:
457
+ self.__data_source = data.pop("data_source")
458
+
459
+ def size(self):
460
+ """
461
+ The total number of states.
462
+
463
+ Returns
464
+ -------
465
+ int:
466
+ The total number of states
467
+
468
+ """
469
+ return self._N
470
+
471
+ def index(self):
472
+ """
473
+ The index list
474
+
475
+ Returns
476
+ -------
477
+ indices: array_like
478
+ The index labels of states, or None for default integers
479
+
480
+ """
481
+ if self.running:
482
+ raise ValueError(f"States '{self.name}': Cannot acces index while running")
483
+ return self.__inds
484
+
485
+ def output_point_vars(self, algo):
486
+ """
487
+ The variables which are being modified by the model.
488
+
489
+ Parameters
490
+ ----------
491
+ algo: foxes.core.Algorithm
492
+ The calculation algorithm
493
+
494
+ Returns
495
+ -------
496
+ output_vars: list of str
497
+ The output variable names
498
+
499
+ """
500
+ return self.ovars
501
+
502
+ def weights(self, algo):
503
+ """
504
+ The statistical weights of all states.
505
+
506
+ Parameters
507
+ ----------
508
+ algo: foxes.core.Algorithm
509
+ The calculation algorithm
510
+
511
+ Returns
512
+ -------
513
+ weights: numpy.ndarray
514
+ The weights, shape: (n_states, n_turbines)
515
+
516
+ """
517
+ if self.running:
518
+ raise ValueError(
519
+ f"States '{self.name}': Cannot acces weights while running"
520
+ )
521
+ return self.__weights
522
+
523
+ def calculate(self, algo, mdata, fdata, tdata):
524
+ """ "
525
+ The main model calculation.
526
+
527
+ This function is executed on a single chunk of data,
528
+ all computations should be based on numpy arrays.
529
+
530
+ Parameters
531
+ ----------
532
+ algo: foxes.core.Algorithm
533
+ The calculation algorithm
534
+ mdata: foxes.core.MData
535
+ The model data
536
+ fdata: foxes.core.FData
537
+ The farm data
538
+ tdata: foxes.core.TData
539
+ The target point data
540
+
541
+ Returns
542
+ -------
543
+ results: dict
544
+ The resulting data, keys: output variable str.
545
+ Values: numpy.ndarray with shape
546
+ (n_states, n_targets, n_tpoints)
547
+
548
+ """
549
+ # prepare:
550
+ n_states = tdata.n_states
551
+ n_targets = tdata.n_targets
552
+ n_tpoints = tdata.n_tpoints
553
+ points = tdata[FC.TARGETS].reshape(n_states, n_targets * n_tpoints, 3)
554
+ n_pts = points.shape[1]
555
+ n_states = fdata.n_states
556
+
557
+ # pick pre-loaded data:
558
+ if self.pre_load:
559
+ x = mdata[self.X]
560
+ y = mdata[self.Y]
561
+ data = mdata[self.DATA].copy()
562
+
563
+ # read data for this chunk:
564
+ else:
565
+ i0 = mdata.states_i0(counter=True)
566
+ s = slice(i0, i0 + n_states)
567
+ ds = self.data_source.isel({self.states_coord: s}).load()
568
+
569
+ x = ds[self.x_coord].to_numpy()
570
+ y = ds[self.y_coord].to_numpy()
571
+ data = self._get_data(ds, verbosity=0)
572
+
573
+ del ds
574
+ n_y = len(y)
575
+ n_x = len(x)
576
+
577
+ # translate WS, WD into U, V:
578
+ if FV.WD in self.ovars and FV.WS in self.ovars:
579
+ wd = data[..., self._dkys[FV.WD]]
580
+ ws = (
581
+ data[..., self._dkys[FV.WS]]
582
+ if FV.WS in self._dkys
583
+ else self.fixed_vars[FV.WS]
584
+ )
585
+ wdwsi = [self._dkys[FV.WD], self._dkys[FV.WS]]
586
+ data[..., wdwsi] = wd2uv(wd, ws, axis=-1)
587
+ del ws, wd
588
+
589
+ # prepare points:
590
+ sts = np.arange(n_states)
591
+ pts = np.append(points, np.zeros((n_states, n_pts, 1), dtype=FC.DTYPE), axis=2)
592
+ pts[:, :, 3] = sts[:, None]
593
+ pts = pts.reshape(n_states * n_pts, 3)
594
+ pts = np.flip(pts, axis=1)
595
+ gvars = (sts, y, x)
596
+
597
+ # interpolate nan values:
598
+ if self.interp_nans and np.any(np.isnan(data)):
599
+ df = pd.DataFrame(
600
+ index=pd.MultiIndex.from_product(gvars, names=["state", "y", "x"]),
601
+ data={
602
+ v: data[..., vi].reshape(n_states * n_y * n_x)
603
+ for v, vi in self._dkys.items()
604
+ },
605
+ )
606
+ df.interpolate(
607
+ axis=0, method="linear", limit_direction="forward", inplace=True
608
+ )
609
+ df.interpolate(
610
+ axis=0, method="linear", limit_direction="backward", inplace=True
611
+ )
612
+ data = df.to_numpy().reshape(n_states, n_y, n_x, self._n_dvars)
613
+ del df
614
+
615
+ # interpolate:
616
+ try:
617
+ ipars = dict(bounds_error=True, fill_value=None)
618
+ ipars.update(self.interpn_pars)
619
+ data = interpn(gvars, data, pts, **ipars).reshape(
620
+ n_states, n_pts, self._n_dvars
621
+ )
622
+ except ValueError as e:
623
+ print(f"\nStates '{self.name}': Interpolation error")
624
+ print("INPUT VARS: (state, y, x)")
625
+ print(
626
+ "DATA BOUNDS:",
627
+ [float(np.min(d)) for d in gvars],
628
+ [float(np.max(d)) for d in gvars],
629
+ )
630
+ print(
631
+ "EVAL BOUNDS:",
632
+ [float(np.min(p)) for p in pts.T],
633
+ [float(np.max(p)) for p in pts.T],
634
+ )
635
+ print(
636
+ "\nMaybe you want to try the option 'bounds_error=False'? This will extrapolate the data.\n"
637
+ )
638
+ raise e
639
+ del pts, x, y, gvars
640
+
641
+ # interpolate nan values:
642
+ if self.interp_nans and np.any(np.isnan(data)):
643
+ df = pd.DataFrame(
644
+ index=pd.MultiIndex.from_product(
645
+ (sts, range(n_pts)), names=["state", "point"]
646
+ ),
647
+ data={
648
+ v: data[:, :, vi].reshape(n_states * n_pts)
649
+ for v, vi in self._dkys.items()
650
+ },
651
+ )
652
+ df["x"] = points[:, :, 0].reshape(n_states * n_pts)
653
+ df["y"] = points[:, :, 1].reshape(n_states * n_pts)
654
+ df = df.reset_index().set_index(["state", "x", "y"])
655
+ df.interpolate(
656
+ axis=0, method="linear", limit_direction="forward", inplace=True
657
+ )
658
+ df.interpolate(
659
+ axis=0, method="linear", limit_direction="backward", inplace=True
660
+ )
661
+ df = df.reset_index().drop(["x", "y"], axis=1).set_index(["state", "point"])
662
+ data = df.to_numpy().reshape(n_states, n_pts, self._n_dvars)
663
+ del df
664
+
665
+ # set output:
666
+ out = {}
667
+ if FV.WD in self.ovars and FV.WS in self.ovars:
668
+ uv = data[..., wdwsi]
669
+ out[FV.WS] = np.linalg.norm(uv, axis=-1)
670
+ out[FV.WD] = uv2wd(uv, axis=-1)
671
+ del uv
672
+ for v in self.ovars:
673
+ if v not in out:
674
+ if v in self._dkys:
675
+ out[v] = data[..., self._dkys[v]]
676
+ else:
677
+ out[v] = np.full(
678
+ (n_states, n_pts), self.fixed_vars[v], dtype=FC.DTYPE
679
+ )
680
+
681
+ return {v: d.reshape(n_states, n_targets, n_tpoints) for v, d in out.items()}