foxes 1.5.1__py3-none-any.whl → 1.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.

@@ -167,7 +167,13 @@ def _read_blockage(blockage_model, induction, algo_dict, mbook, verbosity):
167
167
  print(" Name:", wname)
168
168
  print(" Contents:", [k for k in blockage_model.keys()])
169
169
  if wname not in ["None", "none"]:
170
- indc_dict = Dict(wmodel_type=indc_def_map[wname], induction=induction)
170
+ kys = list(blockage_model.keys())
171
+ for k in kys:
172
+ if len(k) > 3 and k[:3] == "ss_":
173
+ blockage_model[k[3:]] = blockage_model.pop_item(k)
174
+ indc_dict = Dict(
175
+ wmodel_type=indc_def_map[wname], induction=induction, **blockage_model
176
+ )
171
177
  mbook.wake_models[wname] = WakeModel.new(**indc_dict)
172
178
  if verbosity > 2:
173
179
  print(f" Created wake model '{wname}':")
@@ -378,7 +384,7 @@ def read_attributes(wio_attrs, idict, mbook, verbosity=1):
378
384
  print(" Reading flow_model")
379
385
  print(" Name:", fmname)
380
386
  print(" Contents:", [k for k in flow_model.keys()])
381
- if fmname != "foxes":
387
+ if verbosity > 0 and fmname != "foxes":
382
388
  print(f"Running flow model 'foxes', overruling original choice '{fmname}'")
383
389
 
384
390
  # read analysis:
@@ -3,7 +3,9 @@ import pandas as pd
3
3
 
4
4
  from foxes.utils import Dict
5
5
  from foxes.core import Turbine, TurbineType, WindFarm
6
+ from foxes.models.farm_controllers import BasicFarmController, OpFlagController
6
7
  import foxes.variables as FV
8
+ import foxes.constants as FC
7
9
 
8
10
 
9
11
  def read_turbine_types(wio_farm, mbook, ws_exp_P, ws_exp_ct, verbosity):
@@ -204,6 +206,22 @@ def read_farm(wio_dict, mbook, verbosity):
204
206
  ws_exp_P = 1
205
207
  ws_exp_ct = 1
206
208
 
209
+ # create farm controller:
210
+ if FV.OPERATING in wio_farm:
211
+ op_dims, operating = wio_farm.pop_item(FV.OPERATING)
212
+ assert (
213
+ len(op_dims) == 2
214
+ and op_dims[1] == FC.TURBINE
215
+ and op_dims[0] in [FC.STATE, FC.TIME]
216
+ ), f"Expecting operating field to have dims (state, turbine), got {op_dims}"
217
+ mbook.farm_controllers["farm_cntrl"] = OpFlagController(operating)
218
+ else:
219
+ mbook.farm_controllers["farm_cntrl"] = BasicFarmController()
220
+ if verbosity > 1:
221
+ print(
222
+ f" Farm controller type: {type(mbook.farm_controllers['farm_cntrl']).__name__}"
223
+ )
224
+
207
225
  # read turbine type:
208
226
  ttypes = read_turbine_types(wio_farm, mbook, ws_exp_P, ws_exp_ct, verbosity)
209
227
 
@@ -211,6 +229,8 @@ def read_farm(wio_dict, mbook, verbosity):
211
229
  farm = WindFarm()
212
230
  wfarm = wio_farm["layouts"]
213
231
  if isinstance(wfarm, dict):
232
+ if "coordinates" in wfarm:
233
+ wfarm = {"0": wfarm}
214
234
  layouts = Dict(wfarm, _name=wio_farm.name + ".layouts")
215
235
  else:
216
236
  layouts = {str(i): lf for i, lf in enumerate(wfarm)}
@@ -24,6 +24,7 @@ wio2foxes = {
24
24
  "reference_height": FV.H,
25
25
  "weibull_a": FV.WEIBULL_A,
26
26
  "weibull_k": FV.WEIBULL_k,
27
+ "operating": FV.OPERATING,
27
28
  }
28
29
 
29
30
  """ Mapping from foxes to windio variables
@@ -137,6 +138,7 @@ def read_wind_resource_field(
137
138
  """
138
139
  if name in [
139
140
  "potential_temperature",
141
+ "real_temperature",
140
142
  "friction_velocity",
141
143
  "k",
142
144
  "epsilon",
@@ -180,6 +182,7 @@ def read_wind_resource_field(
180
182
  "reference_height",
181
183
  "weibull_a",
182
184
  "weibull_k",
185
+ "operating",
183
186
  ] and _read_multi_dimensional_data(name, wio_data, fields, dims):
184
187
  return True
185
188
 
@@ -110,14 +110,28 @@ def _read_flow_field(wio_outs, olist, algo, states_isel, verbosity):
110
110
  assert len(xb) == 2, f"Expecting two entries for x_bounds, got {xb}"
111
111
  yb = z_planes.pop_item("y_bounds")
112
112
  assert len(yb) == 2, f"Expecting two entries for y_bounds, got {yb}"
113
- dx = z_planes.pop_item("dx")
114
- dy = z_planes.pop_item("dy")
115
- nx = max(int((xb[1] - xb[0]) / dx), 1) + 1
116
- if (xb[1] - xb[0]) / (nx - 1) > dx:
117
- nx += 1
118
- ny = max(int((yb[1] - yb[0]) / dy), 1) + 1
119
- if (yb[1] - yb[0]) / (ny - 1) > dy:
120
- ny += 1
113
+ if "dx" in z_planes or "dy" in z_planes:
114
+ assert "dx" in z_planes and "dy" in z_planes, (
115
+ f"Expecting both 'dx' and 'dy' in z_planes, got {list(z_planes.keys())}"
116
+ )
117
+ dx = z_planes.pop_item("dx")
118
+ dy = z_planes.pop_item("dy")
119
+ nx = max(int((xb[1] - xb[0]) / dx), 1) + 1
120
+ if (xb[1] - xb[0]) / (nx - 1) > dx:
121
+ nx += 1
122
+ ny = max(int((yb[1] - yb[0]) / dy), 1) + 1
123
+ if (yb[1] - yb[0]) / (ny - 1) > dy:
124
+ ny += 1
125
+ elif "Nx" in z_planes or "Ny" in z_planes:
126
+ assert "Nx" in z_planes and "Ny" in z_planes, (
127
+ f"Expecting both 'Nx' and 'Ny' in z_planes, got {list(z_planes.keys())}"
128
+ )
129
+ nx = z_planes.pop_item("Nx")
130
+ ny = z_planes.pop_item("Ny")
131
+ else:
132
+ raise KeyError(
133
+ f"Expecting either 'dx' and 'dy' or 'Nx' and 'Ny' in z_planes, got {list(z_planes.keys())}"
134
+ )
121
135
  z_list = np.asarray(z_list)
122
136
  if verbosity > 2:
123
137
  print(" x_bounds :", xb)
@@ -186,11 +200,10 @@ def read_outputs(wio_outs, idict, algo, verbosity=1):
186
200
 
187
201
  # read subset:
188
202
  run_configuration = wio_outs.pop_item("run_configuration", {})
203
+ states_isel = None
189
204
  if "times_run" in run_configuration:
190
205
  times_run = run_configuration.pop_item("times_run")
191
- if times_run.get_item("all_occurences"):
192
- states_isel = None
193
- else:
206
+ if not times_run.get_item("all_occurences"):
194
207
  states_isel = times_run.get_item("subset")
195
208
  elif "wind_speeds_run" in run_configuration:
196
209
  wind_speeds_run = run_configuration.get_item("wind_speeds_run")
@@ -203,7 +216,6 @@ def read_outputs(wio_outs, idict, algo, verbosity=1):
203
216
  raise NotImplementedError(
204
217
  f"Wind speed and direction subsets are not yet supported, got {directions_run.name} {directions_run}"
205
218
  )
206
- states_isel = None
207
219
 
208
220
  # read turbine_outputs:
209
221
  _read_turbine_outputs(wio_outs, olist, algo, states_isel, verbosity)
@@ -11,6 +11,14 @@ import foxes.constants as FC
11
11
  from .read_fields import read_wind_resource_field
12
12
 
13
13
 
14
+ default_values = {
15
+ FV.WS: 8.0,
16
+ FV.WD: 270.0,
17
+ FV.TI: 0.1,
18
+ FV.RHO: 1.225,
19
+ }
20
+
21
+
14
22
  def _get_profiles(coords, fields, dims, ovars, fixval, verbosity):
15
23
  """Read ABL profiles information
16
24
  :group: input.yaml.windio
@@ -22,6 +30,8 @@ def _get_profiles(coords, fields, dims, ovars, fixval, verbosity):
22
30
  print(
23
31
  f"Ignoring '{FV.Z0}', since no reference_height found. No ABL profile activated."
24
32
  )
33
+ fields.pop(FV.Z0)
34
+ dims.pop(FV.Z0)
25
35
  elif FV.MOL in fields:
26
36
  ovars.append(FV.MOL)
27
37
  fixval[FV.H] = fields[FV.H]
@@ -56,7 +66,7 @@ def _get_SingleStateStates(
56
66
 
57
67
  smap = {FV.WS: "ws", FV.WD: "wd", FV.TI: "ti", FV.RHO: "rho"}
58
68
 
59
- data = {smap[v]: d for v, d in fixval.items()}
69
+ data = {smap[v]: fixval.get(v, default_values[v]) for v in ovars}
60
70
  for v, d in coords.items():
61
71
  if v in smap:
62
72
  data[smap[v]] = d
@@ -91,7 +101,7 @@ def _get_Timeseries(
91
101
  print(" selecting class 'Timeseries'")
92
102
 
93
103
  data = {}
94
- fix = {}
104
+ fix = {v: fixval.get(v, default_values[v]) for v in ovars if v not in fields}
95
105
  for v, d in fields.items():
96
106
  if dims[v] == (FC.TIME,):
97
107
  data[v] = d
@@ -99,7 +109,6 @@ def _get_Timeseries(
99
109
  fix[v] = d
100
110
  elif verbosity > 2:
101
111
  print(f" ignoring field '{v}' with dims {dims[v]}")
102
- fix.update({v: d for v, d in fixval.items() if v not in data})
103
112
 
104
113
  sdata = pd.DataFrame(index=coords[FC.TIME], data=data)
105
114
  sdata.index.name = FC.TIME
@@ -132,7 +141,7 @@ def _get_MultiHeightNCTimeseries(
132
141
  )
133
142
 
134
143
  data = {}
135
- fix = {}
144
+ fix = {v: fixval.get(v, default_values[v]) for v in ovars if v not in fields}
136
145
  for v, d in fields.items():
137
146
  if dims[v] == (FC.TIME, FV.H):
138
147
  data[v] = ((FC.TIME, FV.H), d)
@@ -142,7 +151,6 @@ def _get_MultiHeightNCTimeseries(
142
151
  fix[v] = d
143
152
  elif verbosity > 2:
144
153
  print(f" ignoring field '{v}' with dims {dims[v]}")
145
- fix.update({v: d for v, d in fixval.items() if v not in data})
146
154
 
147
155
  sdata = Dataset(coords=coords, data_vars=data)
148
156
  states_dict.update(
@@ -153,6 +161,7 @@ def _get_MultiHeightNCTimeseries(
153
161
  data_source=sdata,
154
162
  output_vars=ovars,
155
163
  fixed_vars=fix,
164
+ bounds_error=False,
156
165
  )
157
166
  )
158
167
  return True
@@ -177,7 +186,7 @@ def _get_WeibullSectors(
177
186
  print(" selecting class 'WeibullSectors'")
178
187
 
179
188
  data = {}
180
- fix = {}
189
+ fix = {v: fixval.get(v, default_values[v]) for v in ovars if v not in fields}
181
190
  c = dims[FV.WEIBULL_A][0]
182
191
  for v, d in fields.items():
183
192
  if dims[v] == (c,):
@@ -186,7 +195,6 @@ def _get_WeibullSectors(
186
195
  fix[v] = d
187
196
  elif verbosity > 2:
188
197
  print(f" ignoring field '{v}' with dims {dims[v]}")
189
- fix.update({v: d for v, d in fixval.items() if v not in data})
190
198
 
191
199
  if FV.WD in coords:
192
200
  data[FV.WD] = coords[FV.WD]
@@ -230,13 +238,12 @@ def _get_WeibullPointCloud(
230
238
  print(" selecting class 'WeibullPointCloud'")
231
239
 
232
240
  data = {}
233
- fix = {}
241
+ fix = {v: fixval.get(v, default_values[v]) for v in ovars if v not in fields}
234
242
  for v, d in fields.items():
235
243
  if len(dims[v]) == 0:
236
244
  fix[v] = d
237
245
  elif v not in fixval:
238
246
  data[v] = (dims[v], d)
239
- fix.update({v: d for v, d in fixval.items() if v not in data})
240
247
 
241
248
  sdata = Dataset(
242
249
  coords=coords,
@@ -282,13 +289,12 @@ def _get_WeibullField(
282
289
  print(" selecting class 'WeibullField'")
283
290
 
284
291
  data = {}
285
- fix = {}
292
+ fix = {v: fixval.get(v, default_values[v]) for v in ovars if v not in fields}
286
293
  for v, d in fields.items():
287
294
  if len(dims[v]) == 0:
288
295
  fix[v] = d
289
296
  elif v not in fixval:
290
297
  data[v] = (dims[v], d)
291
- fix.update({v: d for v, d in fixval.items() if v not in data})
292
298
 
293
299
  sdata = Dataset(
294
300
  coords=coords,
@@ -342,7 +348,7 @@ def get_states(coords, fields, dims, verbosity=1):
342
348
  print(" Creating states")
343
349
 
344
350
  ovars = [FV.WS, FV.WD, FV.TI, FV.RHO]
345
- fixval = {FV.RHO: 1.225}
351
+ fixval = {}
346
352
  profiles = _get_profiles(coords, fields, dims, ovars, fixval, verbosity)
347
353
 
348
354
  states_dict = {}
@@ -421,6 +427,21 @@ def read_site(wio_dict, verbosity=1):
421
427
  dims = Dict(_name="dims")
422
428
  for n, d in wind_resource.items():
423
429
  read_wind_resource_field(n, d, coords, fields, dims, verbosity)
430
+
431
+ # special case: operating field
432
+ if FV.OPERATING in fields:
433
+ wio_dict["wind_farm"][FV.OPERATING] = (
434
+ dims.pop(FV.OPERATING),
435
+ fields.pop(FV.OPERATING),
436
+ )
437
+ if FC.TURBINE in coords:
438
+ if not any([FC.TURBINE in dms for dms in dims.values()]):
439
+ if verbosity > 2:
440
+ print(
441
+ f" Removing coordinate '{FC.TURBINE}', since only relevant for operating flag"
442
+ )
443
+ coords.pop(FC.TURBINE)
444
+
424
445
  if verbosity > 2:
425
446
  print(" Coords:")
426
447
  for c, d in coords.items():
@@ -55,6 +55,7 @@ def read_windio(wio_dict, verbosity=1):
55
55
  algorithm=Dict(
56
56
  algo_type="Downwind",
57
57
  wake_models=[],
58
+ farm_controller="farm_cntrl",
58
59
  _name="wio2fxs.algorithm",
59
60
  verbosity=verbosity - 3,
60
61
  ),
@@ -3,3 +3,4 @@ Farm controller models.
3
3
  """
4
4
 
5
5
  from .basic import BasicFarmController as BasicFarmController
6
+ from .op_flag import OpFlagController as OpFlagController
@@ -0,0 +1,196 @@
1
+ import numpy as np
2
+ from xarray import open_dataset, Dataset
3
+
4
+ from foxes.core import FarmController
5
+ import foxes.constants as FC
6
+ import foxes.variables as FV
7
+
8
+
9
+ class OpFlagController(FarmController):
10
+ """
11
+ A basic controller with a flag for
12
+ turbine operation at each state.
13
+
14
+ Parameters
15
+ ----------
16
+ non_op_values: dict
17
+ The non-operational values for variables,
18
+ keys: variable str, values: float
19
+ var2ncvar: dict
20
+ The mapping of variable names to NetCDF variable names,
21
+ only needed if data_source is a path to a NetCDF file
22
+
23
+ :group: models.farm_controllers
24
+
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ data_source,
30
+ non_op_values=None,
31
+ var2ncvar={},
32
+ **kwargs,
33
+ ):
34
+ """
35
+ Constructor.
36
+
37
+ Parameters
38
+ ----------
39
+ data_source: numpy.ndarray or str
40
+ The operating flag data, shape: (n_states, n_turbines),
41
+ or path to a NetCDF file
42
+ non_op_values: dict, optional
43
+ The non-operational values for variables,
44
+ keys: variable str, values: float
45
+ var2ncvar: dict
46
+ The mapping of variable names to NetCDF variable names,
47
+ only needed if data_source is a path to a NetCDF file
48
+ kwargs: dict, optional
49
+ Additional keyword arguments for the
50
+ base class constructor
51
+
52
+ """
53
+ super().__init__(**kwargs)
54
+ self.data_source = data_source
55
+ self.var2ncvar = var2ncvar
56
+
57
+ self.non_op_values = {
58
+ FV.P: 0.0,
59
+ FV.CT: 0.0,
60
+ }
61
+ if non_op_values is not None:
62
+ self.non_op_values.update(non_op_values)
63
+
64
+ self._op_flags = None
65
+
66
+ def output_farm_vars(self, algo):
67
+ """
68
+ The variables which are being modified by the model.
69
+
70
+ Parameters
71
+ ----------
72
+ algo: foxes.core.Algorithm
73
+ The calculation algorithm
74
+
75
+ Returns
76
+ -------
77
+ output_vars: list of str
78
+ The output variable names
79
+
80
+ """
81
+ vrs = set(super().output_farm_vars(algo))
82
+ vrs.update([FV.OPERATING])
83
+ return list(vrs)
84
+
85
+ def load_data(self, algo, verbosity=0):
86
+ """
87
+ Load and/or create all model data that is subject to chunking.
88
+
89
+ Such data should not be stored under self, for memory reasons. The
90
+ data returned here will automatically be chunked and then provided
91
+ as part of the mdata object during calculations.
92
+
93
+ Parameters
94
+ ----------
95
+ algo: foxes.core.Algorithm
96
+ The calculation algorithm
97
+ verbosity: int
98
+ The verbosity level, 0 = silent
99
+
100
+ Returns
101
+ -------
102
+ idata: dict
103
+ The dict has exactly two entries: `data_vars`,
104
+ a dict with entries `name_str -> (dim_tuple, data_ndarray)`;
105
+ and `coords`, a dict with entries `dim_name_str -> dim_array`
106
+
107
+ """
108
+
109
+ idata = super().load_data(algo, verbosity)
110
+
111
+ if isinstance(self.data_source, np.ndarray):
112
+ self._op_flags = self.data_source
113
+
114
+ elif isinstance(self.data_source, Dataset):
115
+ cop = self.var2ncvar.get(FV.OPERATING, FV.OPERATING)
116
+ self._op_flags = self.data_source[cop].to_numpy()
117
+
118
+ else:
119
+ if verbosity > 0:
120
+ print(f"OpFlagController: Reading data from {self.data_source}")
121
+ ds = open_dataset(self.data_source)
122
+ cop = self.var2ncvar.get(FV.OPERATING, FV.OPERATING)
123
+ self._op_flags = ds[cop].to_numpy()
124
+ del ds
125
+
126
+ assert self._op_flags.shape == (algo.n_states, algo.n_turbines), (
127
+ f"OpFlagController data shape {self._op_flags.shape} does not match "
128
+ f"(n_states, n_turbines)=({algo.n_states}, {algo.n_turbines})"
129
+ )
130
+ op_flags = self._op_flags.astype(bool)
131
+
132
+ off = np.where(~op_flags)
133
+ tmsels = idata["data_vars"][FC.TMODEL_SELS][1]
134
+ tmsels[off[0], off[1], :] = False
135
+ self._tmall = [np.all(t) for t in tmsels]
136
+
137
+ idata["data_vars"][FC.TMODEL_SELS] = (
138
+ (FC.STATE, FC.TURBINE, FC.TMODELS),
139
+ tmsels,
140
+ )
141
+ idata["data_vars"][FV.OPERATING] = (
142
+ (FC.STATE, FC.TURBINE),
143
+ op_flags,
144
+ )
145
+
146
+ return idata
147
+
148
+ def calculate(self, algo, mdata, fdata, pre_rotor, downwind_index=None):
149
+ """
150
+ The main model calculation.
151
+
152
+ This function is executed on a single chunk of data,
153
+ all computations should be based on numpy arrays.
154
+
155
+ Parameters
156
+ ----------
157
+ algo: foxes.core.Algorithm
158
+ The calculation algorithm
159
+ mdata: foxes.core.MData
160
+ The model data
161
+ fdata: foxes.core.FData
162
+ The farm data
163
+ pre_rotor: bool
164
+ Flag for running pre-rotor or post-rotor
165
+ models
166
+ downwind_index: int, optional
167
+ The index in the downwind order
168
+
169
+ Returns
170
+ -------
171
+ results: dict
172
+ The resulting data, keys: output variable str.
173
+ Values: numpy.ndarray with shape (n_states, n_turbines)
174
+
175
+ """
176
+ self.ensure_output_vars(algo, fdata)
177
+
178
+ # compute data for all operating turbines:
179
+ op = mdata[FV.OPERATING].astype(bool)
180
+ fdata[FV.OPERATING] = op
181
+ results = super().calculate(algo, mdata, fdata, pre_rotor, downwind_index)
182
+ results[FV.OPERATING] = fdata[FV.OPERATING]
183
+
184
+ # set non-operating values:
185
+ if downwind_index is None:
186
+ off = np.where(~op)
187
+ for v in self.output_farm_vars(algo):
188
+ if v != FV.OPERATING:
189
+ fdata[v][off[0], off[1]] = self.non_op_values.get(v, np.nan)
190
+ else:
191
+ off = np.where(~op[:, downwind_index])
192
+ for v in self.output_farm_vars(algo):
193
+ if v != FV.OPERATING:
194
+ fdata[v][off[0], downwind_index] = self.non_op_values.get(v, np.nan)
195
+
196
+ return results
@@ -27,6 +27,12 @@ class SelfSimilar(TurbineInductionModel):
27
27
 
28
28
  Attributes
29
29
  ----------
30
+ alpha: float
31
+ The alpha parameter
32
+ beta: float
33
+ The beta parameter
34
+ gamma: float
35
+ The gamma parameter
30
36
  pre_rotor_only: bool
31
37
  Calculate only the pre-rotor region
32
38
  induction: foxes.core.AxialInductionModel or str
@@ -40,6 +46,8 @@ class SelfSimilar(TurbineInductionModel):
40
46
  self,
41
47
  superposition="ws_linear",
42
48
  induction="Madsen",
49
+ alpha=8 / 9,
50
+ beta=np.sqrt(2),
43
51
  gamma=1.1,
44
52
  pre_rotor_only=False,
45
53
  ):
@@ -52,8 +60,12 @@ class SelfSimilar(TurbineInductionModel):
52
60
  The wind speed superposition.
53
61
  induction: foxes.core.AxialInductionModel or str
54
62
  The induction model
55
- gamma: float, default=1.1
56
- The parameter that multiplies Ct in the ct2a calculation
63
+ alpha: float
64
+ The alpha parameter
65
+ beta: float
66
+ The beta parameter
67
+ gamma: float
68
+ The gamma parameter
57
69
  pre_rotor_only: bool
58
70
  Calculate only the pre-rotor region
59
71
 
@@ -61,6 +73,8 @@ class SelfSimilar(TurbineInductionModel):
61
73
  super().__init__(wind_superposition=superposition)
62
74
  self.induction = induction
63
75
  self.pre_rotor_only = pre_rotor_only
76
+ self.alpha = alpha
77
+ self.beta = beta
64
78
  self.gamma = gamma
65
79
 
66
80
  def __repr__(self):
@@ -163,9 +177,13 @@ class SelfSimilar(TurbineInductionModel):
163
177
  """Helper function: using eqn 13 from [2]"""
164
178
  return np.sqrt(0.587 * (1.32 + x_R**2))
165
179
 
166
- def _rad_fn(self, x_R, r_R, beta=np.sqrt(2), alpha=8 / 9):
180
+ def _rad_fn(self, x_R, r_R):
167
181
  """Helper function: define radial shape function (eqn 12 from [1])"""
168
- return (1 / np.cosh(beta * (r_R) / self._r_half(x_R))) ** alpha # * (x_R < 0)
182
+ with np.errstate(over="ignore"):
183
+ result = (
184
+ 1 / np.cosh(self.beta * (r_R) / self._r_half(x_R))
185
+ ) ** self.alpha # * (x_R < 0)
186
+ return result
169
187
 
170
188
  def contribute(
171
189
  self,
@@ -25,7 +25,7 @@ class SelfSimilar2020(SelfSimilar):
25
25
 
26
26
  """
27
27
 
28
- def _a0(self, ct, x_R, gamma=1.1):
28
+ def _a0(self, ct, x_R):
29
29
  """Helper function: define a0 with gamma factor, eqn 8 from [2]"""
30
30
 
31
31
  x_new = np.minimum(np.maximum(-1 * np.abs(x_R), -6), -1)
@@ -218,7 +218,7 @@ class CrespoHernandezTIWake(TopHatWakeModel):
218
218
 
219
219
  # calculate:
220
220
  a = self.induction.ct2a(ct)
221
- beta = (1 - a) / (1 - 2 * a)
221
+ beta = np.maximum((1 - a) / (1 - 2 * a), 0)
222
222
  radius = 2 * (k * x + self.sbeta_factor * np.sqrt(beta) * D)
223
223
 
224
224
  return radius
@@ -194,7 +194,7 @@ class Bastankhah2014(GaussianWakeModel):
194
194
  # calculate sigma:
195
195
  # beta = 0.5 * (1 + np.sqrt(1.0 - ct)) / np.sqrt(1.0 - ct)
196
196
  a = self.induction.ct2a(ct)
197
- beta = (1 - a) / (1 - 2 * a)
197
+ beta = np.maximum((1 - a) / (1 - 2 * a), 0)
198
198
  sigma = k * x + self.sbeta_factor * np.sqrt(beta) * D
199
199
  del beta, a
200
200
 
@@ -423,7 +423,7 @@ class Bastankhah2016(DistSlicedWakeModel):
423
423
  self,
424
424
  superposition,
425
425
  alpha=0.58,
426
- beta=0.07,
426
+ beta=0.077,
427
427
  induction="Madsen",
428
428
  **wake_k,
429
429
  ):
@@ -220,7 +220,7 @@ class TurbOParkWake(GaussianWakeModel):
220
220
  # calculate sigma:
221
221
  # beta = np.sqrt(0.5 * (1 + np.sqrt(1.0 - ct)) / np.sqrt(1.0 - ct))
222
222
  a = self.induction.ct2a(ct)
223
- beta = (1 - a) / (1 - 2 * a)
223
+ beta = np.maximum((1 - a) / (1 - 2 * a), 0)
224
224
  epsilon = self.sbeta_factor * np.sqrt(beta)
225
225
  del a, beta
226
226
 
@@ -39,6 +39,7 @@ class StateTurbineMap(Output):
39
39
  self,
40
40
  variable,
41
41
  title=None,
42
+ cbar_label=None,
42
43
  ax=None,
43
44
  figsize=None,
44
45
  rotate_xlabels=None,
@@ -93,8 +94,11 @@ class StateTurbineMap(Output):
93
94
 
94
95
  ax.set_yticks(turbines[:-1] + 0.5)
95
96
  ax.set_yticklabels(turbines[:-1])
96
- xt = ax.get_xticks()
97
+ xt = np.asarray(ax.get_xticks())
97
98
  xtl = ax.get_xticklabels()
99
+ if xt.dtype != np.datetime64:
100
+ xt, ar = np.unique(xt.astype(int), return_index=True)
101
+ xtl = [int(float(xtl[i].get_text())) for i in ar]
98
102
  ax.set_xticks(
99
103
  xt[:-1] + 0.5 * (xt[-1] - xt[-2]), xtl[:-1], rotation=rotate_xlabels
100
104
  )
@@ -103,10 +107,9 @@ class StateTurbineMap(Output):
103
107
  ytl = [None for t in yt]
104
108
  ytl[::5] = ax.get_yticklabels()[::5]
105
109
  ax.set_yticks(yt, ytl)
106
- fig.colorbar(c, ax=ax)
110
+ fig.colorbar(c, ax=ax, label=cbar_label)
107
111
 
108
- t = title if title is not None else variable
109
- ax.set_title(t)
112
+ ax.set_title(title)
110
113
  ax.set_ylabel("Turbine index")
111
114
  ax.set_xlabel("State")
112
115
 
foxes/variables.py CHANGED
@@ -1,3 +1,8 @@
1
+ OPERATING = "operating"
2
+ """ Flag for turbine operation
3
+ :group: foxes.variables
4
+ """
5
+
1
6
  X = "X"
2
7
  """ The x coordinate in m
3
8
  :group: foxes.variables
@@ -19,7 +24,7 @@ D = "D"
19
24
  """
20
25
 
21
26
  TXYH = "txyh"
22
- """ The turbine rotor centre coordinate
27
+ """ The turbine rotor centre coordinate
23
28
  vector (x, y, height)
24
29
  :group: foxes.variables
25
30
  """
@@ -190,13 +195,13 @@ AMB_RHO = "AMB_RHO"
190
195
  """
191
196
 
192
197
  AMB_YAW = "AMB_YAW"
193
- """ The ambient absolute yaw angle of
198
+ """ The ambient absolute yaw angle of
194
199
  a turbine in degrees
195
200
  :group: foxes.variables
196
201
  """
197
202
 
198
203
  AMB_YAWM = "AMB_YAWM"
199
- """ The ambient relative yaw angle of
204
+ """ The ambient relative yaw angle of
200
205
  a turbine in degrees
201
206
  :group: foxes.variables
202
207
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foxes
3
- Version: 1.5.1
3
+ Version: 1.5.2
4
4
  Summary: Farm Optimization and eXtended yield Evaluation Software
5
5
  Author: Jonas Schulte
6
6
  Maintainer: Jonas Schulte
@@ -47,17 +47,16 @@ Classifier: Development Status :: 4 - Beta
47
47
  Requires-Python: >=3.9
48
48
  Description-Content-Type: text/markdown
49
49
  License-File: LICENSE
50
- Requires-Dist: cycler>=0.10
51
- Requires-Dist: h5netcdf>=1.0
52
50
  Requires-Dist: matplotlib>=3.8
53
51
  Requires-Dist: numpy>=1.26
54
52
  Requires-Dist: pandas>=2.0
55
- Requires-Dist: pyyaml>=4.0
56
53
  Requires-Dist: scipy>=1.12
57
- Requires-Dist: tqdm>=2.0
58
54
  Requires-Dist: xarray>=2023
55
+ Requires-Dist: h5netcdf>=1.0
56
+ Requires-Dist: pyyaml>=4.0
57
+ Requires-Dist: tqdm>=2.0
59
58
  Provides-Extra: opt
60
- Requires-Dist: foxes-opt>=0.5; extra == "opt"
59
+ Requires-Dist: foxes-opt>=0.6; extra == "opt"
61
60
  Provides-Extra: dask
62
61
  Requires-Dist: dask>=2022.0; extra == "dask"
63
62
  Requires-Dist: distributed>=2022.0; extra == "dask"
@@ -23,7 +23,7 @@ examples/wind_rose/run.py,sha256=CU7pzOSlWHifFvdtKl_t9gufaAKw2TkjwvklDZRaawM,438
23
23
  examples/yawed_wake/run.py,sha256=X-cxmhC0rCRHyB00kXrsnqQ9fufSY7L35lozls7Nxuw,6882
24
24
  foxes/__init__.py,sha256=1RPU7w3WJ3jFb2aJdZKRy-6Fje9qAaH85WYcOCiDvwk,1375
25
25
  foxes/constants.py,sha256=jQcyeU53V629Tf8qQWc2F4Xw1rV3ywtJiT-g1JWenNg,3842
26
- foxes/variables.py,sha256=sK58u3HJRsWo-fbE7GPlQUhF0YUhY-qSg6Vl8Pv1Eo8,6144
26
+ foxes/variables.py,sha256=7B5wFWUunrFZLjU2l2cgYZ5nyZmakOLR1gqh-G3BTuI,6225
27
27
  foxes/algorithms/__init__.py,sha256=L0VyK-B81zXr-IFI6NMhJ7Px9XVhlkmX0M0IrKhcf0o,327
28
28
  foxes/algorithms/downwind/__init__.py,sha256=tdTnMEVLi58EMkqStollQb4gPug4x8Te2K8ddAxkas8,75
29
29
  foxes/algorithms/downwind/downwind.py,sha256=Rqpe-BYWVt8eS1HxsRYFO93SgblVxmuTKFI1sSFzhXQ,26215
@@ -135,19 +135,20 @@ foxes/input/yaml/__init__.py,sha256=XGCJlbE1SwavEWEKDRAuJwqAi9Afm_qwPnQLxPKjzP4,
135
135
  foxes/input/yaml/dict.py,sha256=0hRqpF0Gn3FknS-xldyzzhKTTpYlv8GTqUBsLIFjolU,14921
136
136
  foxes/input/yaml/yaml.py,sha256=0hikKGhkjipYANJNeXgNZ8xzVj-mMscmsA26w_6HdgE,2741
137
137
  foxes/input/yaml/windio/__init__.py,sha256=HXJEBEDd1FbATkDfjXzA3sgo9detUxgvekmC32QOc5E,446
138
- foxes/input/yaml/windio/read_attributes.py,sha256=wibXcIGSY6iTMIyMQ9w6_RNaj3dDH7khd3LI-jsGW40,13017
139
- foxes/input/yaml/windio/read_farm.py,sha256=F2s27Z4yCvPaQsTlNbOtZiDzSwRmOBnMnhfNj4tW4nk,7428
140
- foxes/input/yaml/windio/read_fields.py,sha256=lWMm5GQ6AcnQ_bQPHpIDpClya6yQd9GdpR0qrImGyIs,4738
141
- foxes/input/yaml/windio/read_outputs.py,sha256=iwxZ-9VKlo2r_aH8D-bkJNGV9p1hRAjP5G6BFCA4MVw,7963
142
- foxes/input/yaml/windio/read_site.py,sha256=KVRTr92mNqmsxgHx2XkPIqHVV91WiEzaqvub5uils0A,13124
143
- foxes/input/yaml/windio/windio.py,sha256=IjspxFNBHwbCJ8vSOU3OcOpjVm9vwKTExVA0dQj9Rqo,5617
138
+ foxes/input/yaml/windio/read_attributes.py,sha256=T4FLMGVgWWK4cyNnq0H8x7TX_pbeUv-X4NEmCC-0IBs,13252
139
+ foxes/input/yaml/windio/read_farm.py,sha256=TraS7AYIbu8BVj9ml8dj9yWN9dCZ_VoHrrFkmWWaU28,8247
140
+ foxes/input/yaml/windio/read_fields.py,sha256=pgMMkXaZ8BXzTIyNqgFxJCR_QvO7rn5xEEP77NjD17k,4818
141
+ foxes/input/yaml/windio/read_outputs.py,sha256=IbD5_0PLMy6Dl1-oyCDoY4S6fetD7j03iUZG2cIJ7yY,8678
142
+ foxes/input/yaml/windio/read_site.py,sha256=csmhoe46b3nUecMR9Nuc5vifAvOHB2g3SyR8MvFHzp8,13840
143
+ foxes/input/yaml/windio/windio.py,sha256=mcznjZCwUpX06ADMvhp-kQMeBZG4pP7ooILb6-UH5_A,5659
144
144
  foxes/models/__init__.py,sha256=-wXjx9PkxWT2Koq4tmtCuasnlb8RIeLVWP7iSYPQ1Fc,727
145
145
  foxes/models/model_book.py,sha256=A9lCg-cQtGVQ2dpIGzcEJGHdyktUdpzqyqHrybUMRvo,30065
146
146
  foxes/models/axial_induction/__init__.py,sha256=QJN1RLxIt1EhQXIDVLjPSZ1zIoaS6YjK9VDdgw7NwsA,124
147
147
  foxes/models/axial_induction/betz.py,sha256=rAaq7pnu-FmfYh61BU5Af1h4_bG2AAimP4Qah1aZo88,894
148
148
  foxes/models/axial_induction/madsen.py,sha256=_-ycfh8SnIpCKWm1uwx5VH7f49FQvR0y1Nbb_U8xWyU,1403
149
- foxes/models/farm_controllers/__init__.py,sha256=0HEGiWEsrKjm8Opf890DzVHwTptSlTSgeo5QJMx9gnw,95
149
+ foxes/models/farm_controllers/__init__.py,sha256=9c9vvgfUAvOG1I_5Bp58zJPC1mvUFuSQCDbRAUFTj4c,153
150
150
  foxes/models/farm_controllers/basic.py,sha256=6pinNXiyfoG3apXhqQWcnxx4vN_7Q63YryXX5Z7kTE0,249
151
+ foxes/models/farm_controllers/op_flag.py,sha256=EEtPejZ5majpDiPO_8Y5Z6b2deNpj1PyOX7wsl56Shk,6021
151
152
  foxes/models/farm_models/__init__.py,sha256=8qV0dQ2N3HJpbOolhLtBs0LMdC8hctxy8J-OiqcSvKo,87
152
153
  foxes/models/farm_models/turbine2farm.py,sha256=LuK-qAy4aVXBqFCM4-1qogwAA4qfHsjYRFsrZy_OUro,2276
153
154
  foxes/models/ground_models/__init__.py,sha256=r_MuSulOGQ1SnrojfpwU9kky7tZhPtCwXbXIVvGdrkg,148
@@ -220,17 +221,17 @@ foxes/models/wake_models/top_hat.py,sha256=APZf6r26jfi2rP_rrmk4qXs1NGlzc1hsFxrj6
220
221
  foxes/models/wake_models/induction/__init__.py,sha256=xw5LiTh2YWRRNF8cyYACQc-735C23NarCMHWiHQ_5-8,311
221
222
  foxes/models/wake_models/induction/rankine_half_body.py,sha256=1NaV5eqcsxjlQwCSUDC-w3wO_SGgh4ykEYvNJX8ToBk,6427
222
223
  foxes/models/wake_models/induction/rathmann.py,sha256=ZPF16PESm1wWA7SL7sNbj_XPb4rAGGcmfBrby8VoU3o,7964
223
- foxes/models/wake_models/induction/self_similar.py,sha256=v3bQjX-RAYwJ-WbZfOpAVb-7Wbq8HXgsgqenP6Jel40,8505
224
- foxes/models/wake_models/induction/self_similar2020.py,sha256=kteNnjRG4SptcYiW3avBgiDjmX3NMefTB2G2rpb-Hjs,1527
224
+ foxes/models/wake_models/induction/self_similar.py,sha256=WO5NStvfAy2xT-i4zW63ncyn-NH4_fNPAzv--nQjJvA,8869
225
+ foxes/models/wake_models/induction/self_similar2020.py,sha256=FETB7iiS1av0ymb8w3sgYLRCK3jz0lVXAMgGZ6MXljg,1516
225
226
  foxes/models/wake_models/induction/vortex_sheet.py,sha256=Iy3QX_2lUA7MiEEYKFeRh7jz9s7KpyyS7qBLhfCEsAo,7197
226
227
  foxes/models/wake_models/ti/__init__.py,sha256=0ZKF4sHbtU3QCRzC1Ff0vnVymbdby7s12Qcy3PnBgKU,163
227
- foxes/models/wake_models/ti/crespo_hernandez.py,sha256=UVxeDbxPu7beKNm5b8jwvgOdqJFGUQxG9Wybwfwy5p8,8990
228
+ foxes/models/wake_models/ti/crespo_hernandez.py,sha256=Or1uf0t8GjtQePyiczSIf4Dmb8tl4AMqdV9TqWFQNbE,9005
228
229
  foxes/models/wake_models/ti/iec_ti.py,sha256=P9SUNaxf9UdA_rufuJcmpgLXM1Fj1FRhFHU4DNxYFa4,7064
229
230
  foxes/models/wake_models/wind/__init__.py,sha256=QOCeb8E7TYuwkX5jhgPUST1kGmv9c59qvclLhtUeLnQ,379
230
- foxes/models/wake_models/wind/bastankhah14.py,sha256=zMhlZq9YMTMje-zbbx4BKHl9DwZK_e0SoODlNuVumvo,5567
231
- foxes/models/wake_models/wind/bastankhah16.py,sha256=6yBMInYi0Zg3Ly6x2gU9GbjTho6lmfQ1CkGd4uXIFgg,18742
231
+ foxes/models/wake_models/wind/bastankhah14.py,sha256=0NxM4QO1CtaY7dim2Xc7CXK3uMMWCMkhuOd3Wnb75RU,5582
232
+ foxes/models/wake_models/wind/bastankhah16.py,sha256=FuBQi9Q01f6gIK0_S5aO6QpCPJvyFzqH8wV44q1eTIw,18743
232
233
  foxes/models/wake_models/wind/jensen.py,sha256=rGEtBgTtT1z3_sGVHkAlqSP4sSErD2yQu-lDwqfykIw,4493
233
- foxes/models/wake_models/wind/turbopark.py,sha256=Vu69CeFnWfGfSlLRp0p4M7GCYezEUFt12ia_elCm7fg,15222
234
+ foxes/models/wake_models/wind/turbopark.py,sha256=w0ZXloSgjsbk32JuivrmVF4WbVv0TNE3KiU9UC07Y4c,15237
234
235
  foxes/models/wake_superpositions/__init__.py,sha256=fMrBOoBGSa45xbc2E1Jdgjfkm2ahKBKgPeBVq8s9uH0,690
235
236
  foxes/models/wake_superpositions/ti_linear.py,sha256=Cqteww87M94ZDIrSoAoXSouCcH-Auj0u8NSD6JCv9Lw,3895
236
237
  foxes/models/wake_superpositions/ti_max.py,sha256=s2NYnqMIk-Bj6-SAUyhBX0WWEye5yu4hRoe5dGcLEAw,3920
@@ -255,7 +256,7 @@ foxes/output/rose_plot.py,sha256=bHd9LrGCsE39zUxmiaRk8Z3r2m3csWqSUTS-3H-vdnk,188
255
256
  foxes/output/rotor_point_plots.py,sha256=J2gfwDZVBrQIgCk6RJuj0Jq1UA1cmyh7k8VC2TfV6xw,3324
256
257
  foxes/output/slice_data.py,sha256=bnY8D0ITFFN-i1mSkm0e2ukIMqxjev2KaUINbxo62pA,33788
257
258
  foxes/output/slices_data.py,sha256=FqulnXnZ5aI2fOns4ffBYSw5j42IEGq-lLCNJxWG5HA,8671
258
- foxes/output/state_turbine_map.py,sha256=LY-fhGws2LC0b-Tgkh76g9gKzEtNBOUWYkVEZ_lV9dI,2772
259
+ foxes/output/state_turbine_map.py,sha256=tBHDbAqxEqanVYRJhPlpvIV6FslZGG0VYEfRzEfh5AI,2944
259
260
  foxes/output/state_turbine_table.py,sha256=ldG0LcC126YsXQZtJY9OSWxW3ETbHdBehjtU21caTDw,2228
260
261
  foxes/output/turbine_type_curves.py,sha256=fvODho-HmH8hIrv0iQ6BXqtQ4Hz0jomnOTK-kV7vfBQ,5724
261
262
  foxes/output/flow_plots_2d/__init__.py,sha256=5pFJqvSuZKkm1TkN3d7won0mPLvr9MUg9m0TMlOqbec,91
@@ -295,7 +296,7 @@ foxes/utils/geom2d/example_intersection.py,sha256=4e6sjpZEk_bNc462YvwKPzwxdV1B90
295
296
  foxes/utils/geom2d/example_union.py,sha256=BKfLt1mtQcSto-qExeMQkq8tQ6kfFXVJ93Cc7DhOal8,1750
296
297
  foxes/utils/geom2d/half_plane.py,sha256=kzZD6pkZxZ03MK9WAboWzXb5Ws5dWLQY9GIahD4D9mA,6167
297
298
  foxes/utils/geom2d/polygon.py,sha256=Xj7triA5Pe4-48sNSAvGxEXlQGptV161LUpKKCf3YOY,5535
298
- foxes-1.5.1.dist-info/licenses/LICENSE,sha256=bBCH6mYTPzSepk2s2UUZ3II_ZYXrn1bnSqB85-aZHxU,1071
299
+ foxes-1.5.2.dist-info/licenses/LICENSE,sha256=bBCH6mYTPzSepk2s2UUZ3II_ZYXrn1bnSqB85-aZHxU,1071
299
300
  tests/0_consistency/iterative/test_iterative.py,sha256=lzagmJ1y241l6Szw4Cu80S8S1ATHIyD7ukr1vVBrusY,2637
300
301
  tests/0_consistency/partial_wakes/test_partial_wakes.py,sha256=7rdg2lcUYUzxwCdGhue7A4QYCJQGrOO4E0bytgatYj4,2584
301
302
  tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py,sha256=8tIqifsykvxMCd9eD-mZWxRwT56sKjIxrGf3hf8aCFA,2530
@@ -320,9 +321,10 @@ tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Cres
320
321
  tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/flappy/run.py,sha256=nwIyn20ZyYHhCKcPCnm02zjwNKMRzr9t0U8TjKK61QU,2213
321
322
  tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py,sha256=QUULW3AJJq8zvUIdIQHkXiNZpaysVYIALh5HOJ6-428,2595
322
323
  tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/flappy/run.py,sha256=1paRjCe71AIM8igK4_f0FFH1xiIETvuBwnZAATiwWUU,2102
324
+ tests/2_models/turbine_models/test_set_farm_vars.py,sha256=_y-lVYsL9jByh_34c08Qu4GgYxW8s-E2HsSyX7ptfnY,1680
323
325
  tests/3_examples/test_examples.py,sha256=9_PtvlYA6ELsQGHt-xAnsjCYdGj1ekX237ymWvEJaCk,732
324
- foxes-1.5.1.dist-info/METADATA,sha256=zRir9XRuTKME-eYx3K7yLKrD9wqWTah5-PTPWIKvEjw,8123
325
- foxes-1.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
326
- foxes-1.5.1.dist-info/entry_points.txt,sha256=KuS44FRH5NnMw201A8Btr76eNRKr2UOoKHjejAsqKwE,123
327
- foxes-1.5.1.dist-info/top_level.txt,sha256=G7oHApEz5nc-iP__XsPcvjYe_NyXGmKMUMPHi3C3x6I,26
328
- foxes-1.5.1.dist-info/RECORD,,
326
+ foxes-1.5.2.dist-info/METADATA,sha256=yJDpMwsBVrfk6vpMRDi85OFO89RTham8VVQk1-Bre7Y,8095
327
+ foxes-1.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
328
+ foxes-1.5.2.dist-info/entry_points.txt,sha256=KuS44FRH5NnMw201A8Btr76eNRKr2UOoKHjejAsqKwE,123
329
+ foxes-1.5.2.dist-info/top_level.txt,sha256=G7oHApEz5nc-iP__XsPcvjYe_NyXGmKMUMPHi3C3x6I,26
330
+ foxes-1.5.2.dist-info/RECORD,,
@@ -0,0 +1,64 @@
1
+ import numpy as np
2
+
3
+ import foxes
4
+ import foxes.variables as FV
5
+ import foxes.constants as FC
6
+
7
+
8
+ def test_set_farm_vars():
9
+ n_s = 360
10
+ n_tr = 3
11
+
12
+ wd = np.arange(0.0, 360.0, 360 / n_s)
13
+ states = foxes.input.states.ScanStates(
14
+ scans={
15
+ FV.WD: wd,
16
+ FV.WS: [9.0],
17
+ FV.TI: [0.04],
18
+ FV.RHO: [1.225],
19
+ },
20
+ )
21
+
22
+ n_t = n_tr**2
23
+ x = np.zeros((n_s, n_t), dtype=foxes.config.dtype_double)
24
+ x[:] = wd[:, None] + np.arange(n_t)[None, :] / 10
25
+
26
+ farm = foxes.WindFarm()
27
+ foxes.input.farm_layout.add_grid(
28
+ farm,
29
+ xy_base=[0.0, 0.0],
30
+ step_vectors=[[800.0, 0.0], [0.0, 800.0]],
31
+ steps=[n_tr, n_tr],
32
+ turbine_models=["NREL5MW", "set_x"],
33
+ verbosity=0,
34
+ )
35
+
36
+ with foxes.Engine.new("default", verbosity=0):
37
+ for pr in [False, True]:
38
+ print(f"\npre_rotor = {pr}\n")
39
+
40
+ mbook = foxes.ModelBook()
41
+ mbook.turbine_models["set_x"] = foxes.models.turbine_models.SetFarmVars(
42
+ pre_rotor=pr
43
+ )
44
+ mbook.turbine_models["set_x"].add_var("x", x)
45
+
46
+ algo = foxes.algorithms.Downwind(
47
+ farm=farm,
48
+ states=states,
49
+ wake_models=["Bastankhah2014_linear_lim_k004"],
50
+ mbook=mbook,
51
+ verbosity=0,
52
+ )
53
+
54
+ farm_results = algo.calc_farm()
55
+
56
+ fr = farm_results.to_dataframe()
57
+ print(fr[[FV.WD, "x"]])
58
+
59
+ for i, g in fr.reset_index().groupby(FC.TURBINE):
60
+ assert np.allclose(g["x"].values, g[FV.WD].values + i / 10)
61
+
62
+
63
+ if __name__ == "__main__":
64
+ test_set_farm_vars()
File without changes