foxes 0.5.0.2__py3-none-any.whl → 0.5.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 (66) hide show
  1. foxes/VERSION +1 -1
  2. foxes/algorithms/downwind/downwind.py +41 -46
  3. foxes/algorithms/downwind/models/point_wakes_calc.py +4 -9
  4. foxes/algorithms/downwind/models/set_amb_point_results.py +5 -22
  5. foxes/core/algorithm.py +1 -1
  6. foxes/core/data_calc_model.py +26 -2
  7. foxes/core/partial_wakes_model.py +1 -1
  8. foxes/core/rotor_model.py +36 -2
  9. foxes/core/turbine_model.py +36 -0
  10. foxes/core/turbine_type.py +35 -1
  11. foxes/core/wake_frame.py +39 -3
  12. foxes/core/wake_model.py +36 -0
  13. foxes/models/model_book.py +129 -89
  14. foxes/models/turbine_models/rotor_centre_calc.py +1 -2
  15. foxes/models/turbine_types/CpCt_file.py +13 -3
  16. foxes/models/turbine_types/CpCt_from_two.py +14 -4
  17. foxes/models/vertical_profiles/abl_log_neutral_ws.py +32 -5
  18. foxes/models/vertical_profiles/abl_log_stable_ws.py +32 -4
  19. foxes/models/vertical_profiles/abl_log_unstable_ws.py +32 -4
  20. foxes/models/vertical_profiles/abl_log_ws.py +50 -18
  21. foxes/models/wake_frames/yawed_wakes.py +15 -9
  22. foxes/models/wake_models/induction/__init__.py +1 -1
  23. foxes/models/wake_models/induction/rankine_half_body.py +33 -7
  24. foxes/models/wake_models/ti/crespo_hernandez.py +6 -1
  25. foxes/models/wake_models/ti/iec_ti.py +5 -3
  26. foxes/models/wake_models/wind/__init__.py +2 -2
  27. foxes/models/wake_models/wind/{bastankhah.py → bastankhah14.py} +11 -14
  28. foxes/models/wake_models/wind/{porte_agel.py → bastankhah16.py} +24 -16
  29. foxes/models/wake_models/wind/turbopark.py +11 -22
  30. foxes/models/wake_superpositions/__init__.py +9 -5
  31. foxes/models/wake_superpositions/ti_linear.py +134 -0
  32. foxes/models/wake_superpositions/ti_max.py +134 -0
  33. foxes/models/wake_superpositions/{ti_superp.py → ti_pow.py} +15 -57
  34. foxes/models/wake_superpositions/ti_quadratic.py +134 -0
  35. foxes/models/wake_superpositions/ws_linear.py +170 -0
  36. foxes/models/wake_superpositions/ws_max.py +173 -0
  37. foxes/models/wake_superpositions/ws_pow.py +175 -0
  38. foxes/models/wake_superpositions/{product.py → ws_product.py} +43 -22
  39. foxes/models/wake_superpositions/ws_quadratic.py +170 -0
  40. foxes/output/__init__.py +4 -0
  41. foxes/output/calc_points.py +143 -0
  42. foxes/output/flow_plots_2d/__init__.py +1 -0
  43. foxes/output/flow_plots_2d/common.py +104 -1
  44. foxes/output/flow_plots_2d/flow_plots.py +237 -569
  45. foxes/output/flow_plots_2d/get_fig.py +183 -0
  46. foxes/output/flow_plots_2d/seq_flow_ani_plugin.py +0 -1
  47. foxes/output/grids.py +705 -0
  48. foxes/output/output.py +58 -11
  49. foxes/output/results_writer.py +101 -17
  50. foxes/output/round.py +10 -0
  51. foxes/output/slice_data.py +900 -0
  52. foxes/utils/__init__.py +5 -3
  53. foxes/utils/exec_python.py +56 -0
  54. foxes/utils/geopandas_utils.py +294 -0
  55. foxes/utils/pandas_utils.py +175 -0
  56. foxes/utils/plotly_utils.py +19 -0
  57. foxes/utils/xarray_utils.py +38 -0
  58. {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/METADATA +1 -1
  59. {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/RECORD +63 -49
  60. foxes/models/wake_superpositions/linear.py +0 -242
  61. foxes/models/wake_superpositions/max.py +0 -258
  62. foxes/models/wake_superpositions/quadratic.py +0 -252
  63. {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/LICENSE +0 -0
  64. {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/WHEEL +0 -0
  65. {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/top_level.txt +0 -0
  66. {foxes-0.5.0.2.dist-info → foxes-0.5.2.dist-info}/zip-safe +0 -0
foxes/output/grids.py ADDED
@@ -0,0 +1,705 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from xarray import Dataset
4
+
5
+ from foxes.utils import wd2uv, write_nc
6
+ import foxes.variables as FV
7
+ import foxes.constants as FC
8
+
9
+ def calc_point_results(
10
+ algo,
11
+ g_pts,
12
+ farm_results=None,
13
+ seq_iter=None,
14
+ runner=None,
15
+ verbosity=0,
16
+ **kwargs,
17
+ ):
18
+ """
19
+ Helper function that calculates results at grid points.
20
+
21
+ Parameters
22
+ ----------
23
+ algo: foxes.Algorithm
24
+ The algorithm for point calculation
25
+ g_pts: numpy.ndarray
26
+ The grid points, shape: (n_states, n_x, n_y, 3)
27
+ farm_results: xarray.Dataset, optional
28
+ The farm results
29
+ seq_iter: foxes.algorithms.sequential.SequentialIter, optional
30
+ The sequential itarator
31
+ runner: foxes.utils.runners.Runner, optional
32
+ The runner
33
+ verbosity: int
34
+ The verbosity level, 0 = silent
35
+ kwargs: dict, optional
36
+ Additional parameters for algo.calc_points
37
+
38
+ """
39
+ averb = None if verbosity == algo.verbosity else algo.verbosity
40
+ if averb is not None:
41
+ algo.verbosity = verbosity
42
+ fres = farm_results if seq_iter is None else seq_iter.farm_results
43
+ if runner is None:
44
+ point_results = algo.calc_points(fres, points=g_pts, **kwargs)
45
+ else:
46
+ kwargs["points"] = g_pts
47
+ point_results = runner.run(algo.calc_points, args=(fres,), kwargs=kwargs)
48
+ if averb is not None:
49
+ algo.verbosity = averb
50
+
51
+ return point_results
52
+
53
+ def get_grid_xy(
54
+ farm_results,
55
+ resolution,
56
+ xmin=None,
57
+ ymin=None,
58
+ xmax=None,
59
+ ymax=None,
60
+ z=None,
61
+ xspace=500.0,
62
+ yspace=500.0,
63
+ verbosity=0,
64
+ ):
65
+ """
66
+ Helper function that generates 2D grid in a horizontal xy-plane.
67
+
68
+ Parameters
69
+ ----------
70
+ farm_results: xarray.Dataset
71
+ The farm results. The calculated variables have
72
+ dimensions (state, turbine)
73
+ resolution: float
74
+ The resolution in m
75
+ xmin: float
76
+ The min x coordinate, or None for automatic
77
+ ymin: float
78
+ The min y coordinate, or None for automatic
79
+ xmax: float
80
+ The max x coordinate, or None for automatic
81
+ ymax: float
82
+ The max y coordinate, or None for automatic
83
+ z: float
84
+ The z coordinate of the plane
85
+ xspace: float
86
+ The extra space in x direction, before and after wind farm
87
+ yspace: float
88
+ The extra space in y direction, before and after wind farm
89
+ verbosity: int, optional
90
+ The verbosity level
91
+
92
+ Returns
93
+ -------
94
+ x_pos: numpy.ndarray
95
+ The x grid positions, shape: (n_x,)
96
+ y_pos: numpy.ndarray
97
+ The y grid positions, shape: (n_y,)
98
+ z_pos: float
99
+ The z position of the grid
100
+ g_pts: numpy.ndarray
101
+ The grid points, shape: (n_states, n_pts, 3)
102
+
103
+ """
104
+
105
+ # prepare:
106
+ n_states = farm_results[FV.H].shape[0]
107
+
108
+ # get base rectangle:
109
+ x_min = xmin if xmin is not None else farm_results[FV.X].min() - xspace
110
+ y_min = ymin if ymin is not None else farm_results[FV.Y].min() - yspace
111
+ z_min = z if z is not None else farm_results[FV.H].min()
112
+ x_max = xmax if xmax is not None else farm_results[FV.X].max() + xspace
113
+ y_max = ymax if ymax is not None else farm_results[FV.Y].max() + yspace
114
+ z_max = z if z is not None else farm_results[FV.H].max()
115
+
116
+ x_pos, x_res = np.linspace(
117
+ x_min,
118
+ x_max,
119
+ num=int((x_max - x_min) / resolution) + 1,
120
+ endpoint=True,
121
+ retstep=True,
122
+ dtype=None,
123
+ )
124
+ y_pos, y_res = np.linspace(
125
+ y_min,
126
+ y_max,
127
+ num=int((y_max - y_min) / resolution) + 1,
128
+ endpoint=True,
129
+ retstep=True,
130
+ dtype=None,
131
+ )
132
+ N_x, N_y = len(x_pos), len(y_pos)
133
+ n_pts = len(x_pos) * len(y_pos)
134
+ z_pos = 0.5 * (z_min + z_max)
135
+ g_pts = np.zeros((n_states, N_x, N_y, 3), dtype=FC.DTYPE)
136
+ g_pts[:, :, :, 0] = x_pos[None, :, None]
137
+ g_pts[:, :, :, 1] = y_pos[None, None, :]
138
+ g_pts[:, :, :, 2] = z_pos
139
+
140
+ if verbosity > 1:
141
+ print("\nFlowPlots2D plot grid:")
142
+ print("Min XYZ =", x_min, y_min, z_min)
143
+ print("Max XYZ =", x_max, y_max, z_max)
144
+ print("Pos Z =", z_pos)
145
+ print("Res XY =", x_res, y_res)
146
+ print("Dim XY =", N_x, N_y)
147
+ print("Grid pts =", n_pts)
148
+
149
+ return (
150
+ x_pos,
151
+ y_pos,
152
+ z_pos,
153
+ g_pts.reshape(n_states, n_pts, 3),
154
+ )
155
+
156
+ def get_grid_xz(
157
+ farm_results,
158
+ resolution,
159
+ x_direction=270,
160
+ xmin=None,
161
+ zmin=0.0,
162
+ xmax=None,
163
+ zmax=None,
164
+ y=None,
165
+ xspace=500.0,
166
+ zspace=500.0,
167
+ verbosity=0,
168
+ ):
169
+ """
170
+ Helper function that generates 2D grid in a vertical xz-plane.
171
+
172
+ Parameters
173
+ ----------
174
+ farm_results: xarray.Dataset
175
+ The farm results. The calculated variables have
176
+ dimensions (state, turbine)
177
+ resolution: float
178
+ The resolution in m
179
+ x_direction: float
180
+ The direction of the x axis, 0 = north
181
+ xmin: float
182
+ The min x coordinate, or None for automatic
183
+ zmin: float
184
+ The min z coordinate
185
+ xmax: float
186
+ The max x coordinate, or None for automatic
187
+ zmax: float
188
+ The max z coordinate, or None for automatic
189
+ y: float
190
+ The y coordinate of the plane
191
+ xspace: float
192
+ The extra space in x direction, before and after wind farm
193
+ zspace: float
194
+ The extra space in z direction, below and above wind farm
195
+ verbosity: int, optional
196
+ The verbosity level
197
+
198
+ Returns
199
+ -------
200
+ x_pos: numpy.ndarray
201
+ The x grid positions, shape: (n_x,)
202
+ y_pos: float
203
+ The y position of the grid
204
+ z_pos: numpy.ndarray
205
+ The z grid positions, shape: (n_z,)
206
+ g_pts: numpy.ndarray
207
+ The grid points, shape: (n_states, n_pts, 3)
208
+
209
+ """
210
+
211
+ # prepare:
212
+ n_states, n_turbines = farm_results[FV.H].shape
213
+ n_x = np.append(wd2uv(x_direction), [0.0], axis=0)
214
+ n_z = np.array([0.0, 0.0, 1.0])
215
+ n_y = np.cross(n_z, n_x)
216
+
217
+ # project to axes:
218
+ xyz = np.zeros((n_states, n_turbines, 3), dtype=FC.DTYPE)
219
+ xyz[:, :, 0] = farm_results[FV.X]
220
+ xyz[:, :, 1] = farm_results[FV.Y]
221
+ xyz[:, :, 2] = farm_results[FV.H]
222
+ xx = np.einsum("std,d->st", xyz, n_x)
223
+ yy = np.einsum("std,d->st", xyz, n_y)
224
+ zz = np.einsum("std,d->st", xyz, n_z)
225
+ del xyz
226
+
227
+ # get base rectangle:
228
+ x_min = xmin if xmin is not None else np.min(xx) - xspace
229
+ z_min = zmin if zmin is not None else np.minimum(np.min(zz) - zspace, 0.0)
230
+ y_min = y if y is not None else np.min(yy)
231
+ x_max = xmax if xmax is not None else np.max(xx) + xspace
232
+ z_max = zmax if zmax is not None else np.max(zz) + zspace
233
+ y_max = y if y is not None else np.max(yy)
234
+ del xx, yy, zz
235
+
236
+ x_pos, x_res = np.linspace(
237
+ x_min,
238
+ x_max,
239
+ num=int((x_max - x_min) / resolution) + 1,
240
+ endpoint=True,
241
+ retstep=True,
242
+ dtype=None,
243
+ )
244
+ z_pos, z_res = np.linspace(
245
+ z_min,
246
+ z_max,
247
+ num=int((z_max - z_min) / resolution) + 1,
248
+ endpoint=True,
249
+ retstep=True,
250
+ dtype=None,
251
+ )
252
+ N_x, N_z = len(x_pos), len(z_pos)
253
+ n_pts = len(x_pos) * len(z_pos)
254
+ y_pos = 0.5 * (y_min + y_max)
255
+ g_pts = np.zeros((n_states, N_x, N_z, 3), dtype=FC.DTYPE)
256
+ g_pts[:] += x_pos[None, :, None, None] * n_x[None, None, None, :]
257
+ g_pts[:] += y_pos * n_y[None, None, None, :]
258
+ g_pts[:] += z_pos[None, None, :, None] * n_z[None, None, None, :]
259
+
260
+ if verbosity > 1:
261
+ print("\nFlowPlots2D plot grid:")
262
+ print("Min XYZ =", x_min, y_min, z_min)
263
+ print("Max XYZ =", x_max, y_max, z_max)
264
+ print("Pos Y =", y_pos)
265
+ print("Res XZ =", x_res, z_res)
266
+ print("Dim XZ =", N_x, N_z)
267
+ print("Grid pts =", n_pts)
268
+
269
+ return (
270
+ x_pos,
271
+ y_pos,
272
+ z_pos,
273
+ g_pts.reshape(n_states, n_pts, 3),
274
+ )
275
+
276
+ def get_grid_yz(
277
+ farm_results,
278
+ resolution,
279
+ x_direction=270,
280
+ ymin=None,
281
+ zmin=0.0,
282
+ ymax=None,
283
+ zmax=None,
284
+ x=None,
285
+ yspace=500.0,
286
+ zspace=500.0,
287
+ verbosity=0,
288
+ ):
289
+ """
290
+ Helper function that generates 2D grid in a vertical yz-plane.
291
+
292
+ Parameters
293
+ ----------
294
+ farm_results: xarray.Dataset
295
+ The farm results. The calculated variables have
296
+ dimensions (state, turbine)
297
+ resolution: float
298
+ The resolution in m
299
+ x_direction: float
300
+ The direction of the x axis, 0 = north
301
+ ymin: float
302
+ The min y coordinate, or None for automatic
303
+ zmin: float
304
+ The min z coordinate
305
+ ymax: float
306
+ The max y coordinate, or None for automatic
307
+ zmax: float
308
+ The max z coordinate, or None for automatic
309
+ x: float
310
+ The x coordinate of the plane
311
+ yspace: float
312
+ The extra space in y direction, before and after wind farm
313
+ zspace: float
314
+ The extra space in z direction, below and above wind farm
315
+ verbosity: int, optional
316
+ The verbosity level
317
+
318
+ Returns
319
+ -------
320
+ x_pos: float
321
+ The x position of the grid
322
+ y_pos: numpy.ndarray
323
+ The y grid positions, shape: (n_y,)
324
+ z_pos: numpy.ndarray
325
+ The z grid positions, shape: (n_z,)
326
+ g_pts: numpy.ndarray
327
+ The grid points, shape: (n_states, n_pts, 3)
328
+
329
+ """
330
+
331
+ # prepare:
332
+ n_states, n_turbines = farm_results[FV.H].shape
333
+ n_x = np.append(wd2uv(x_direction), [0.0], axis=0)
334
+ n_z = np.array([0.0, 0.0, 1.0])
335
+ n_y = np.cross(n_z, n_x)
336
+
337
+ # project to axes:
338
+ xyz = np.zeros((n_states, n_turbines, 3), dtype=FC.DTYPE)
339
+ xyz[:, :, 0] = farm_results[FV.X]
340
+ xyz[:, :, 1] = farm_results[FV.Y]
341
+ xyz[:, :, 2] = farm_results[FV.H]
342
+ xx = np.einsum("std,d->st", xyz, n_x)
343
+ yy = np.einsum("std,d->st", xyz, n_y)
344
+ zz = np.einsum("std,d->st", xyz, n_z)
345
+ del xyz
346
+
347
+ # get base rectangle:
348
+ y_min = ymin if ymin is not None else np.min(yy) - yspace
349
+ z_min = zmin if zmin is not None else np.minimum(np.min(zz) - zspace, 0.0)
350
+ x_min = x if x is not None else np.min(xx)
351
+ y_max = ymax if ymax is not None else np.max(yy) + yspace
352
+ z_max = zmax if zmax is not None else np.max(zz) + zspace
353
+ x_max = x if x is not None else np.max(xx)
354
+ del xx, yy, zz
355
+
356
+ y_pos, y_res = np.linspace(
357
+ y_min,
358
+ y_max,
359
+ num=int((y_max - y_min) / resolution) + 1,
360
+ endpoint=True,
361
+ retstep=True,
362
+ dtype=None,
363
+ )
364
+ z_pos, z_res = np.linspace(
365
+ z_min,
366
+ z_max,
367
+ num=int((z_max - z_min) / resolution) + 1,
368
+ endpoint=True,
369
+ retstep=True,
370
+ dtype=None,
371
+ )
372
+ N_y, N_z = len(y_pos), len(z_pos)
373
+ n_pts = len(y_pos) * len(z_pos)
374
+ x_pos = 0.5 * (x_min + x_max)
375
+ g_pts = np.zeros((n_states, N_y, N_z, 3), dtype=FC.DTYPE)
376
+ g_pts[:] += x_pos * n_x[None, None, None, :]
377
+ g_pts[:] += y_pos[None, :, None, None] * n_y[None, None, None, :]
378
+ g_pts[:] += z_pos[None, None, :, None] * n_z[None, None, None, :]
379
+
380
+ if verbosity > 1:
381
+ print("\nFlowPlots2D plot grid:")
382
+ print("Min XYZ =", x_min, y_min, z_min)
383
+ print("Max XYZ =", x_max, y_max, z_max)
384
+ print("Pos X =", x_pos)
385
+ print("Res YZ =", y_res, z_res)
386
+ print("Dim YZ =", N_y, N_z)
387
+ print("Grid pts =", n_pts)
388
+
389
+ return (
390
+ x_pos,
391
+ y_pos,
392
+ z_pos,
393
+ g_pts.reshape(n_states, n_pts, 3),
394
+ )
395
+
396
+ def np2np_p(data, a_pos, b_pos):
397
+ """
398
+ Create numpy data from numpy data
399
+
400
+ Parameters
401
+ ----------
402
+ data: dict
403
+ The data on the grid. Key: variable name,
404
+ value: numpy.ndarray with shape (n_gpts,)
405
+ a_pos: numpy.ndarray
406
+ The first axis coordinates, e.g. x_pos,
407
+ shape: (n_a,)
408
+ b_pos: numpy.ndarray
409
+ The second axis coordinates, e.g. y_pos,
410
+ shape: (n_b,)
411
+
412
+ Returns
413
+ -------
414
+ out: dict
415
+ The data on the grid. Key: variable name,
416
+ value: numpy.ndarray with shape (n_a, n_b, n_vars)
417
+
418
+ """
419
+ n_a = len(a_pos)
420
+ n_b = len(b_pos)
421
+ n_v = len(data)
422
+ out = np.zeros((n_a, n_b, n_v), dtype=FC.DTYPE)
423
+ for vi, (v, d) in enumerate(data.items()):
424
+ out[:, :, vi] = d.reshape(n_a, n_b)
425
+ return out
426
+
427
+ def np2np_sp(data, states, a_pos, b_pos):
428
+ """
429
+ Create numpy data from numpy data
430
+
431
+ Parameters
432
+ ----------
433
+ data: dict
434
+ The data on the grid. Key: variable name,
435
+ value: numpy.ndarray with shape (n_states, n_gpts)
436
+ states: numpy.ndarray
437
+ The states index, shape: (n_states,)
438
+ a_pos: numpy.ndarray
439
+ The first axis coordinates, e.g. x_pos,
440
+ shape: (n_a,)
441
+ b_pos: numpy.ndarray
442
+ The second axis coordinates, e.g. y_pos,
443
+ shape: (n_b,)
444
+
445
+ Returns
446
+ -------
447
+ out: dict
448
+ The data on the grid. Key: variable name,
449
+ value: numpy.ndarray with shape (n_states, n_a, n_b, n_vars)
450
+
451
+ """
452
+ n_s = len(states)
453
+ n_a = len(a_pos)
454
+ n_b = len(b_pos)
455
+ n_v = len(data)
456
+ out = np.zeros((n_s, n_a, n_b, n_v), dtype=FC.DTYPE)
457
+ for vi, (v, d) in enumerate(data.items()):
458
+ out[:, :, :, vi] = d.reshape(n_s, n_a, n_b)
459
+ return out
460
+
461
+ def np2pd_p(data, a_pos, b_pos, ori, label_map={}):
462
+ """
463
+ Create pandas DataFrame from numpy data
464
+
465
+ Parameters
466
+ ----------
467
+ data: dict
468
+ The data on the grid. Key: variable name,
469
+ value: numpy.ndarray with shape (n_gpts,)
470
+ a_pos: numpy.ndarray
471
+ The first axis coordinates, e.g. x_pos,
472
+ shape: (n_a,)
473
+ b_pos: numpy.ndarray
474
+ The second axis coordinates, e.g. y_pos,
475
+ shape: (n_b,)
476
+ ori: str
477
+ The orientation, 'xy' or 'xz' or 'yz'
478
+ label_map: dict
479
+ The mapping from original to new field names
480
+
481
+ Returns
482
+ -------
483
+ out: pandas.DataFrame
484
+ The multi-indexed DataFrame object
485
+
486
+ """
487
+ a, b = [label_map.get(o, o) for o in ori]
488
+ n_a = len(a_pos)
489
+ n_b = len(b_pos)
490
+ minds = pd.MultiIndex.from_product([range(n_a), range(n_b)],
491
+ names=[a, b])
492
+ return pd.DataFrame(index=minds, data=data)
493
+
494
+ def np2pd_sp(data, states, a_pos, b_pos, ori, label_map={}):
495
+ """
496
+ Create pandas DataFrame from numpy data
497
+
498
+ Parameters
499
+ ----------
500
+ data: dict
501
+ The data on the grid. Key: variable name,
502
+ value: numpy.ndarray with shape (n_states, n_gpts,)
503
+ states: numpy.ndarray
504
+ The states index, shape: (n_states,)
505
+ a_pos: numpy.ndarray
506
+ The first axis coordinates, e.g. x_pos,
507
+ shape: (n_a,)
508
+ b_pos: numpy.ndarray
509
+ The second axis coordinates, e.g. y_pos,
510
+ shape: (n_b,)
511
+ ori: str
512
+ The orientation, 'xy' or 'xz' or 'yz'
513
+ label_map: dict
514
+ The mapping from original to new field names
515
+
516
+ Returns
517
+ -------
518
+ out: pandas.DataFrame
519
+ The multi-indexed DataFrame object
520
+
521
+ """
522
+ a, b = [label_map.get(o, o) for o in ori]
523
+ s = label_map.get(FC.STATE, FC.STATE)
524
+ n_a = len(a_pos)
525
+ n_b = len(b_pos)
526
+ minds = pd.MultiIndex.from_product([states, range(n_a), range(n_b)],
527
+ names=[s, a, b])
528
+ return pd.DataFrame(index=minds, data=data)
529
+
530
+ def np2xr_p(data, a_pos, b_pos, c_pos, ori, label_map={}):
531
+ """
532
+ Create xarray Dataset from numpy data
533
+
534
+ Parameters
535
+ ----------
536
+ data: dict
537
+ The data on the grid. Key: variable name,
538
+ value: numpy.ndarray with shape (n_gpts,)
539
+ a_pos: numpy.ndarray
540
+ The first axis coordinates, e.g. x_pos,
541
+ shape: (n_a,)
542
+ b_pos: numpy.ndarray
543
+ The second axis coordinates, e.g. y_pos,
544
+ shape: (n_b,)
545
+ ori: str
546
+ The orientation, 'xy' or 'xz' or 'yz'
547
+ label_map: dict
548
+ The mapping from original to new field names
549
+
550
+ Returns
551
+ -------
552
+ out: xarray.Dataset
553
+ The Dataset object
554
+
555
+ """
556
+ a, b = [label_map.get(o, o) for o in ori]
557
+ c = list(set("xyz") - set(ori))[0]
558
+ c = label_map.get(c, c)
559
+ n_a = len(a_pos)
560
+ n_b = len(b_pos)
561
+ return Dataset(
562
+ coords={b: b_pos, a: a_pos},
563
+ data_vars={
564
+ v: ((b, a), np.swapaxes(d.reshape(n_a, n_b), 0, 1))
565
+ for v, d in data.items()
566
+ },
567
+ attrs={c: float(c_pos)}
568
+ )
569
+
570
+ def np2xr_sp(data, states, a_pos, b_pos, c_pos, ori, label_map={}):
571
+ """
572
+ Create xarray Dataset from numpy data
573
+
574
+ Parameters
575
+ ----------
576
+ data: dict
577
+ The data on the grid. Key: variable name,
578
+ value: numpy.ndarray with shape (n_states, n_gpts,)
579
+ states: numpy.ndarray
580
+ The states index, shape: (n_states,)
581
+ a_pos: numpy.ndarray
582
+ The first axis coordinates, e.g. x_pos,
583
+ shape: (n_a,)
584
+ b_pos: numpy.ndarray
585
+ The second axis coordinates, e.g. y_pos,
586
+ shape: (n_b,)
587
+ ori: str
588
+ The orientation, 'xy' or 'xz' or 'yz'
589
+ label_map: dict
590
+ The mapping from original to new field names
591
+
592
+ Returns
593
+ -------
594
+ out: xarray.Dataset
595
+ The Dataset object
596
+
597
+ """
598
+ a, b = [label_map.get(o, o) for o in ori]
599
+ c = list(set("xyz") - set(ori))[0]
600
+ c = label_map.get(c, c)
601
+ s = label_map.get(FC.STATE, FC.STATE)
602
+ n_s = len(states)
603
+ n_a = len(a_pos)
604
+ n_b = len(b_pos)
605
+ return Dataset(
606
+ coords={s: states, b: b_pos, a: a_pos},
607
+ data_vars={
608
+ v: ((s, b, a), np.swapaxes(d.reshape(n_s, n_a, n_b), 1, 2))
609
+ for v, d in data.items()
610
+ },
611
+ attrs={c: float(c_pos)}
612
+ )
613
+
614
+ def data2xr(
615
+ x_pos,
616
+ y_pos,
617
+ z_pos,
618
+ point_results,
619
+ vars=None,
620
+ state_mean=False,
621
+ to_file=None,
622
+ **kwargs,
623
+ ):
624
+ """
625
+ Converts the image data to xarray data
626
+
627
+ Parameter
628
+ ---------
629
+ x_pos: numpy.ndarray or float
630
+ The x grid positions, shape: (n_x, 3)
631
+ y_pos: numpy.ndarray or float
632
+ The y grid positions, shape: (n_y, 3)
633
+ z_pos: numpy.ndarray or float
634
+ The z grid positions, shape: (n_z, 3)
635
+ point_results: xarray.Dataset
636
+ Results of calc_points
637
+ vars: list of str, optional
638
+ Variable selection, or None for all
639
+ state_mean: numpy.ndarray or bool
640
+ Computes mean over states, optionally with
641
+ given weights
642
+ round: dict, optional
643
+ Round variables to given digits, or 'auto'
644
+ for default
645
+ to_file: str, optional
646
+ Write to nc file
647
+ kwargs: dict, optional
648
+ Additional parameters for write_nc
649
+
650
+ Returns
651
+ -------
652
+ ds: xarray.Dataset
653
+ The xarray data object
654
+
655
+ """
656
+ if vars is None:
657
+ vars = list(point_results.data_vars.keys())
658
+ data = {}
659
+ for v in vars:
660
+ if isinstance(state_mean, np.ndarray):
661
+ data[v] = np.einsum('sp,s->p', point_results[v].to_numpy(), state_mean)
662
+ elif state_mean:
663
+ data[v] = np.mean(point_results[v].to_numpy(), axis=0)
664
+ else:
665
+ data[v] = point_results[v].to_numpy()
666
+
667
+ allc = [x_pos, y_pos, z_pos]
668
+ allcn = ["x", "y", "z"]
669
+ ci = [i for i, x in enumerate(allc) if isinstance(x, np.ndarray)]
670
+ cj = [i for i in range(3) if i not in ci][0]
671
+ cl = [len(allc[i]) for i in ci]
672
+ cn = list(reversed([allcn[i] for i in ci]))
673
+
674
+ coords = {}
675
+ attrs = {allcn[cj]: allc[cj].to_numpy()}
676
+ if (
677
+ FC.STATE in point_results.coords and
678
+ isinstance(state_mean, bool)
679
+ and not state_mean
680
+ ):
681
+ if point_results.dims[FC.STATE] > 1:
682
+ coords[FC.STATE] = point_results[FC.STATE].to_numpy()
683
+ else:
684
+ attrs[FC.STATE] = str(point_results[FC.STATE][0].to_numpy())
685
+ coords.update({
686
+ allcn[i]: allc[i] for i in reversed(ci)
687
+ })
688
+
689
+ dvars = {}
690
+ for v, d in data.items():
691
+ if len(d.shape) == 1:
692
+ dvars[v] = (cn, np.swapaxes(d.reshape(*cl), 0, 1))
693
+ else:
694
+ dvars[v] = ([FC.STATE] + cn, np.swapaxes(d.reshape(d.shape[0], *cl), 1, 2))
695
+
696
+ ds = Dataset(
697
+ coords=coords,
698
+ data_vars=dvars,
699
+ attrs=attrs
700
+ )
701
+
702
+ if to_file is not None:
703
+ write_nc(ds, to_file, **kwargs)
704
+
705
+ return ds