foxes 1.2__py3-none-any.whl → 1.2.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 (57) hide show
  1. examples/abl_states/run.py +5 -5
  2. examples/induction/run.py +5 -5
  3. examples/random_timeseries/run.py +13 -13
  4. examples/scan_row/run.py +12 -7
  5. examples/sector_management/run.py +11 -7
  6. examples/single_state/run.py +5 -5
  7. examples/tab_file/run.py +1 -1
  8. examples/timeseries/run.py +5 -5
  9. examples/timeseries_slurm/run.py +5 -5
  10. examples/wind_rose/run.py +1 -1
  11. examples/yawed_wake/run.py +5 -5
  12. foxes/algorithms/downwind/downwind.py +15 -5
  13. foxes/algorithms/sequential/sequential.py +1 -1
  14. foxes/core/algorithm.py +24 -20
  15. foxes/core/axial_induction_model.py +18 -0
  16. foxes/core/engine.py +2 -14
  17. foxes/core/farm_controller.py +18 -0
  18. foxes/core/ground_model.py +19 -0
  19. foxes/core/partial_wakes_model.py +9 -21
  20. foxes/core/point_data_model.py +18 -0
  21. foxes/core/rotor_model.py +2 -18
  22. foxes/core/states.py +2 -17
  23. foxes/core/turbine_model.py +2 -18
  24. foxes/core/turbine_type.py +2 -18
  25. foxes/core/vertical_profile.py +8 -20
  26. foxes/core/wake_frame.py +2 -20
  27. foxes/core/wake_model.py +19 -20
  28. foxes/core/wake_superposition.py +19 -0
  29. foxes/input/states/__init__.py +1 -1
  30. foxes/input/states/field_data_nc.py +14 -1
  31. foxes/input/states/{scan_ws.py → scan.py} +39 -52
  32. foxes/input/yaml/__init__.py +1 -1
  33. foxes/input/yaml/dict.py +317 -50
  34. foxes/input/yaml/yaml.py +5 -5
  35. foxes/output/__init__.py +2 -1
  36. foxes/output/farm_results_eval.py +57 -35
  37. foxes/output/output.py +2 -18
  38. foxes/output/plt.py +19 -0
  39. foxes/output/rose_plot.py +413 -207
  40. foxes/utils/__init__.py +1 -2
  41. foxes/utils/subclasses.py +69 -0
  42. {foxes-1.2.dist-info → foxes-1.2.2.dist-info}/METADATA +1 -2
  43. {foxes-1.2.dist-info → foxes-1.2.2.dist-info}/RECORD +56 -56
  44. tests/0_consistency/iterative/test_iterative.py +1 -1
  45. tests/0_consistency/partial_wakes/test_partial_wakes.py +1 -1
  46. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +7 -2
  47. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +7 -2
  48. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +7 -2
  49. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +7 -2
  50. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +7 -2
  51. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +7 -3
  52. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +7 -2
  53. foxes/utils/windrose_plot.py +0 -152
  54. {foxes-1.2.dist-info → foxes-1.2.2.dist-info}/LICENSE +0 -0
  55. {foxes-1.2.dist-info → foxes-1.2.2.dist-info}/WHEEL +0 -0
  56. {foxes-1.2.dist-info → foxes-1.2.2.dist-info}/entry_points.txt +0 -0
  57. {foxes-1.2.dist-info → foxes-1.2.2.dist-info}/top_level.txt +0 -0
foxes/input/yaml/dict.py CHANGED
@@ -5,13 +5,13 @@ from inspect import signature
5
5
  import foxes.input.farm_layout as farm_layout
6
6
  from foxes.core import States, Engine, WindFarm, Algorithm
7
7
  from foxes.models import ModelBook
8
- from foxes import output
9
- from foxes.utils import Dict
8
+ from foxes.output import Output
9
+ from foxes.utils import Dict, new_cls
10
10
  from foxes.config import config
11
11
  import foxes.constants as FC
12
12
 
13
13
 
14
- def run_dict(
14
+ def read_dict(
15
15
  idict,
16
16
  farm=None,
17
17
  states=None,
@@ -25,7 +25,7 @@ def run_dict(
25
25
  **algo_pars,
26
26
  ):
27
27
  """
28
- Runs foxes from dictionary input
28
+ Read dictionary input into foxes objects
29
29
 
30
30
  Parameters
31
31
  ----------
@@ -58,12 +58,10 @@ def run_dict(
58
58
 
59
59
  Returns
60
60
  -------
61
- farm_results: xarray.Dataset, optional
62
- The farm results
63
- point_results: xarray.Dataset, optional
64
- The point results
65
- output_i: object
66
- For each output either None or the output result
61
+ algo: foxes.core.Algorithm
62
+ The algorithm
63
+ engine: foxes.core.Engine
64
+ The engine, or None if not set
67
65
 
68
66
  :group: input.yaml
69
67
 
@@ -137,12 +135,316 @@ def run_dict(
137
135
  if verbosity is not None:
138
136
  adict["verbosity"] = verbosity - 1
139
137
  if algo_pars is not None:
140
- adict.update(algo_pars)
138
+ adict.update({v: d for v, d in algo_pars.items() if d is not None})
141
139
  algo = Algorithm.new(**adict)
142
140
 
141
+ return algo, engine
142
+
143
+
144
+ def get_output_obj(
145
+ ocls,
146
+ odict,
147
+ algo,
148
+ farm_results=None,
149
+ point_results=None,
150
+ base_class=Output,
151
+ extra_sig={},
152
+ ):
153
+ """
154
+ Create the output object
155
+
156
+ Parameters
157
+ ----------
158
+ ocls: str
159
+ Name of the output class
160
+ odict: dict
161
+ The output dict
162
+ algo: foxes.core.Algorithm
163
+ The algorithm
164
+ farm_results: xarray.Dataset, optional
165
+ The farm results
166
+ point_results: xarray.Dataset, optional
167
+ The point results
168
+ base_class: object
169
+ The output's base class
170
+ extra_sig: dict
171
+ Extra function signature check, sets
172
+ arguments (key) with data (value)
173
+
174
+ Returns
175
+ -------
176
+ obj: object or None
177
+ The output object
178
+
179
+ :group: input.yaml
180
+
181
+ """
182
+ cls = new_cls(base_class, ocls)
183
+ prs = list(signature(cls.__init__).parameters.keys())
184
+ if "algo" in prs:
185
+ assert algo is not None, f"Output of type '{ocls}' requires algo"
186
+ odict["algo"] = algo
187
+ if "farm" in prs:
188
+ odict["farm"] = algo.farm
189
+ if "farm_results" in prs:
190
+ if farm_results is None:
191
+ print(f"No farm results; skipping output {ocls}")
192
+ return None
193
+ odict["farm_results"] = farm_results
194
+ if "point_results" in prs:
195
+ odict["point_results"] = point_results
196
+ for k, v in extra_sig.items():
197
+ if k in prs:
198
+ odict[k] = v
199
+
200
+ return cls(**odict)
201
+
202
+
203
+ def _get_object(rlabels, d):
204
+ """Helper function for object extraction"""
205
+ d = d.replace("]", "")
206
+ i0 = d.find("[")
207
+ if i0 > 0:
208
+ inds = tuple([int(x) for x in d[i0 + 1 :].split(",")])
209
+ return rlabels[d[:i0]][inds]
210
+ else:
211
+ return rlabels[d]
212
+
213
+
214
+ def run_obj_function(
215
+ obj,
216
+ fdict,
217
+ algo,
218
+ rlabels,
219
+ verbosity=None,
220
+ ):
221
+ """
222
+ Runs a function of an object
223
+
224
+ Parameters
225
+ ----------
226
+ obj: object
227
+ The object
228
+ fdict: dict
229
+ The function call dict
230
+ algo: foxes.core.Algorithm
231
+ The algorithm
232
+ rlabels: dict
233
+ Storage for result variables
234
+ verbosity: int, optional
235
+ The verbosity level, 0 = silent
236
+
237
+ Returns
238
+ -------
239
+ results: object
240
+ The returns of the function
241
+
242
+ :group: input.yaml
243
+
244
+ """
245
+
246
+ def _print(*args, level=1, **kwargs):
247
+ if verbosity is None or verbosity >= level:
248
+ print(*args, **kwargs)
249
+
250
+ fname = fdict.pop_item("function")
251
+ _print(f" - {fname}")
252
+ plt_show = fdict.pop("plt_show", False)
253
+ plt_close = fdict.pop("plt_close", False)
254
+ rlbs = fdict.pop("result_labels", None)
255
+
256
+ # grab function:
257
+ ocls = type(obj).__name__
258
+ assert hasattr(obj, fname), f"Output of type '{ocls}': Function '{fname}' not found"
259
+ f = getattr(obj, fname)
260
+
261
+ # add required input data objects:
262
+ prs = list(signature(f).parameters.keys())
263
+ if "algo" in prs:
264
+ fdict["algo"] = algo
265
+ if "farm" in prs:
266
+ fdict["farm"] = algo.farm
267
+
268
+ # replace result labels by objects:
269
+ for k, d in fdict.items():
270
+ if isinstance(d, str) and d[0] == "$":
271
+ fdict[k] = _get_object(rlabels, d)
272
+
273
+ # run function:
274
+ args = fdict.pop("args", tuple())
275
+ results = f(*args, **fdict)
276
+
277
+ # pyplot shortcuts:
278
+ if plt_show:
279
+ plt.show()
280
+ if plt_close:
281
+ results = None
282
+ plt.close()
283
+
284
+ # store results under result labels:
285
+ if rlbs is not None:
286
+
287
+ def _set_label(rlabels, k, r):
288
+ if k not in ["", "none", "None", "_", "__"]:
289
+ assert (
290
+ k[0] == "$"
291
+ ), f"Output {i} of type '{ocls}', function '{fname}': result labels must start with '$', got '{k}'"
292
+ assert (
293
+ "[" not in k and "]" not in k and "," not in k
294
+ ), f"Output {i} of type '{ocls}', function '{fname}': result labels cannot contain '[' or ']' or comma, got '{k}'"
295
+ _print(f" result label {k}: {type(r).__name__}")
296
+ rlabels[k] = r
297
+
298
+ if isinstance(rlbs, (list, tuple)):
299
+ for i, k in enumerate(rlbs):
300
+ _set_label(rlabels, k, results[i])
301
+ else:
302
+ _set_label(rlabels, rlbs, results)
303
+
304
+ return results
305
+
306
+
307
+ def run_outputs(
308
+ idict,
309
+ algo=None,
310
+ farm_results=None,
311
+ point_results=None,
312
+ extra_sig={},
313
+ ret_rlabels=False,
314
+ verbosity=None,
315
+ ):
316
+ """
317
+ Run outputs from dict.
318
+
319
+ Parameters
320
+ ----------
321
+ idict: foxes.utils.Dict
322
+ The input parameter dictionary
323
+ algo: foxes.core.Algorithm, optional
324
+ The algorithm
325
+ farm_results: xarray.Dataset, optional
326
+ The farm results
327
+ point_results: xarray.Dataset, optional
328
+ The point results
329
+ extra_sig: dict
330
+ Extra function signature check, sets
331
+ arguments (key) with data (value)
332
+ ret_rlabels: bool
333
+ Flag for returning results variables
334
+ verbosity: int, optional
335
+ The verbosity level, 0 = silent
336
+
337
+ Returns
338
+ -------
339
+ outputs: list of tuple
340
+ For each output enty, a tuple (dict, results),
341
+ where results is a tuple that represents one
342
+ entry per function call
343
+ rlabels: dict, optional
344
+ The results variables
345
+
346
+ :group: input.yaml
347
+
348
+ """
349
+
350
+ def _print(*args, level=1, **kwargs):
351
+ if verbosity is None or verbosity >= level:
352
+ print(*args, **kwargs)
353
+
354
+ out = []
355
+ rlabels = Dict(name="result_labels")
356
+ if "outputs" in idict:
357
+ _print("Running outputs")
358
+ odicts = [
359
+ Dict(odict, name=f"{idict.name}.output{i}")
360
+ for i, odict in enumerate(idict["outputs"])
361
+ ]
362
+
363
+ for i, d in enumerate(odicts):
364
+ if "output_type" in d:
365
+ ocls = d.pop_item("output_type")
366
+ _print(f" Output {i}: {ocls}")
367
+ d0 = dict(output_type=ocls)
368
+ d0.update(d)
369
+
370
+ flist = [
371
+ Dict(f, name=f"{d.name}.function{j}")
372
+ for j, f in enumerate(d.pop_item("functions"))
373
+ ]
374
+
375
+ o = get_output_obj(
376
+ ocls, d, algo, farm_results, point_results, extra_sig=extra_sig
377
+ )
378
+ if o is None:
379
+ out.append((d0, None))
380
+ continue
381
+
382
+ elif "object" in d:
383
+ ocls = d.pop("object")
384
+ _print(f" Output {i}: Object {ocls}")
385
+ o = _get_object(rlabels, ocls)
386
+ d0 = dict(object=ocls)
387
+ d0.update(d)
388
+ flist = [
389
+ Dict(f, name=f"{d.name}.function{j}")
390
+ for j, f in enumerate(d.pop_item("functions"))
391
+ ]
392
+
393
+ else:
394
+ raise KeyError(
395
+ f"Output {i}: Please specify either 'output_type' or 'object'"
396
+ )
397
+
398
+ fres = []
399
+ for fdict in flist:
400
+ results = run_obj_function(o, fdict, algo, rlabels, verbosity)
401
+ fres.append(results)
402
+ out.append((d0, fres))
403
+
404
+ return out if not ret_rlabels else out, rlabels
405
+
406
+
407
+ def run_dict(idict, *args, verbosity=None, **kwargs):
408
+ """
409
+ Runs foxes from dictionary input
410
+
411
+ Parameters
412
+ ----------
413
+ idict: foxes.utils.Dict
414
+ The input parameter dictionary
415
+ args: tuple, optional
416
+ Additional parameters for read_dict
417
+ verbosity: int, optional
418
+ Force a verbosity level, 0 = silent, overrules
419
+ settings from idict
420
+ kwargs: dict, optional
421
+ Additional parameters for read_dict
422
+
423
+ Returns
424
+ -------
425
+ farm_results: xarray.Dataset, optional
426
+ The farm results
427
+ point_results: xarray.Dataset, optional
428
+ The point results
429
+ outputs: list of tuple
430
+ For each output enty, a tuple (dict, results),
431
+ where results is a tuple that represents one
432
+ entry per function call
433
+
434
+ :group: input.yaml
435
+
436
+ """
437
+
438
+ def _print(*args, level=1, **kwargs):
439
+ if verbosity is None or verbosity >= level:
440
+ print(*args, **kwargs)
441
+
442
+ # read components:
443
+ algo, engine = read_dict(idict, *args, verbosity=verbosity, **kwargs)
444
+
143
445
  # run farm calculation:
144
- rdict = idict.get_item("calc_farm")
145
- if rdict.pop_item("run"):
446
+ rdict = idict.get_item("calc_farm", Dict(name=idict.name + ".calc_farm"))
447
+ if rdict.pop_item("run", True):
146
448
  _print("Running calc_farm")
147
449
  farm_results = algo.calc_farm(**rdict)
148
450
  else:
@@ -150,6 +452,7 @@ def run_dict(
150
452
  out = (farm_results,)
151
453
 
152
454
  # run points calculation:
455
+ point_results = None
153
456
  if "calc_points" in idict:
154
457
  rdict = idict.get_item("calc_points")
155
458
  if rdict.pop_item("run"):
@@ -164,43 +467,7 @@ def run_dict(
164
467
  out += (point_results,)
165
468
 
166
469
  # run outputs:
167
- if "outputs" in idict:
168
- _print("Running outputs")
169
- odict = idict["outputs"]
170
- for ocls, d in odict.items():
171
- _print(f" Output {ocls}")
172
- flist = [
173
- Dict(f, name=f"{d.name}.function{i}")
174
- for i, f in enumerate(d.pop_item("functions"))
175
- ]
176
- try:
177
- cls = getattr(output, ocls)
178
- except AttributeError as e:
179
- print(f"\nClass '{ocls}' not found in outputs. Found:")
180
- prs = list(signature(cls.__init__).parameters.keys())
181
- if "algo" in prs:
182
- d["algo"] = algo
183
- if "farm_results" in prs:
184
- if farm_results is None:
185
- print(f"No farm results; skipping output {ocls}")
186
- for fdict in flist:
187
- out += (None,)
188
- continue
189
- d["farm_results"] = farm_results
190
- o = cls(**d)
191
- for fdict in flist:
192
- fname = fdict.pop_item("name")
193
- _print(f" - {fname}")
194
- plt_show = fdict.pop("plt_show", False)
195
- f = getattr(o, fname)
196
- prs = list(signature(f).parameters.keys())
197
- if "algo" in prs:
198
- fdict["algo"] = algo
199
- res = f(**fdict)
200
- out += (res,) if not isinstance(res, tuple) else res
201
- if plt_show:
202
- plt.show()
203
- plt.close()
470
+ out += (run_outputs(idict, algo, farm_results, point_results, verbosity=verbosity),)
204
471
 
205
472
  # shutdown engine, if created above:
206
473
  if engine is not None:
foxes/input/yaml/yaml.py CHANGED
@@ -24,18 +24,18 @@ def foxes_yaml():
24
24
  help="The input yaml file",
25
25
  )
26
26
  parser.add_argument("-o", "--out_dir", help="The output directory", default=".")
27
- parser.add_argument("-r", "--rotor", help="The rotor model", default="centre")
27
+ parser.add_argument("-r", "--rotor", help="The rotor model", default=None)
28
28
  parser.add_argument(
29
- "-p", "--pwakes", help="The partial wakes models", default="centre", nargs="+"
29
+ "-p", "--pwakes", help="The partial wakes models", default=None, nargs="+"
30
30
  )
31
31
  parser.add_argument(
32
32
  "-w",
33
33
  "--wakes",
34
34
  help="The wake models",
35
- default=["Jensen_linear_k007"],
35
+ default=None,
36
36
  nargs="+",
37
37
  )
38
- parser.add_argument("-f", "--frame", help="The wake frame", default="rotor_wd")
38
+ parser.add_argument("-f", "--frame", help="The wake frame", default=None)
39
39
  parser.add_argument("-e", "--engine", help="The engine", default=None)
40
40
  parser.add_argument(
41
41
  "-n", "--n_procs", help="The number of processes", default=None, type=int
@@ -51,7 +51,7 @@ def foxes_yaml():
51
51
  "-C",
52
52
  "--chunksize_points",
53
53
  help="The chunk size for points",
54
- default=5000,
54
+ default=None,
55
55
  type=int,
56
56
  )
57
57
  parser.add_argument(
foxes/output/__init__.py CHANGED
@@ -6,7 +6,7 @@ from .round import round_defaults
6
6
  from .output import Output
7
7
  from .farm_layout import FarmLayoutOutput
8
8
  from .farm_results_eval import FarmResultsEval
9
- from .rose_plot import RosePlotOutput, StatesRosePlotOutput
9
+ from .rose_plot import RosePlotOutput, StatesRosePlotOutput, WindRoseBinPlot
10
10
  from .results_writer import ResultsWriter
11
11
  from .state_turbine_map import StateTurbineMap
12
12
  from .turbine_type_curves import TurbineTypeCurves
@@ -15,6 +15,7 @@ from .calc_points import PointCalculator
15
15
  from .slice_data import SliceData
16
16
  from .rotor_point_plots import RotorPointPlot
17
17
  from .state_turbine_table import StateTurbineTable
18
+ from .plt import plt
18
19
 
19
20
  from .flow_plots_2d import FlowPlots2D
20
21
  from .seq_plugins import SeqFlowAnimationPlugin, SeqWakeDebugPlugin
@@ -64,7 +64,12 @@ class FarmResultsEval(Output):
64
64
  fields = []
65
65
  for v in vars:
66
66
  if isinstance(v, str):
67
- fields.append(self.results[v].to_numpy())
67
+ vdata = self.results[v].to_numpy()
68
+ nns = np.sum(np.isnan(vdata))
69
+ assert (
70
+ nns == 0
71
+ ), f"Found {nns} nan values for variable '{v}' of shape {vdata.shape}"
72
+ fields.append(vdata)
68
73
  else:
69
74
  fields.append(v)
70
75
  if nas is None:
@@ -99,7 +104,7 @@ class FarmResultsEval(Output):
99
104
  vars_op: dict
100
105
  The operation per variable. Key: str, the variable
101
106
  name. Value: str, the operation, choices
102
- are: sum, mean, min, max.
107
+ are: weights, mean_no_weights, sum, min, max.
103
108
 
104
109
  Returns
105
110
  -------
@@ -111,23 +116,27 @@ class FarmResultsEval(Output):
111
116
 
112
117
  rdata = {}
113
118
  for v, op in vars_op.items():
114
- if op == "mean":
115
- rdata[v] = self.weinsum("t", v)
119
+ vdata = self.results[v].to_numpy()
120
+ nns = np.sum(np.isnan(vdata))
121
+ assert (
122
+ nns == 0
123
+ ), f"Found {nns} nan values for variable '{v}' of shape {vdata.shape}"
124
+
125
+ if op == "weights":
126
+ rdata[v] = self.weinsum("t", vdata)
127
+ elif op == "mean_no_weights":
128
+ rdata[v] = np.mean(vdata, axis=0)
116
129
  elif op == "sum":
117
- vdata = self.results[v].to_numpy()
118
130
  rdata[v] = np.sum(vdata, axis=0)
119
131
  elif op == "min":
120
- vdata = self.results[v].to_numpy()
121
132
  rdata[v] = np.min(vdata, axis=0)
122
133
  elif op == "max":
123
- vdata = self.results[v].to_numpy()
124
134
  rdata[v] = np.max(vdata, axis=0)
125
135
  elif op == "std":
126
- vdata = self.results[v].to_numpy()
127
136
  rdata[v] = np.std(vdata, axis=0)
128
137
  else:
129
138
  raise KeyError(
130
- f"Unknown operation '{op}' for variable '{v}'. Please choose: sum, mean, min, max"
139
+ f"Unknown operation '{op}' for variable '{v}'. Please choose: weights, mean_no_weights, sum, min, max"
131
140
  )
132
141
 
133
142
  data = pd.DataFrame(index=range(n_turbines), data=rdata)
@@ -144,7 +153,7 @@ class FarmResultsEval(Output):
144
153
  vars_op: dict
145
154
  The operation per variable. Key: str, the variable
146
155
  name. Value: str, the operation, choices
147
- are: sum, mean, min, max.
156
+ are: weights, mean_no_weights, sum, min, max.
148
157
 
149
158
  Returns
150
159
  -------
@@ -156,20 +165,25 @@ class FarmResultsEval(Output):
156
165
 
157
166
  rdata = {}
158
167
  for v, op in vars_op.items():
159
- if op == "mean":
160
- rdata[v] = self.weinsum("s", v)
168
+ vdata = self.results[v].to_numpy()
169
+ nns = np.sum(np.isnan(vdata))
170
+ assert (
171
+ nns == 0
172
+ ), f"Found {nns} nan values for variable '{v}' of shape {vdata.shape}"
173
+
174
+ if op == "weights":
175
+ rdata[v] = self.weinsum("s", vdata)
176
+ elif op == "mean_no_weights":
177
+ rdata[v] = np.mean(vdata, axis=1)
161
178
  elif op == "sum":
162
- vdata = self.results[v].to_numpy()
163
179
  rdata[v] = np.sum(vdata, axis=1)
164
180
  elif op == "min":
165
- vdata = self.results[v].to_numpy()
166
181
  rdata[v] = np.min(vdata, axis=1)
167
182
  elif op == "max":
168
- vdata = self.results[v].to_numpy()
169
183
  rdata[v] = np.max(vdata, axis=1)
170
184
  else:
171
185
  raise KeyError(
172
- f"Unknown operation '{op}' for variable '{v}'. Please choose: sum, mean, min, max"
186
+ f"Unknown operation '{op}' for variable '{v}'. Please choose: weights, mean_no_weights, sum, min, max"
173
187
  )
174
188
 
175
189
  data = pd.DataFrame(index=states, data=rdata)
@@ -205,29 +219,32 @@ class FarmResultsEval(Output):
205
219
  rdata = {}
206
220
  for v, op in turbines_op.items():
207
221
  vdata = sdata[v].to_numpy()
208
- if op == "mean":
209
- if states_op[v] == "mean":
222
+ nns = np.sum(np.isnan(vdata))
223
+ assert (
224
+ nns == 0
225
+ ), f"Found {nns} nan values for variable '{v}' of shape {vdata.shape}"
226
+
227
+ if op == "weights":
228
+ if states_op[v] == "weights":
210
229
  rdata[v] = self.weinsum("", v)
211
230
  else:
212
- vdata = sdata[v].to_numpy()
213
231
  rdata[v] = self.weinsum("", vdata[None, :])
232
+ elif op == "mean_no_weights":
233
+ rdata[v] = np.sum(vdata)
214
234
  elif op == "sum":
215
- vdata = sdata[v].to_numpy()
216
235
  rdata[v] = np.sum(vdata)
217
236
  elif op == "min":
218
- vdata = sdata[v].to_numpy()
219
237
  rdata[v] = np.min(vdata)
220
238
  elif op == "max":
221
- vdata = sdata[v].to_numpy()
222
239
  rdata[v] = np.max(vdata)
223
240
  else:
224
241
  raise KeyError(
225
- f"Unknown operation '{op}' for variable '{v}'. Please choose: sum, mean, min, max"
242
+ f"Unknown operation '{op}' for variable '{v}'. Please choose: sum, mean, min, max, weights"
226
243
  )
227
244
 
228
245
  return rdata
229
246
 
230
- def calc_states_mean(self, vars):
247
+ def calc_states_mean(self, vars, use_weights=True):
231
248
  """
232
249
  Calculates the mean wrt states.
233
250
 
@@ -235,6 +252,8 @@ class FarmResultsEval(Output):
235
252
  ----------
236
253
  vars: list of str
237
254
  The variables
255
+ use_weights: bool
256
+ Flag for using states weights for the mean
238
257
 
239
258
  Returns
240
259
  -------
@@ -242,9 +261,10 @@ class FarmResultsEval(Output):
242
261
  The results per turbine
243
262
 
244
263
  """
264
+ r = "weights" if use_weights else "mean_no_weights"
245
265
  if isinstance(vars, str):
246
- return self.reduce_states({vars: "mean"})
247
- return self.reduce_states({v: "mean" for v in vars})
266
+ return self.reduce_states({vars: r})
267
+ return self.reduce_states({v: r for v in vars})
248
268
 
249
269
  def calc_states_sum(self, vars):
250
270
  """
@@ -291,7 +311,7 @@ class FarmResultsEval(Output):
291
311
  The results per state
292
312
 
293
313
  """
294
- return self.reduce_turbines({v: "mean" for v in vars})
314
+ return self.reduce_turbines({v: "mean_no_weights" for v in vars})
295
315
 
296
316
  def calc_turbine_sum(self, vars):
297
317
  """
@@ -325,7 +345,7 @@ class FarmResultsEval(Output):
325
345
  The fully contracted results
326
346
 
327
347
  """
328
- op = {v: "mean" for v in vars}
348
+ op = {v: "weights" for v in vars}
329
349
  return self.reduce_all(states_op=op, turbines_op=op)
330
350
 
331
351
  def calc_farm_sum(self, vars):
@@ -362,7 +382,7 @@ class FarmResultsEval(Output):
362
382
 
363
383
  """
364
384
  v = FV.P if not ambient else FV.AMB_P
365
- cdata = self.reduce_all(states_op={v: "mean"}, turbines_op={v: "sum"})
385
+ cdata = self.reduce_all(states_op={v: "weights"}, turbines_op={v: "sum"})
366
386
  return cdata[v]
367
387
 
368
388
  def calc_turbine_yield(
@@ -536,10 +556,12 @@ class FarmResultsEval(Output):
536
556
  The verbosity level, 0 = silent
537
557
 
538
558
  """
539
- P = self.results[FV.P]
540
- P0 = self.results[FV.AMB_P] + 1e-14
541
- self.results[FV.EFF] = P / P0 # add to farm results
542
- if verbosity:
559
+ P = self.results[FV.P].to_numpy()
560
+ P0 = np.maximum(self.results[FV.AMB_P].to_numpy(), 1e-12)
561
+ eff = np.minimum(P / P0, 1)
562
+ eff[P < 1e-10] = 0
563
+ self.results[FV.EFF] = (self.results[FV.AMB_P].dims, eff)
564
+ if verbosity > 0:
543
565
  print("Efficiency added to farm results")
544
566
 
545
567
  def calc_farm_efficiency(self):
@@ -553,8 +575,8 @@ class FarmResultsEval(Output):
553
575
 
554
576
  """
555
577
  P = self.calc_mean_farm_power()
556
- P0 = self.calc_mean_farm_power(ambient=True) + 1e-14
557
- return P / P0
578
+ P0 = np.maximum(self.calc_mean_farm_power(ambient=True), 1e-14)
579
+ return np.minimum(P / P0, 1)
558
580
 
559
581
  def gen_stdata(
560
582
  self,