foxes 1.2.5__py3-none-any.whl → 1.3__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 (52) hide show
  1. examples/quickstart/run.py +17 -0
  2. foxes/__init__.py +1 -1
  3. foxes/algorithms/downwind/downwind.py +9 -15
  4. foxes/algorithms/downwind/models/farm_wakes_calc.py +13 -7
  5. foxes/algorithms/downwind/models/init_farm_data.py +4 -4
  6. foxes/algorithms/downwind/models/reorder_farm_output.py +5 -1
  7. foxes/algorithms/downwind/models/set_amb_point_results.py +1 -1
  8. foxes/algorithms/iterative/models/farm_wakes_calc.py +6 -3
  9. foxes/algorithms/sequential/models/seq_state.py +0 -18
  10. foxes/algorithms/sequential/sequential.py +5 -18
  11. foxes/constants.py +6 -0
  12. foxes/core/data.py +44 -18
  13. foxes/core/engine.py +19 -1
  14. foxes/core/farm_data_model.py +1 -0
  15. foxes/core/rotor_model.py +42 -38
  16. foxes/core/states.py +2 -47
  17. foxes/input/states/__init__.py +1 -0
  18. foxes/input/states/field_data_nc.py +39 -61
  19. foxes/input/states/multi_height.py +31 -54
  20. foxes/input/states/one_point_flow.py +22 -21
  21. foxes/input/states/scan.py +6 -19
  22. foxes/input/states/single.py +5 -17
  23. foxes/input/states/states_table.py +15 -37
  24. foxes/input/states/wrg_states.py +148 -36
  25. foxes/models/partial_wakes/rotor_points.py +8 -2
  26. foxes/models/partial_wakes/segregated.py +9 -4
  27. foxes/models/rotor_models/centre.py +6 -4
  28. foxes/models/wake_frames/seq_dynamic_wakes.py +5 -2
  29. foxes/models/wake_frames/timelines.py +10 -0
  30. foxes/output/farm_layout.py +12 -4
  31. foxes/output/farm_results_eval.py +36 -12
  32. foxes/output/rose_plot.py +20 -2
  33. foxes/output/slice_data.py +16 -19
  34. {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/METADATA +10 -8
  35. {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/RECORD +52 -51
  36. {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/WHEEL +1 -1
  37. tests/0_consistency/iterative/test_iterative.py +2 -3
  38. tests/0_consistency/partial_wakes/test_partial_wakes.py +2 -2
  39. tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +48 -56
  40. tests/1_verification/flappy_0_6/abl_states/test_abl_states.py +33 -36
  41. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +3 -2
  42. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +3 -3
  43. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +3 -3
  44. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +3 -3
  45. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +3 -3
  46. tests/1_verification/flappy_0_6_2/grid_rotors/test_grid_rotors.py +3 -3
  47. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +3 -2
  48. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +3 -3
  49. tests/3_examples/test_examples.py +3 -2
  50. {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/LICENSE +0 -0
  51. {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/entry_points.txt +0 -0
  52. {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import numpy as np
2
+ from scipy.interpolate import interpn
2
3
 
3
4
  from foxes.core.states import States
4
5
  from foxes.config import config, get_input_path
@@ -25,6 +26,8 @@ class WRGStates(States):
25
26
  bounds_extra_space: float or str
26
27
  The extra space, either float in m,
27
28
  or str for units of D, e.g. '2.5D'
29
+ interpn_pars: dict
30
+ Additional parameters for scipy.interpolate.interpn
28
31
 
29
32
  :group: input.states
30
33
 
@@ -36,7 +39,7 @@ class WRGStates(States):
36
39
  ws_bins,
37
40
  fixed_vars={},
38
41
  bounds_extra_space="1D",
39
- **kwargs,
42
+ **interpn_pars,
40
43
  ):
41
44
  """
42
45
  Constructor
@@ -54,15 +57,16 @@ class WRGStates(States):
54
57
  bounds_extra_space: float or str, optional
55
58
  The extra space, either float in m,
56
59
  or str for units of D, e.g. '2.5D'
57
- kwargs: dict, optional
58
- Parameters for the base class
60
+ interpn_pars: dict, optional
61
+ Additional parameters for scipy.interpolate.interpn
59
62
 
60
63
  """
61
- super().__init__(**kwargs)
64
+ super().__init__()
62
65
  self.wrg_fname = wrg_fname
63
66
  self.ws_bins = np.asarray(ws_bins)
64
67
  self.fixed_vars = fixed_vars
65
68
  self.bounds_extra_space = bounds_extra_space
69
+ self.interpn_pars = interpn_pars
66
70
 
67
71
  def load_data(self, algo, verbosity=0):
68
72
  """
@@ -100,11 +104,14 @@ class WRGStates(States):
100
104
  elif verbosity:
101
105
  print(f"States '{self.name}': Reading file {fpath}")
102
106
  wrg = ReaderWRG(fpath)
103
- self._p0 = np.array([wrg.x0, wrg.y0], dtype=config.dtype_double)
104
- self._nx = wrg.nx
105
- self._ny = wrg.ny
106
- self._ns = wrg.n_sectors
107
- self._res = wrg.resolution
107
+ p0 = np.array([wrg.x0, wrg.y0], dtype=config.dtype_double)
108
+ nx = wrg.nx
109
+ ny = wrg.ny
110
+ ns = wrg.n_sectors
111
+ res = wrg.resolution
112
+ p1 = p0 + np.array([nx * res, ny * res])
113
+ if verbosity > 0:
114
+ print(f"States '{self.name}': Data bounds {p0} - {p1}")
108
115
 
109
116
  # find bounds:
110
117
  if self.bounds_extra_space is not None:
@@ -112,48 +119,56 @@ class WRGStates(States):
112
119
  extra_space=self.bounds_extra_space, algo=algo
113
120
  )
114
121
  if verbosity > 0:
115
- print(
116
- f"States '{self.name}': Restricting to bounds {xy_min} - {xy_max}"
117
- )
118
- xy_min -= self._p0
119
- xy_max -= self._p0
120
- ij_min = np.asarray(xy_min / self._res, dtype=config.dtype_int)
121
- ij_max = np.asarray(xy_max / self._res, dtype=config.dtype_int) + 1
122
+ print(f"States '{self.name}': Farm bounds {xy_min} - {xy_max}")
123
+ ij_min = np.asarray((xy_min - p0) / res, dtype=config.dtype_int)
124
+ ij_max = np.asarray((xy_max - p0) / res, dtype=config.dtype_int) + 1
122
125
  sx = slice(ij_min[0], ij_max[0])
123
126
  sy = slice(ij_min[1], ij_max[1])
124
127
  else:
125
128
  sx = np.s_[:]
126
129
  sy = np.s_[:]
130
+ self._x = p0[0] + np.arange(nx) * res
131
+ self._x = self._x[sx]
132
+ self._y = p0[1] + np.arange(ny) * res
133
+ self._y = self._y[sy]
134
+ if len(self._x) < 2 or len(self._y) < 2:
135
+ raise ValueError(f"No overlap between data bounds and farm bounds")
136
+ p0[0] = np.min(self._x)
137
+ p0[1] = np.min(self._y)
138
+ p1[0] = np.max(self._x)
139
+ p1[1] = np.max(self._y)
140
+ if verbosity > 0:
141
+ print(f"States '{self.name}': New bounds {p0} - {p1}")
127
142
 
128
143
  # store data:
129
144
  A = []
130
145
  k = []
131
- fs = []
132
- for s in range(self._ns):
133
- A.append(wrg.data[f"A_{s}"].to_numpy().reshape(self._ny, self._nx)[sy, sx])
134
- k.append(wrg.data[f"k_{s}"].to_numpy().reshape(self._ny, self._nx)[sy, sx])
135
- fs.append(
136
- wrg.data[f"fs_{s}"].to_numpy().reshape(self._ny, self._nx)[sy, sx]
137
- )
146
+ f = []
147
+ for s in range(ns):
148
+ A.append(wrg.data[f"As_{s}"].to_numpy().reshape(ny, nx)[sy, sx])
149
+ k.append(wrg.data[f"Ks_{s}"].to_numpy().reshape(ny, nx)[sy, sx])
150
+ f.append(wrg.data[f"fs_{s}"].to_numpy().reshape(ny, nx)[sy, sx])
138
151
  del wrg
139
152
  A = np.stack(A, axis=0).T
140
153
  k = np.stack(k, axis=0).T
141
- fs = np.stack(fs, axis=0).T
142
- self._data = np.stack([A, k, fs], axis=-1) # (x, y, wd, AKfs)
154
+ f = np.stack(f, axis=0).T
155
+ self._data = np.stack([A, k, f], axis=-1) # (x, y, wd, AKfs)
143
156
 
144
157
  # store ws and wd:
145
- self.WSWD = self.var("WSWD")
146
- self._wds = np.arange(0.0, 360.0, 360 / self._ns)
158
+ self.VARS = self.var("VARS")
159
+ self.DATA = self.var("DATA")
160
+ self._wds = np.arange(0.0, 360.0, 360 / ns)
147
161
  self._wsd = self.ws_bins[1:] - self.ws_bins[:-1]
148
162
  self._wss = 0.5 * (self.ws_bins[:-1] + self.ws_bins[1:])
149
- self._N = len(self._wss) * self._ns
150
- wswd = np.zeros((len(self._wss), self._ns, 2), dtype=config.dtype_double)
151
- wswd[..., 0] = self._wss[:, None]
152
- wswd[..., 1] = self._wds[None, :]
153
- wswd = wswd.reshape(self._N, 2)
163
+ self._N = len(self._wss) * ns
164
+ data = np.zeros((len(self._wss), ns, 3), dtype=config.dtype_double)
165
+ data[..., 0] = self._wss[:, None]
166
+ data[..., 1] = self._wds[None, :]
167
+ data[..., 2] = self._wsd[:, None]
168
+ data = data.reshape(self._N, 3)
154
169
  idata = super().load_data(algo, verbosity)
155
- idata.coords[self.WSWD] = [self.var(FV.WS), self.var(FV.WD)]
156
- idata.data_vars[self.WSWD] = ((FC.STATE, self.WSWD), wswd)
170
+ idata["coords"][self.VARS] = ["ws", "wd", "dws"]
171
+ idata["data_vars"][self.DATA] = ((FC.STATE, self.VARS), data)
157
172
 
158
173
  return idata
159
174
 
@@ -184,6 +199,103 @@ class WRGStates(States):
184
199
  The output variable names
185
200
 
186
201
  """
187
- ovars = set([FV.WS, FV.WD, FV.WEIGHT])
188
- ovars.update(self.fixed_vars.values())
202
+ ovars = set([FV.WS, FV.WD])
203
+ ovars.update(self.fixed_vars.keys())
189
204
  return list(ovars)
205
+
206
+ def calculate(self, algo, mdata, fdata, tdata):
207
+ """
208
+ The main model calculation.
209
+
210
+ This function is executed on a single chunk of data,
211
+ all computations should be based on numpy arrays.
212
+
213
+ Parameters
214
+ ----------
215
+ algo: foxes.core.Algorithm
216
+ The calculation algorithm
217
+ mdata: foxes.core.MData
218
+ The model data
219
+ fdata: foxes.core.FData
220
+ The farm data
221
+ tdata: foxes.core.TData
222
+ The target point data
223
+
224
+ Returns
225
+ -------
226
+ results: dict
227
+ The resulting data, keys: output variable str.
228
+ Values: numpy.ndarray with shape
229
+ (n_states, n_targets, n_tpoints)
230
+
231
+ """
232
+
233
+ # prepare:
234
+ n_states = tdata.n_states
235
+ n_targets = tdata.n_targets
236
+ n_tpoints = tdata.n_tpoints
237
+ n_pts = n_states * n_targets * n_tpoints
238
+ points = tdata[FC.TARGETS]
239
+ ws = mdata[self.DATA][:, 0]
240
+ wd = mdata[self.DATA][:, 1]
241
+ wsd = mdata[self.DATA][:, 2]
242
+
243
+ out = {}
244
+
245
+ out[FV.WS] = tdata[FV.WS]
246
+ out[FV.WS][:] = ws[:, None, None]
247
+
248
+ out[FV.WD] = tdata[FV.WD]
249
+ out[FV.WD][:] = wd[:, None, None]
250
+
251
+ for v, d in self.fixed_vars.items():
252
+ out[v] = tdata[v]
253
+ out[v][:] = d
254
+
255
+ # interpolate A, k, f from x, y, wd
256
+ z = points[..., 2].copy()
257
+ points[..., 2] = wd[:, None, None]
258
+ pts = points.reshape(n_pts, 3)
259
+ gvars = (self._x, self._y, self._wds)
260
+ try:
261
+ ipars = dict(bounds_error=True, fill_value=None)
262
+ ipars.update(self.interpn_pars)
263
+ data = interpn(gvars, self._data, pts, **ipars).reshape(
264
+ n_states, n_targets, n_tpoints, 3
265
+ )
266
+ except ValueError as e:
267
+ print(f"\nStates '{self.name}': Interpolation error")
268
+ print("INPUT VARS: (x, y, wd)")
269
+ print(
270
+ "DATA BOUNDS:",
271
+ [float(np.min(d)) for d in gvars],
272
+ [float(np.max(d)) for d in gvars],
273
+ )
274
+ print(
275
+ "EVAL BOUNDS:",
276
+ [float(np.min(p)) for p in pts.T],
277
+ [float(np.max(p)) for p in pts.T],
278
+ )
279
+ print(
280
+ "\nMaybe you want to try the option 'bounds_error=False'? This will extrapolate the data.\n"
281
+ )
282
+ raise e
283
+
284
+ A = data[..., 0]
285
+ k = data[..., 1]
286
+ f = data[..., 2]
287
+ points[..., 2] = z
288
+ del data, gvars, pts, z, wd
289
+
290
+ tdata.add(
291
+ FV.WEIGHT,
292
+ f,
293
+ dims=(FC.STATE, FC.TARGET, FC.TPOINT),
294
+ )
295
+
296
+ wsA = out[FV.WS] / A
297
+ tdata[FV.WEIGHT] *= wsd[:, None, None] * (
298
+ k / A * wsA ** (k - 1) * np.exp(-(wsA**k))
299
+ )
300
+
301
+ return out
@@ -91,8 +91,14 @@ class RotorPoints(PartialWakesModel):
91
91
  of shape (n_states, n_rotor_points)
92
92
 
93
93
  """
94
- ares = {v: d[:, downwind_index, None] for v, d in amb_res.items()}
95
- wdel = {v: d[:, downwind_index, None].copy() for v, d in wake_deltas.items()}
94
+ ares = {
95
+ v: d[:, downwind_index, None] if d.shape[1] > 1 else d[:, 0, None]
96
+ for v, d in amb_res.items()
97
+ }
98
+ wdel = {
99
+ v: d[:, downwind_index, None].copy() if d.shape[1] > 1 else d[:, 0, None]
100
+ for v, d in wake_deltas.items()
101
+ }
96
102
  wmodel.finalize_wake_deltas(algo, mdata, fdata, ares, wdel)
97
103
 
98
104
  return {v: d[:, 0] for v, d in wdel.items()}
@@ -140,16 +140,21 @@ class PartialSegregated(PartialWakesModel):
140
140
  wdel = {v: d[:, downwind_index, None].copy() for v, d in wake_deltas.items()}
141
141
 
142
142
  if n_rotor_points == tdata.n_tpoints:
143
- ares = {v: d[:, downwind_index, None] for v, d in amb_res.items()}
143
+ ares = {
144
+ v: d[:, downwind_index, None] if d.shape[1] > 1 else d[:, 0, None]
145
+ for v, d in amb_res.items()
146
+ }
144
147
  else:
145
148
  ares = {}
146
149
  for v, d in amb_res.items():
147
150
  ares[v] = np.zeros(
148
151
  (n_states, 1, tdata.n_tpoints), dtype=config.dtype_double
149
152
  )
150
- ares[v][:] = np.einsum("sp,p->s", d[:, downwind_index], rpoint_weights)[
151
- :, None, None
152
- ]
153
+ ares[v][:] = np.einsum(
154
+ "sp,p->s",
155
+ d[:, downwind_index] if d.shape[1] > 1 else d[:, 0],
156
+ rpoint_weights,
157
+ )[:, None, None]
153
158
 
154
159
  wmodel.finalize_wake_deltas(algo, mdata, fdata, ares, wdel)
155
160
 
@@ -89,7 +89,7 @@ class CentreRotor(RotorModel):
89
89
  mdata,
90
90
  fdata,
91
91
  rpoint_results,
92
- weights,
92
+ rpoint_weights,
93
93
  downwind_index=None,
94
94
  copy_to_ambient=False,
95
95
  ):
@@ -124,9 +124,9 @@ class CentreRotor(RotorModel):
124
124
  variables after calculation
125
125
 
126
126
  """
127
- if len(weights) > 1:
127
+ if len(rpoint_weights) > 1:
128
128
  return super().eval_rpoint_results(
129
- algo, mdata, fdata, rpoint_results, weights, downwind_index
129
+ algo, mdata, fdata, rpoint_results, rpoint_weights, downwind_index
130
130
  )
131
131
 
132
132
  n_states = mdata.n_states
@@ -192,7 +192,9 @@ class CentreRotor(RotorModel):
192
192
  del uvp
193
193
 
194
194
  for v in self.calc_vars:
195
- if v not in vdone:
195
+ if v not in vdone and (
196
+ fdata[v].shape[1] > 1 or downwind_index is None or downwind_index == 0
197
+ ):
196
198
  res = rpoint_results[v][:, :, 0]
197
199
  self._set_res(fdata, v, res, downwind_index)
198
200
  del res
@@ -186,12 +186,15 @@ class SeqDynamicWakes(FarmOrder):
186
186
 
187
187
  # compute wind vectors at wake traces:
188
188
  # TODO: dz from U_z is missing here
189
- hpdata = TData.from_points(points=self._traces_p[None, :N, downwind_index])
189
+ svrs = algo.states.output_point_vars(algo)
190
+ hpdata = TData.from_points(
191
+ points=self._traces_p[None, :N, downwind_index], variables=svrs
192
+ )
190
193
  res = algo.states.calculate(algo, mdata, fdata, hpdata)
191
194
  self._traces_v[:N, downwind_index, :2] = wd2uv(
192
195
  res[FV.WD][0, :, 0], res[FV.WS][0, :, 0]
193
196
  )
194
- del hpdata, res
197
+ del hpdata, res, svrs
195
198
 
196
199
  # find nearest wake point:
197
200
  dists = cdist(points[0], self._traces_p[:N, downwind_index])
@@ -105,6 +105,7 @@ class Timelines(WakeFrame):
105
105
 
106
106
  # calculate all heights:
107
107
  self.timelines_data = {"dxy": (("height", FC.STATE, "dir"), [])}
108
+ weight_data = None
108
109
  for h in heights:
109
110
 
110
111
  if verbosity > 0:
@@ -118,6 +119,13 @@ class Timelines(WakeFrame):
118
119
  )
119
120
 
120
121
  res = states.calculate(algo, mdata, fdata, tdata)
122
+
123
+ if weight_data is None:
124
+ weight_data = ((FC.STATE,), tdata[FV.WEIGHT][:, 0, 0])
125
+ elif not np.all(tdata[FV.WEIGHT] == weight_data[1]):
126
+ raise AssertionError(
127
+ f"States '{self.name}': weight data mismatch between heights"
128
+ )
121
129
  del tdata
122
130
 
123
131
  uv = wd2uv(res[FV.WD], res[FV.WS])[:, 0, 0, :2]
@@ -163,6 +171,8 @@ class Timelines(WakeFrame):
163
171
  },
164
172
  )
165
173
 
174
+ return weight_data
175
+
166
176
  def initialize(self, algo, verbosity=0):
167
177
  """
168
178
  Initializes the model.
@@ -5,8 +5,9 @@ import matplotlib.pyplot as plt
5
5
  from mpl_toolkits.axes_grid1 import make_axes_locatable
6
6
 
7
7
  from foxes.config import config
8
- import foxes.variables as FV
9
8
  from foxes.output.output import Output
9
+ import foxes.variables as FV
10
+ import foxes.constants as FC
10
11
 
11
12
 
12
13
  class FarmLayoutOutput(Output):
@@ -229,9 +230,16 @@ class FarmLayoutOutput(Output):
229
230
  if self.fres is None:
230
231
  raise ValueError(f"Missing farm_results for color_by '{color_by}'")
231
232
  if color_by[:5] == "mean_":
232
- kw["c"] = np.einsum(
233
- "st,st->t", self.fres[color_by[5:]], self.fres[FV.WEIGHT]
234
- )
233
+ weights = self.fres[FV.WEIGHT]
234
+ if weights.dims == (FC.STATE,):
235
+ wx = "s"
236
+ elif weights.dims == (FC.STATE, FC.TURBINE):
237
+ wx = "st"
238
+ else:
239
+ raise ValueError(
240
+ f"Unsupported dimensions for '{FV.WEIGHT}': Expecting '{(FC.STATE,)}' or '{(FC.STATE, FC.TURBINE)}', got '{weights.dims}'"
241
+ )
242
+ kw["c"] = np.einsum(f"st,{wx}->t", self.fres[color_by[5:]], weights)
235
243
  elif color_by[:4] == "sum_":
236
244
  kw["c"] = np.sum(self.fres[color_by[4:]], axis=0)
237
245
  elif color_by[:4] == "min_":
@@ -76,22 +76,46 @@ class FarmResultsEval(Output):
76
76
  nas = np.zeros_like(fields[-1], dtype=bool)
77
77
  nas = nas | np.isnan(fields[-1])
78
78
 
79
- inds = ["st" for v in fields] + ["st"]
80
- expr = ",".join(inds) + "->" + rhs
79
+ inds = ["st" for __ in fields]
80
+ if self.results[FV.WEIGHT].dims == (FC.STATE,):
81
+ inds += ["s"]
82
+
83
+ if np.any(nas):
84
+ sel = ~np.any(nas, axis=1)
85
+ fields = [f[sel] for f in fields]
86
+
87
+ weights0 = self.results[FV.WEIGHT].to_numpy()
88
+ w0 = np.sum(weights0)
89
+ weights = weights0[sel]
90
+ w1 = np.sum(weights)
91
+ weights *= w0 / w1
92
+ fields.append(weights)
93
+
94
+ else:
95
+ fields.append(self.results[FV.WEIGHT].to_numpy())
96
+
97
+ elif self.results[FV.WEIGHT].dims == (FC.STATE, FC.TURBINE):
98
+ inds += ["st"]
81
99
 
82
- if np.any(nas):
83
- sel = ~np.any(nas, axis=1)
84
- fields = [f[sel] for f in fields]
100
+ if np.any(nas):
101
+ sel = ~np.any(nas, axis=1)
102
+ fields = [f[sel] for f in fields]
85
103
 
86
- weights0 = self.results[FV.WEIGHT].to_numpy()
87
- w0 = np.sum(weights0, axis=0)[None, :]
88
- weights = weights0[sel]
89
- w1 = np.sum(weights, axis=0)[None, :]
90
- weights *= w0 / w1
91
- fields.append(weights)
104
+ weights0 = self.results[FV.WEIGHT].to_numpy()
105
+ w0 = np.sum(weights0, axis=0)[None, :]
106
+ weights = weights0[sel]
107
+ w1 = np.sum(weights, axis=0)[None, :]
108
+ weights *= w0 / w1
109
+ fields.append(weights)
110
+
111
+ else:
112
+ fields.append(self.results[FV.WEIGHT].to_numpy())
92
113
 
93
114
  else:
94
- fields.append(self.results[FV.WEIGHT].to_numpy())
115
+ raise ValueError(
116
+ f"Expecting '{FV.WEIGHT}' variable with dimensions {(FC.STATE,)} or {(FC.STATE, FC.TURBINE)}, got {self.results[FV.WEIGHT].dims}"
117
+ )
118
+ expr = ",".join(inds) + "->" + rhs
95
119
 
96
120
  return np.einsum(expr, *fields)
97
121
 
foxes/output/rose_plot.py CHANGED
@@ -151,9 +151,19 @@ class RosePlotOutput(Output):
151
151
  The plot data
152
152
 
153
153
  """
154
+ if self.results[FV.WEIGHT].dims == (FC.STATE,):
155
+ w = self.results[FV.WEIGHT].to_numpy()
156
+ elif self.results[FV.WEIGHT].dims == (FC.STATE, FC.TURBINE):
157
+ w = self.results[FV.WEIGHT].to_numpy()[:, turbine]
158
+ elif self.results[FV.WEIGHT].dims == (FC.STATE, FC.POINT):
159
+ w = self.results[FV.WEIGHT].to_numpy()[:, point]
160
+ else:
161
+ raise ValueError(
162
+ f"Wrong dimensions for '{FV.WEIGHT}'. Expecting {(FC.STATE,)}, {(FC.STATE, FC.TURBINE)} or {(FC.STATE, FC.POINT)}, got {self.results[FV.WEIGHT].dims}"
163
+ )
164
+
154
165
  if add_inf:
155
166
  ws_bins = list(ws_bins) + [np.inf]
156
- w = self.results[FV.WEIGHT].to_numpy()[:, turbine]
157
167
  t = turbine if self._rtype == FC.TURBINE else point
158
168
  ws = self.results[ws_var].to_numpy()[:, t]
159
169
  wd = self.results[wd_var].to_numpy()[:, t].copy()
@@ -449,8 +459,16 @@ class WindRoseBinPlot(Output):
449
459
  The plot data
450
460
 
451
461
  """
462
+ if self.farm_results[FV.WEIGHT].dims == (FC.STATE,):
463
+ w = self.farm_results[FV.WEIGHT].to_numpy()
464
+ elif self.farm_results[FV.WEIGHT].dims == (FC.STATE, FC.TURBINE):
465
+ w = self.farm_results[FV.WEIGHT].to_numpy()[:, turbine]
466
+ else:
467
+ raise ValueError(
468
+ f"Wrong dimensions for '{FV.WEIGHT}'. Expecting {(FC.STATE,)} or {(FC.STATE, FC.TURBINE)}, got {self.farm_results[FV.WEIGHT].dims}"
469
+ )
470
+
452
471
  var = self.farm_results[variable].to_numpy()[:, turbine]
453
- w = self.farm_results[FV.WEIGHT].to_numpy()[:, turbine]
454
472
  ws = self.farm_results[ws_var].to_numpy()[:, turbine]
455
473
  wd = self.farm_results[wd_var].to_numpy()[:, turbine].copy()
456
474
  wd_delta = 360 / wd_sectors
@@ -123,7 +123,6 @@ class SliceData(Output):
123
123
  label_map,
124
124
  vmin,
125
125
  vmax,
126
- weight_turbine,
127
126
  to_file,
128
127
  write_pars,
129
128
  ret_states,
@@ -147,12 +146,22 @@ class SliceData(Output):
147
146
  del g_pts
148
147
 
149
148
  # take mean over states:
150
- weights = self.fres[FV.WEIGHT][:, weight_turbine].to_numpy()
151
- data = {
152
- v: np.einsum("s,sp->p", weights, point_results[v].to_numpy())
153
- for v in variables
154
- }
155
- del point_results
149
+ weights = point_results[FV.WEIGHT].to_numpy()
150
+ if point_results[FV.WEIGHT].dims == (FC.STATE,):
151
+ data = {
152
+ v: np.einsum("s,sp->p", weights, point_results[v].to_numpy())
153
+ for v in variables
154
+ }
155
+ elif point_results[FV.WEIGHT].dims == (FC.STATE, FC.POINT):
156
+ data = {
157
+ v: np.einsum("sp,sp->p", weights, point_results[v].to_numpy())
158
+ for v in variables
159
+ }
160
+ else:
161
+ raise ValueError(
162
+ f"Wrong dimensions for '{FV.WEIGHT}': Expecting {(FC.STATE,)} or {(FC.STATE, FC.POINT)}, got {point_results[FV.WEIGHT].dims}"
163
+ )
164
+ del point_results, weights
156
165
 
157
166
  # apply data modification:
158
167
  a_pos, b_pos, c_pos, data = self._data_mod(
@@ -207,7 +216,6 @@ class SliceData(Output):
207
216
  vmax={},
208
217
  states_sel=None,
209
218
  states_isel=None,
210
- weight_turbine=0,
211
219
  to_file=None,
212
220
  write_pars={},
213
221
  ret_states=False,
@@ -260,8 +268,6 @@ class SliceData(Output):
260
268
  Reduce to selected states
261
269
  states_isel: list, optional
262
270
  Reduce to the selected states indices
263
- weight_turbine: int, optional
264
- Index of the turbine from which to take the weight
265
271
  to_file: str, optional
266
272
  Write data to this file name
267
273
  write_pars: dict
@@ -314,7 +320,6 @@ class SliceData(Output):
314
320
  label_map,
315
321
  vmin,
316
322
  vmax,
317
- weight_turbine,
318
323
  to_file,
319
324
  write_pars,
320
325
  ret_states,
@@ -352,7 +357,6 @@ class SliceData(Output):
352
357
  vmax={},
353
358
  states_sel=None,
354
359
  states_isel=None,
355
- weight_turbine=0,
356
360
  to_file=None,
357
361
  write_pars={},
358
362
  ret_states=False,
@@ -407,8 +411,6 @@ class SliceData(Output):
407
411
  Reduce to selected states
408
412
  states_isel: list, optional
409
413
  Reduce to the selected states indices
410
- weight_turbine: int, optional
411
- Index of the turbine from which to take the weight
412
414
  to_file: str, optional
413
415
  Write data to this file name
414
416
  write_pars: dict
@@ -463,7 +465,6 @@ class SliceData(Output):
463
465
  label_map,
464
466
  vmin,
465
467
  vmax,
466
- weight_turbine,
467
468
  to_file,
468
469
  write_pars,
469
470
  ret_states,
@@ -501,7 +502,6 @@ class SliceData(Output):
501
502
  vmax={},
502
503
  states_sel=None,
503
504
  states_isel=None,
504
- weight_turbine=0,
505
505
  to_file=None,
506
506
  write_pars={},
507
507
  ret_states=False,
@@ -556,8 +556,6 @@ class SliceData(Output):
556
556
  Reduce to selected states
557
557
  states_isel: list, optional
558
558
  Reduce to the selected states indices
559
- weight_turbine: int, optional
560
- Index of the turbine from which to take the weight
561
559
  to_file: str, optional
562
560
  Write data to this file name
563
561
  write_pars: dict
@@ -612,7 +610,6 @@ class SliceData(Output):
612
610
  label_map,
613
611
  vmin,
614
612
  vmax,
615
- weight_turbine,
616
613
  to_file,
617
614
  write_pars,
618
615
  ret_states,
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: foxes
3
- Version: 1.2.5
3
+ Version: 1.3
4
4
  Summary: Farm Optimization and eXtended yield Evaluation Software
5
5
  Author: Jonas Schulte
6
6
  Maintainer: Jonas Schulte
@@ -191,15 +191,17 @@ For detailed examples of how to run _foxes_, check the `examples` and `notebooks
191
191
  ```python
192
192
  import foxes
193
193
 
194
- states = foxes.input.states.Timeseries("timeseries_3000.csv.gz", ["WS", "WD","TI","RHO"])
194
+ if __name__ == "__main__":
195
195
 
196
- farm = foxes.WindFarm()
197
- foxes.input.farm_layout.add_from_file(farm, "test_farm_67.csv", turbine_models=["NREL5MW"])
196
+ states = foxes.input.states.Timeseries("timeseries_3000.csv.gz", ["WS", "WD","TI","RHO"])
198
197
 
199
- algo = foxes.algorithms.Downwind(farm, states, ["Jensen_linear_k007"])
200
- farm_results = algo.calc_farm()
198
+ farm = foxes.WindFarm()
199
+ foxes.input.farm_layout.add_from_file(farm, "test_farm_67.csv", turbine_models=["NREL5MW"])
201
200
 
202
- print(farm_results)
201
+ algo = foxes.algorithms.Downwind(farm, states, ["Jensen_linear_k007"])
202
+ farm_results = algo.calc_farm()
203
+
204
+ print(farm_results)
203
205
  ```
204
206
 
205
207
  ## Testing