foxes 1.4__py3-none-any.whl → 1.5.1__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 (95) hide show
  1. docs/source/conf.py +1 -1
  2. examples/abl_states/run.py +58 -56
  3. examples/dyn_wakes/run.py +110 -118
  4. examples/field_data_nc/run.py +23 -21
  5. examples/multi_height/run.py +8 -6
  6. examples/scan_row/run.py +89 -87
  7. examples/sector_management/run.py +40 -38
  8. examples/states_lookup_table/run.py +6 -4
  9. examples/streamline_wakes/run.py +10 -8
  10. examples/timelines/run.py +100 -98
  11. examples/timeseries/run.py +71 -76
  12. examples/wind_rose/run.py +27 -25
  13. examples/yawed_wake/run.py +85 -81
  14. foxes/algorithms/downwind/downwind.py +5 -5
  15. foxes/algorithms/downwind/models/init_farm_data.py +58 -28
  16. foxes/algorithms/downwind/models/set_amb_farm_results.py +1 -1
  17. foxes/core/algorithm.py +6 -5
  18. foxes/core/data.py +75 -4
  19. foxes/core/data_calc_model.py +4 -2
  20. foxes/core/engine.py +33 -40
  21. foxes/core/farm_data_model.py +16 -13
  22. foxes/core/model.py +19 -1
  23. foxes/core/point_data_model.py +19 -14
  24. foxes/core/rotor_model.py +1 -0
  25. foxes/core/wake_deflection.py +3 -3
  26. foxes/data/states/point_cloud_100.nc +0 -0
  27. foxes/data/states/weibull_cloud_4.nc +0 -0
  28. foxes/data/states/weibull_grid.nc +0 -0
  29. foxes/engines/dask.py +3 -6
  30. foxes/engines/default.py +2 -2
  31. foxes/engines/numpy.py +11 -10
  32. foxes/engines/pool.py +21 -11
  33. foxes/engines/single.py +8 -6
  34. foxes/input/farm_layout/__init__.py +1 -0
  35. foxes/input/farm_layout/from_arrays.py +68 -0
  36. foxes/input/states/__init__.py +7 -1
  37. foxes/input/states/dataset_states.py +710 -0
  38. foxes/input/states/field_data.py +531 -0
  39. foxes/input/states/multi_height.py +2 -0
  40. foxes/input/states/one_point_flow.py +1 -0
  41. foxes/input/states/point_cloud_data.py +618 -0
  42. foxes/input/states/scan.py +2 -0
  43. foxes/input/states/single.py +2 -0
  44. foxes/input/states/states_table.py +13 -23
  45. foxes/input/states/weibull_sectors.py +182 -77
  46. foxes/input/states/wrg_states.py +1 -1
  47. foxes/input/yaml/dict.py +25 -24
  48. foxes/input/yaml/windio/read_attributes.py +40 -27
  49. foxes/input/yaml/windio/read_farm.py +12 -10
  50. foxes/input/yaml/windio/read_outputs.py +25 -15
  51. foxes/input/yaml/windio/read_site.py +121 -12
  52. foxes/input/yaml/windio/windio.py +22 -10
  53. foxes/input/yaml/yaml.py +1 -0
  54. foxes/models/model_book.py +16 -15
  55. foxes/models/rotor_models/__init__.py +1 -0
  56. foxes/models/rotor_models/centre.py +1 -1
  57. foxes/models/rotor_models/direct_infusion.py +241 -0
  58. foxes/models/turbine_models/calculator.py +16 -3
  59. foxes/models/turbine_models/kTI_model.py +1 -0
  60. foxes/models/turbine_models/lookup_table.py +2 -0
  61. foxes/models/turbine_models/power_mask.py +1 -0
  62. foxes/models/turbine_models/rotor_centre_calc.py +2 -0
  63. foxes/models/turbine_models/sector_management.py +1 -0
  64. foxes/models/turbine_models/set_farm_vars.py +3 -8
  65. foxes/models/turbine_models/table_factors.py +2 -0
  66. foxes/models/turbine_models/thrust2ct.py +1 -0
  67. foxes/models/turbine_models/yaw2yawm.py +2 -0
  68. foxes/models/turbine_models/yawm2yaw.py +2 -0
  69. foxes/models/turbine_types/PCt_file.py +2 -4
  70. foxes/models/turbine_types/PCt_from_two.py +1 -0
  71. foxes/models/turbine_types/__init__.py +1 -0
  72. foxes/models/turbine_types/calculator_type.py +123 -0
  73. foxes/models/turbine_types/null_type.py +1 -0
  74. foxes/models/turbine_types/wsrho2PCt_from_two.py +2 -0
  75. foxes/models/turbine_types/wsti2PCt_from_two.py +3 -1
  76. foxes/output/farm_layout.py +2 -0
  77. foxes/output/farm_results_eval.py +4 -1
  78. foxes/output/flow_plots_2d/flow_plots.py +18 -0
  79. foxes/output/flow_plots_2d/get_fig.py +1 -0
  80. foxes/output/output.py +6 -1
  81. foxes/output/results_writer.py +1 -1
  82. foxes/output/rose_plot.py +10 -0
  83. foxes/output/rotor_point_plots.py +3 -0
  84. foxes/output/state_turbine_map.py +3 -0
  85. foxes/output/turbine_type_curves.py +3 -0
  86. foxes/utils/dict.py +46 -34
  87. foxes/utils/factory.py +2 -2
  88. foxes/utils/xarray_utils.py +20 -12
  89. {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/METADATA +32 -52
  90. {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/RECORD +94 -86
  91. foxes/input/states/field_data_nc.py +0 -833
  92. {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/WHEEL +0 -0
  93. {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/entry_points.txt +0 -0
  94. {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/licenses/LICENSE +0 -0
  95. {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/top_level.txt +0 -0
@@ -1,833 +0,0 @@
1
- import numpy as np
2
- import pandas as pd
3
- import xarray as xr
4
- from copy import copy
5
- from scipy.interpolate import interpn
6
-
7
- from foxes.core import States, get_engine
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
- from foxes.config import config, get_input_path
13
-
14
-
15
- def _read_nc_file(
16
- fpath,
17
- coords,
18
- vars,
19
- nc_engine,
20
- sel,
21
- isel,
22
- minimal,
23
- ):
24
- """Helper function for nc file reading"""
25
- data = xr.open_dataset(fpath, engine=nc_engine)
26
- for c in coords:
27
- if c is not None and c not in data:
28
- raise KeyError(
29
- f"Missing coordinate '{c}' in file {fpath}, got: {list(data.coords.keys())}"
30
- )
31
- if minimal:
32
- return data[coords[0]].to_numpy()
33
- else:
34
- data = data[vars]
35
- data.attrs = {}
36
- if isel is not None and len(isel):
37
- data = data.isel(**isel)
38
- if sel is not None and len(sel):
39
- data = data.sel(**sel)
40
- return data
41
-
42
-
43
- class FieldDataNC(States):
44
- """
45
- Heterogeneous ambient states on a regular
46
- horizontal grid in NetCDF format.
47
-
48
- Attributes
49
- ----------
50
- data_source: str or xarray.Dataset
51
- The data or the file search pattern, should end with
52
- suffix '.nc'. One or many files.
53
- ovars: list of str
54
- The output variables
55
- var2ncvar: dict
56
- Mapping from variable names to variable names
57
- in the nc file
58
- fixed_vars: dict
59
- Uniform values for output variables, instead
60
- of reading from data
61
- states_coord: str
62
- The states coordinate name in the data
63
- x_coord: str
64
- The x coordinate name in the data
65
- y_coord: str
66
- The y coordinate name in the data
67
- h_coord: str
68
- The height coordinate name in the data
69
- load_mode: str
70
- The load mode, choices: preload, lazy, fly.
71
- preload loads all data during initialization,
72
- lazy lazy-loads the data using dask, and fly
73
- reads only states index and weights during initialization
74
- and then opens the relevant files again within
75
- the chunk calculation
76
- weight_ncvar: str
77
- Name of the weight data variable in the nc file(s)
78
- bounds_error: bool
79
- Flag for raising errors if bounds are exceeded
80
- fill_value: number
81
- Fill value in case of exceeding bounds, if no bounds error
82
- time_format: str
83
- The datetime parsing format string
84
- interp_nans: bool
85
- Linearly interpolate nan values
86
- interpn_pars: dict, optional
87
- Additional parameters for scipy.interpolate.interpn
88
- bounds_extra_space: float or str
89
- The extra space, either float in m,
90
- or str for units of D, e.g. '2.5D'
91
-
92
- :group: input.states
93
-
94
- """
95
-
96
- def __init__(
97
- self,
98
- data_source,
99
- output_vars,
100
- var2ncvar={},
101
- fixed_vars={},
102
- states_coord="Time",
103
- x_coord="UTMX",
104
- y_coord="UTMY",
105
- h_coord="height",
106
- load_mode="preload",
107
- weight_ncvar=None,
108
- time_format="%Y-%m-%d_%H:%M:%S",
109
- sel=None,
110
- isel=None,
111
- interp_nans=False,
112
- bounds_extra_space=1000,
113
- **interpn_pars,
114
- ):
115
- """
116
- Constructor.
117
-
118
- Parameters
119
- ----------
120
- data_source: str or xarray.Dataset
121
- The data or the file search pattern, should end with
122
- suffix '.nc'. One or many files.
123
- output_vars: list of str
124
- The output variables
125
- var2ncvar: dict, optional
126
- Mapping from variable names to variable names
127
- in the nc file
128
- fixed_vars: dict, optional
129
- Uniform values for output variables, instead
130
- of reading from data
131
- states_coord: str
132
- The states coordinate name in the data
133
- x_coord: str
134
- The x coordinate name in the data
135
- y_coord: str
136
- The y coordinate name in the data
137
- h_coord: str, optional
138
- The height coordinate name in the data
139
- load_mode: str
140
- The load mode, choices: preload, lazy, fly.
141
- preload loads all data during initialization,
142
- lazy lazy-loads the data using dask, and fly
143
- reads only states index and weights during initialization
144
- and then opens the relevant files again within
145
- the chunk calculation
146
- weight_ncvar: str, optional
147
- Name of the weight data variable in the nc file(s)
148
- time_format: str
149
- The datetime parsing format string
150
- sel: dict, optional
151
- Subset selection via xr.Dataset.sel()
152
- isel: dict, optional
153
- Subset selection via xr.Dataset.isel()
154
- interp_nans: bool
155
- Linearly interpolate nan values
156
- bounds_extra_space: float or str, optional
157
- The extra space, either float in m,
158
- or str for units of D, e.g. '2.5D'
159
- interpn_pars: dict, optional
160
- Additional parameters for scipy.interpolate.interpn
161
-
162
- """
163
- super().__init__()
164
-
165
- self.states_coord = states_coord
166
- self.ovars = list(output_vars)
167
- self.fixed_vars = fixed_vars
168
- self.x_coord = x_coord
169
- self.y_coord = y_coord
170
- self.h_coord = h_coord
171
- self.weight_ncvar = weight_ncvar
172
- self.load_mode = load_mode
173
- self.time_format = time_format
174
- self.sel = sel if sel is not None else {}
175
- self.isel = isel if isel is not None else {}
176
- self.interpn_pars = interpn_pars
177
- self.interp_nans = interp_nans
178
- self.bounds_extra_space = bounds_extra_space
179
-
180
- self.var2ncvar = {
181
- v: var2ncvar.get(v, v) for v in output_vars if v not in fixed_vars
182
- }
183
-
184
- self._N = None
185
-
186
- self.__data_source = data_source
187
- self.__inds = None
188
-
189
- @property
190
- def data_source(self):
191
- """
192
- The data source
193
-
194
- Returns
195
- -------
196
- s: object
197
- The data source
198
-
199
- """
200
- if self.load_mode in ["preload", "fly"] and self.running:
201
- raise ValueError(
202
- f"States '{self.name}': Cannot access data_source while running for load mode '{self.load_mode}'"
203
- )
204
- return self.__data_source
205
-
206
- def _get_data(self, ds, coords, verbosity):
207
- """
208
- Helper function for data extraction
209
- """
210
- for ci, c in enumerate(coords):
211
- found = False
212
- if c is not None:
213
- for v in ds.data_vars.values():
214
- if c in v.dims:
215
- found = True
216
- break
217
- if not found:
218
- coords[ci] = None
219
-
220
- dlst = []
221
- for c in coords:
222
- if c is not None:
223
- dlst.append(np.atleast_1d(ds[c].to_numpy()))
224
- else:
225
- dlst.append(np.array([0], dtype=config.dtype_double))
226
- sts, h, y, x = dlst
227
- n_sts, n_h, n_y, n_x = [len(u) for u in dlst]
228
-
229
- cor_shxy = (self.states_coord, self.h_coord, self.x_coord, self.y_coord)
230
- cor_shyx = (self.states_coord, self.h_coord, self.y_coord, self.x_coord)
231
- cor_sxy = (self.states_coord, self.x_coord, self.y_coord)
232
- cor_syx = (self.states_coord, self.y_coord, self.x_coord)
233
- cor_sh = (self.states_coord, self.h_coord)
234
- cor_s = (self.states_coord,)
235
- vars_shyx = []
236
- vars_syx = []
237
- vars_sh = []
238
- vars_s = []
239
- for v, ncv in self.var2ncvar.items():
240
- if ds[ncv].dims == cor_shyx or ds[ncv].dims == cor_shxy:
241
- vars_shyx.append(v)
242
- elif ds[ncv].dims == cor_syx or ds[ncv].dims == cor_sxy:
243
- vars_syx.append(v)
244
- elif ds[ncv].dims == cor_sh:
245
- vars_sh.append(v)
246
- elif ds[ncv].dims == cor_s:
247
- vars_s.append(v)
248
- else:
249
- expc = [
250
- c
251
- for c in [cor_shxy, cor_shyx, cor_sxy, cor_syx, cor_sh, cor_s]
252
- if None not in c
253
- ]
254
- raise ValueError(
255
- f"States '{self.name}': Wrong coordinates for variable '{ncv}': Found {ds[ncv].dims}, expecting one of {expc}"
256
- )
257
-
258
- data = np.zeros(
259
- (n_sts, n_h, n_y, n_x, len(self.var2ncvar)), dtype=config.dtype_double
260
- )
261
- for v in vars_shyx:
262
- ncv = self.var2ncvar[v]
263
- if ds[ncv].dims == cor_shyx:
264
- data[..., self._dkys[v]] = ds[ncv][:]
265
- else:
266
- data[..., self._dkys[v]] = np.swapaxes(ds[ncv].to_numpy(), 2, 3)
267
- for v in vars_syx:
268
- ncv = self.var2ncvar[v]
269
- if ds[ncv].dims == cor_syx:
270
- data[..., self._dkys[v]] = ds[ncv].to_numpy()[:, None]
271
- else:
272
- data[..., self._dkys[v]] = np.swapaxes(ds[ncv].to_numpy(), 1, 2)[
273
- :, None
274
- ]
275
- for v in vars_sh:
276
- ncv = self.var2ncvar[v]
277
- data[..., self._dkys[v]] = ds[ncv].to_numpy()[:, :, None, None]
278
- for v in vars_s:
279
- ncv = self.var2ncvar[v]
280
- data[..., self._dkys[v]] = ds[ncv].to_numpy()[:, None, None, None]
281
- if FV.WD in self.fixed_vars:
282
- data[..., self._dkys[FV.WD]] = np.full(
283
- (n_sts, n_h, n_y, n_x),
284
- self.fixed_vars[FV.WD],
285
- dtype=config.dtype_double,
286
- )
287
-
288
- weights = None
289
- if self.weight_ncvar is not None:
290
- if self.weight_ncvar not in ds.data_vars:
291
- raise KeyError(
292
- f"States '{self.name}': Missing weights variable '{self.weight_ncvar}' in data, found {sorted(list(ds.data_vars.keys()))}"
293
- )
294
- if ds[self.weight_ncvar].dims != (self.states_coord,):
295
- raise ValueError(
296
- f"States '{self.name}': Weights variable '{self.weight_ncvar}' has wrong dimensions. Expecting {(self.states_coord,)}, got {ds[self.weight_ncvar].dims}"
297
- )
298
- weights = ds[self.weight_ncvar].to_numpy()
299
-
300
- if verbosity > 1:
301
- print(f"\n{self.name}: Data ranges")
302
- for v, i in self._dkys.items():
303
- d = data[..., i]
304
- nn = np.sum(np.isnan(d))
305
- print(
306
- f" {v}: {np.nanmin(d)} --> {np.nanmax(d)}, nans: {nn} ({100 * nn / len(d.flat):.2f}%)"
307
- )
308
-
309
- return sts, h, y, x, data, weights
310
-
311
- def output_point_vars(self, algo):
312
- """
313
- The variables which are being modified by the model.
314
-
315
- Parameters
316
- ----------
317
- algo: foxes.core.Algorithm
318
- The calculation algorithm
319
-
320
- Returns
321
- -------
322
- output_vars: list of str
323
- The output variable names
324
-
325
- """
326
- return self.ovars
327
-
328
- def load_data(self, algo, verbosity=0):
329
- """
330
- Load and/or create all model data that is subject to chunking.
331
-
332
- Such data should not be stored under self, for memory reasons. The
333
- data returned here will automatically be chunked and then provided
334
- as part of the mdata object during calculations.
335
-
336
- Parameters
337
- ----------
338
- algo: foxes.core.Algorithm
339
- The calculation algorithm
340
- verbosity: int
341
- The verbosity level, 0 = silent
342
-
343
- Returns
344
- -------
345
- idata: dict
346
- The dict has exactly two entries: `data_vars`,
347
- a dict with entries `name_str -> (dim_tuple, data_ndarray)`;
348
- and `coords`, a dict with entries `dim_name_str -> dim_array`
349
-
350
- """
351
-
352
- # pre-load file reading:
353
- coords = [self.states_coord, self.h_coord, self.y_coord, self.x_coord]
354
- if not isinstance(self.data_source, xr.Dataset):
355
- # check variables:
356
- for v in self.ovars:
357
- if v == FV.WEIGHT and self.weight_ncvar is None:
358
- pass
359
- elif v not in self.var2ncvar and v not in self.fixed_vars:
360
- raise ValueError(
361
- f"States '{self.name}': Variable '{v}' neither found in var2ncvar not in fixed_vars"
362
- )
363
- if (FV.WS in self.ovars and FV.WD not in self.ovars) or (
364
- FV.WS not in self.ovars and FV.WD in self.ovars
365
- ):
366
- raise KeyError(
367
- f"States '{self.name}': Missing '{FV.WS}' or '{FV.WD}' in output variables {self.ovars}"
368
- )
369
-
370
- # check static data:
371
- fpath = get_input_path(self.data_source)
372
- if "*" not in str(self.data_source):
373
- if not fpath.is_file():
374
- fpath = StaticData().get_file_path(
375
- STATES, fpath.name, check_raw=False
376
- )
377
-
378
- # find bounds:
379
- if self.x_coord is not None and self.x_coord not in self.sel:
380
- xy_min, xy_max = algo.farm.get_xy_bounds(
381
- extra_space=self.bounds_extra_space, algo=algo
382
- )
383
- if verbosity > 0:
384
- print(
385
- f"States '{self.name}': Restricting to bounds {xy_min} - {xy_max}"
386
- )
387
- self.sel.update(
388
- {
389
- self.x_coord: slice(xy_min[0], xy_max[1]),
390
- self.y_coord: slice(xy_min[1], xy_max[1]),
391
- }
392
- )
393
-
394
- # read files:
395
- if verbosity > 0:
396
- if self.load_mode == "preload":
397
- print(
398
- f"States '{self.name}': Reading data from '{self.data_source}'"
399
- )
400
- elif self.load_mode == "lazy":
401
- print(
402
- f"States '{self.name}': Reading header from '{self.data_source}'"
403
- )
404
- else:
405
- print(
406
- f"States '{self.name}': Reading states from '{self.data_source}'"
407
- )
408
- files = sorted(list(fpath.resolve().parent.glob(fpath.name)))
409
- vars = list(self.var2ncvar.values())
410
- if self.weight_ncvar is not None:
411
- vars += [self.weight_ncvar]
412
- self.__data_source = get_engine().map(
413
- _read_nc_file,
414
- files,
415
- coords=coords,
416
- vars=vars,
417
- nc_engine=config.nc_engine,
418
- isel=self.isel,
419
- sel=self.sel,
420
- minimal=self.load_mode == "fly",
421
- )
422
-
423
- if self.load_mode in ["preload", "lazy"]:
424
- if self.load_mode == "lazy":
425
- try:
426
- self.__data_source = [ds.chunk() for ds in self.__data_source]
427
- except (ModuleNotFoundError, ValueError) as e:
428
- import_module("dask")
429
- raise e
430
- self.__data_source = xr.concat(
431
- self.__data_source,
432
- dim=self.states_coord,
433
- coords="minimal",
434
- data_vars="minimal",
435
- compat="equals",
436
- join="exact",
437
- combine_attrs="drop",
438
- )
439
- if self.load_mode == "preload":
440
- self.__data_source.load()
441
- self.__inds = self.__data_source[self.states_coord].to_numpy()
442
- self._N = len(self.__inds)
443
-
444
- elif self.load_mode == "fly":
445
- self.__inds = self.__data_source
446
- self.__data_source = fpath
447
- self._files_maxi = {f: len(inds) for f, inds in zip(files, self.__inds)}
448
- self.__inds = np.concatenate(self.__inds, axis=0)
449
- self._N = len(self.__inds)
450
-
451
- else:
452
- raise KeyError(
453
- f"States '{self.name}': Unknown load_mode '{self.load_mode}', choices: preload, lazy, fly"
454
- )
455
-
456
- if self.time_format is not None:
457
- self.__inds = pd.to_datetime(
458
- self.__inds, format=self.time_format
459
- ).to_numpy()
460
-
461
- # given data is already Dataset:
462
- else:
463
- self.__inds = self.data_source[self.states_coord].to_numpy()
464
- self._N = len(self.__inds)
465
-
466
- # ensure WD and WS get the first two slots of data:
467
- self._dkys = {}
468
- if FV.WS in self.ovars:
469
- self._dkys[FV.WD] = 0
470
- if FV.WS in self.var2ncvar:
471
- self._dkys[FV.WS] = 1
472
- for v in self.var2ncvar:
473
- if v not in self._dkys:
474
- self._dkys[v] = len(self._dkys)
475
- self._n_dvars = len(self._dkys)
476
-
477
- idata = super().load_data(algo, verbosity)
478
-
479
- if self.load_mode == "preload":
480
- self.X = self.var(FV.X)
481
- self.Y = self.var(FV.Y)
482
- self.H = self.var(FV.H)
483
- self.VARS = self.var("vars")
484
- self.DATA = self.var("data")
485
- self.WEIGHT = self.var(FV.WEIGHT)
486
-
487
- __, h, y, x, data, weights = self._get_data(
488
- self.data_source, coords, verbosity
489
- )
490
- self._prl_coords = coords
491
-
492
- coos = (FC.STATE, self.H, self.Y, self.X, self.VARS)
493
- data = (coos, data)
494
-
495
- idata["coords"][self.H] = h
496
- idata["coords"][self.Y] = y
497
- idata["coords"][self.X] = x
498
- idata["coords"][self.VARS] = list(self._dkys.keys())
499
- idata["data_vars"][self.DATA] = data
500
- if weights is not None:
501
- idata["data_vars"][self.WEIGHT] = ((FC.STATE,), weights)
502
-
503
- return idata
504
-
505
- def set_running(
506
- self,
507
- algo,
508
- data_stash,
509
- sel=None,
510
- isel=None,
511
- verbosity=0,
512
- ):
513
- """
514
- Sets this model status to running, and moves
515
- all large data to stash.
516
-
517
- The stashed data will be returned by the
518
- unset_running() function after running calculations.
519
-
520
- Parameters
521
- ----------
522
- algo: foxes.core.Algorithm
523
- The calculation algorithm
524
- data_stash: dict
525
- Large data stash, this function adds data here.
526
- Key: model name. Value: dict, large model data
527
- sel: dict, optional
528
- The subset selection dictionary
529
- isel: dict, optional
530
- The index subset selection dictionary
531
- verbosity: int
532
- The verbosity level, 0 = silent
533
-
534
- """
535
- super().set_running(algo, data_stash, sel, isel, verbosity)
536
-
537
- data_stash[self.name] = dict(
538
- inds=self.__inds,
539
- )
540
- del self.__inds
541
-
542
- if self.load_mode == "preload":
543
- data_stash[self.name]["data_source"] = self.__data_source
544
- del self.__data_source
545
-
546
- def unset_running(
547
- self,
548
- algo,
549
- data_stash,
550
- sel=None,
551
- isel=None,
552
- verbosity=0,
553
- ):
554
- """
555
- Sets this model status to not running, recovering large data
556
- from stash
557
-
558
- Parameters
559
- ----------
560
- algo: foxes.core.Algorithm
561
- The calculation algorithm
562
- data_stash: dict
563
- Large data stash, this function adds data here.
564
- Key: model name. Value: dict, large model data
565
- sel: dict, optional
566
- The subset selection dictionary
567
- isel: dict, optional
568
- The index subset selection dictionary
569
- verbosity: int
570
- The verbosity level, 0 = silent
571
-
572
- """
573
- super().unset_running(algo, data_stash, sel, isel, verbosity)
574
-
575
- data = data_stash[self.name]
576
- self.__inds = data.pop("inds")
577
-
578
- if self.load_mode == "preload":
579
- self.__data_source = data.pop("data_source")
580
-
581
- def size(self):
582
- """
583
- The total number of states.
584
-
585
- Returns
586
- -------
587
- int:
588
- The total number of states
589
-
590
- """
591
- return self._N
592
-
593
- def index(self):
594
- """
595
- The index list
596
-
597
- Returns
598
- -------
599
- indices: array_like
600
- The index labels of states, or None for default integers
601
-
602
- """
603
- if self.running:
604
- raise ValueError(f"States '{self.name}': Cannot access index while running")
605
- return self.__inds
606
-
607
- def calculate(self, algo, mdata, fdata, tdata):
608
- """
609
- The main model calculation.
610
-
611
- This function is executed on a single chunk of data,
612
- all computations should be based on numpy arrays.
613
-
614
- Parameters
615
- ----------
616
- algo: foxes.core.Algorithm
617
- The calculation algorithm
618
- mdata: foxes.core.MData
619
- The model data
620
- fdata: foxes.core.FData
621
- The farm data
622
- tdata: foxes.core.TData
623
- The target point data
624
-
625
- Returns
626
- -------
627
- results: dict
628
- The resulting data, keys: output variable str.
629
- Values: numpy.ndarray with shape
630
- (n_states, n_targets, n_tpoints)
631
-
632
- """
633
- # prepare:
634
- n_states = tdata.n_states
635
- n_targets = tdata.n_targets
636
- n_tpoints = tdata.n_tpoints
637
- points = tdata[FC.TARGETS].reshape(n_states, n_targets * n_tpoints, 3)
638
- n_pts = points.shape[1]
639
- n_states = fdata.n_states
640
- coords = [self.states_coord, self.h_coord, self.y_coord, self.x_coord]
641
-
642
- # case preload:
643
- if self.load_mode == "preload":
644
- x = mdata[self.X]
645
- y = mdata[self.Y]
646
- h = mdata[self.H]
647
- data = mdata[self.DATA].copy()
648
- weights = mdata.get(self.WEIGHT, None)
649
- coords = self._prl_coords
650
-
651
- # case lazy:
652
- elif self.load_mode == "lazy":
653
- i0 = mdata.states_i0(counter=True)
654
- s = slice(i0, i0 + n_states)
655
- ds = self.data_source.isel({self.states_coord: s}).load()
656
- __, h, y, x, data, weights = self._get_data(ds, coords, verbosity=0)
657
- del ds
658
-
659
- # case fly:
660
- elif self.load_mode == "fly":
661
- vars = list(self.var2ncvar.values())
662
- if self.weight_ncvar is not None:
663
- vars += [self.weight_ncvar]
664
-
665
- i0 = mdata.states_i0(counter=True)
666
- i1 = i0 + n_states
667
- j0 = 0
668
- data = []
669
- for fpath, n in self._files_maxi.items():
670
- if i0 < j0:
671
- break
672
- else:
673
- j1 = j0 + n
674
- if i0 < j1:
675
- a = i0 - j0
676
- b = min(i1, j1) - j0
677
- isel = copy(self.isel)
678
- isel[self.states_coord] = slice(a, b)
679
-
680
- data.append(
681
- _read_nc_file(
682
- fpath,
683
- coords=coords,
684
- vars=vars,
685
- nc_engine=config.nc_engine,
686
- isel=isel,
687
- sel=self.sel,
688
- minimal=False,
689
- )
690
- )
691
-
692
- i0 += b - a
693
- j0 = j1
694
-
695
- assert i0 == i1, (
696
- f"States '{self.name}': Missing states for load_mode '{self.load_mode}': (i0, i1) = {(i0, i1)}"
697
- )
698
-
699
- data = xr.concat(data, dim=self.states_coord)
700
- __, h, y, x, data, weights = self._get_data(data, coords, verbosity=0)
701
-
702
- else:
703
- raise KeyError(
704
- f"States '{self.name}': Unknown load_mode '{self.load_mode}', choices: preload, lazy, fly"
705
- )
706
-
707
- n_h = len(h)
708
- n_y = len(y)
709
- n_x = len(x)
710
-
711
- # translate WS, WD into U, V:
712
- if FV.WD in self.ovars and FV.WS in self.ovars:
713
- wd = data[..., self._dkys[FV.WD]]
714
- ws = (
715
- data[..., self._dkys[FV.WS]]
716
- if FV.WS in self._dkys
717
- else self.fixed_vars[FV.WS]
718
- )
719
- wdwsi = [self._dkys[FV.WD], self._dkys[FV.WS]]
720
- data[..., wdwsi] = wd2uv(wd, ws, axis=-1)
721
- del ws, wd
722
-
723
- # prepare points:
724
- sts = np.arange(n_states)
725
- pts = np.append(
726
- points, np.zeros((n_states, n_pts, 1), dtype=config.dtype_double), axis=2
727
- )
728
- pts[:, :, 3] = sts[:, None]
729
- pts = pts.reshape(n_states * n_pts, 4)
730
- pts = np.flip(pts, axis=1)
731
- gvars = (sts, h, y, x)
732
-
733
- # reset None coordinate data, since that should not be interpolated:
734
- for i, (c, g) in enumerate(zip(coords, gvars)):
735
- if c is None:
736
- pts[..., i] = g[0]
737
-
738
- # interpolate nan values:
739
- if self.interp_nans and np.any(np.isnan(data)):
740
- df = pd.DataFrame(
741
- index=pd.MultiIndex.from_product(
742
- gvars, names=["state", "height", "y", "x"]
743
- ),
744
- data={
745
- v: data[..., vi].reshape(n_states * n_h * n_y * n_x)
746
- for v, vi in self._dkys.items()
747
- },
748
- )
749
- df.interpolate(
750
- axis=0, method="linear", limit_direction="forward", inplace=True
751
- )
752
- df.interpolate(
753
- axis=0, method="linear", limit_direction="backward", inplace=True
754
- )
755
- data = df.to_numpy().reshape(n_states, n_h, n_y, n_x, self._n_dvars)
756
- del df
757
-
758
- # interpolate:
759
- try:
760
- ipars = dict(bounds_error=True, fill_value=None)
761
- ipars.update(self.interpn_pars)
762
- data = interpn(gvars, data, pts, **ipars).reshape(
763
- n_states, n_pts, self._n_dvars
764
- )
765
- except ValueError as e:
766
- print(f"\nStates '{self.name}': Interpolation error")
767
- print("INPUT VARS: (state, heights, y, x)")
768
- print(
769
- "DATA BOUNDS:",
770
- [float(np.min(d)) for d in gvars],
771
- [float(np.max(d)) for d in gvars],
772
- )
773
- print(
774
- "EVAL BOUNDS:",
775
- [float(np.min(p)) for p in pts.T],
776
- [float(np.max(p)) for p in pts.T],
777
- )
778
- print(
779
- "\nMaybe you want to try the option 'bounds_error=False'? This will extrapolate the data.\n"
780
- )
781
- raise e
782
- del pts, x, y, h, gvars
783
-
784
- # interpolate nan values:
785
- if self.interp_nans and np.any(np.isnan(data)):
786
- df = pd.DataFrame(
787
- index=pd.MultiIndex.from_product(
788
- (sts, range(n_pts)), names=["state", "point"]
789
- ),
790
- data={
791
- v: data[:, :, vi].reshape(n_states * n_pts)
792
- for v, vi in self._dkys.items()
793
- },
794
- )
795
- df["x"] = points[:, :, 0].reshape(n_states * n_pts)
796
- df["y"] = points[:, :, 1].reshape(n_states * n_pts)
797
- df = df.reset_index().set_index(["state", "x", "y"])
798
- df.interpolate(
799
- axis=0, method="linear", limit_direction="forward", inplace=True
800
- )
801
- df.interpolate(
802
- axis=0, method="linear", limit_direction="backward", inplace=True
803
- )
804
- df = df.reset_index().drop(["x", "y"], axis=1).set_index(["state", "point"])
805
- data = df.to_numpy().reshape(n_states, n_pts, self._n_dvars)
806
- del df
807
-
808
- # set output:
809
- out = {}
810
- if FV.WD in self.ovars and FV.WS in self.ovars:
811
- uv = data[..., wdwsi]
812
- out[FV.WS] = np.linalg.norm(uv, axis=-1)
813
- out[FV.WD] = uv2wd(uv, axis=-1)
814
- del uv
815
- for v in self.ovars:
816
- if v != FV.WEIGHT and v not in out:
817
- if v in self._dkys:
818
- out[v] = data[..., self._dkys[v]]
819
- elif v in self.fixed_vars:
820
- out[v] = np.full(
821
- (n_states, n_pts), self.fixed_vars[v], dtype=config.dtype_double
822
- )
823
-
824
- # add weights:
825
- if weights is not None:
826
- tdata[FV.WEIGHT] = weights[:, None, None]
827
- else:
828
- tdata[FV.WEIGHT] = np.full(
829
- (mdata.n_states, 1, 1), 1 / self._N, dtype=config.dtype_double
830
- )
831
- tdata.dims[FV.WEIGHT] = (FC.STATE, FC.TARGET, FC.TPOINT)
832
-
833
- return {v: d.reshape(n_states, n_targets, n_tpoints) for v, d in out.items()}