foxes 1.5__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.

Files changed (38) hide show
  1. examples/abl_states/run.py +58 -56
  2. examples/dyn_wakes/run.py +110 -118
  3. examples/field_data_nc/run.py +22 -20
  4. examples/multi_height/run.py +8 -6
  5. examples/scan_row/run.py +89 -87
  6. examples/sector_management/run.py +40 -38
  7. examples/states_lookup_table/run.py +6 -4
  8. examples/streamline_wakes/run.py +8 -6
  9. examples/timelines/run.py +100 -98
  10. examples/timeseries/run.py +71 -76
  11. examples/wind_rose/run.py +27 -25
  12. examples/yawed_wake/run.py +82 -80
  13. foxes/core/algorithm.py +1 -0
  14. foxes/engines/pool.py +1 -0
  15. foxes/input/yaml/windio/read_attributes.py +8 -2
  16. foxes/input/yaml/windio/read_farm.py +20 -0
  17. foxes/input/yaml/windio/read_fields.py +3 -0
  18. foxes/input/yaml/windio/read_outputs.py +24 -12
  19. foxes/input/yaml/windio/read_site.py +33 -12
  20. foxes/input/yaml/windio/windio.py +1 -0
  21. foxes/models/farm_controllers/__init__.py +1 -0
  22. foxes/models/farm_controllers/op_flag.py +196 -0
  23. foxes/models/wake_models/induction/self_similar.py +22 -4
  24. foxes/models/wake_models/induction/self_similar2020.py +1 -1
  25. foxes/models/wake_models/ti/crespo_hernandez.py +1 -1
  26. foxes/models/wake_models/wind/bastankhah14.py +1 -1
  27. foxes/models/wake_models/wind/bastankhah16.py +1 -1
  28. foxes/models/wake_models/wind/turbopark.py +1 -1
  29. foxes/output/state_turbine_map.py +7 -4
  30. foxes/utils/xarray_utils.py +20 -12
  31. foxes/variables.py +8 -3
  32. {foxes-1.5.dist-info → foxes-1.5.2.dist-info}/METADATA +5 -6
  33. {foxes-1.5.dist-info → foxes-1.5.2.dist-info}/RECORD +38 -36
  34. tests/2_models/turbine_models/test_set_farm_vars.py +64 -0
  35. {foxes-1.5.dist-info → foxes-1.5.2.dist-info}/WHEEL +0 -0
  36. {foxes-1.5.dist-info → foxes-1.5.2.dist-info}/entry_points.txt +0 -0
  37. {foxes-1.5.dist-info → foxes-1.5.2.dist-info}/licenses/LICENSE +0 -0
  38. {foxes-1.5.dist-info → foxes-1.5.2.dist-info}/top_level.txt +0 -0
@@ -114,93 +114,95 @@ if __name__ == "__main__":
114
114
  wake_deflection=args.deflection,
115
115
  partial_wakes=args.pwakes,
116
116
  mbook=mbook,
117
- engine=args.engine,
117
+ )
118
+
119
+ with foxes.Engine.new(
120
+ engine_type=args.engine,
118
121
  n_procs=args.n_cpus,
119
122
  chunk_size_states=args.chunksize_states,
120
123
  chunk_size_points=args.chunksize_points,
121
- )
124
+ ):
125
+ # calculate farm results
126
+ farm_results = algo.calc_farm()
127
+ print("\nResults data:\n", farm_results)
122
128
 
123
- # calculate farm results
124
- farm_results = algo.calc_farm()
125
- print("\nResults data:\n", farm_results)
126
-
127
- if not args.nofig:
128
- # xy horizontal flow plot
129
- print("\nHorizontal flow figure output:")
130
- o = foxes.output.FlowPlots2D(algo, farm_results)
131
- g = o.gen_states_fig_xy(
132
- args.var, resolution=10, xmin=-500, xmax=3000, rotor_color="red"
133
- )
134
- fig = next(g)
135
- plt.show()
136
- plt.close(fig)
129
+ if not args.nofig:
130
+ # xy horizontal flow plot
131
+ print("\nHorizontal flow figure output:")
132
+ o = foxes.output.FlowPlots2D(algo, farm_results)
133
+ g = o.gen_states_fig_xy(
134
+ args.var, resolution=10, xmin=-500, xmax=3000, rotor_color="red"
135
+ )
136
+ fig = next(g)
137
+ plt.show()
138
+ plt.close(fig)
137
139
 
138
- # yz flow plot
139
- print("\nVertical flow figure output:")
140
- o = foxes.output.FlowPlots2D(algo, farm_results)
141
- g = o.gen_states_fig_yz(
142
- args.var,
143
- resolution=5,
144
- x=750,
145
- ymin=-200,
146
- ymax=200,
147
- zmin=0,
148
- zmax=250,
149
- rotor_color="red",
150
- verbosity=0,
151
- )
152
- fig = next(g)
153
- plt.show()
154
- plt.close(fig)
140
+ # yz flow plot
141
+ print("\nVertical flow figure output:")
142
+ o = foxes.output.FlowPlots2D(algo, farm_results)
143
+ g = o.gen_states_fig_yz(
144
+ args.var,
145
+ resolution=5,
146
+ x=750,
147
+ ymin=-200,
148
+ ymax=200,
149
+ zmin=0,
150
+ zmax=250,
151
+ rotor_color="red",
152
+ verbosity=0,
153
+ )
154
+ fig = next(g)
155
+ plt.show()
156
+ plt.close(fig)
155
157
 
156
- # add capacity and efficiency to farm results
157
- o = foxes.output.FarmResultsEval(farm_results)
158
- o.add_capacity(algo)
159
- o.add_capacity(algo, ambient=True)
160
- o.add_efficiency()
158
+ # add capacity and efficiency to farm results
159
+ o = foxes.output.FarmResultsEval(farm_results)
160
+ o.add_capacity(algo)
161
+ o.add_capacity(algo, ambient=True)
162
+ o.add_efficiency()
161
163
 
162
- # state-turbine results
163
- farm_df = farm_results.to_dataframe()
164
- print("\nFarm results data:\n")
165
- print(
166
- farm_df[
167
- [
168
- FV.X,
169
- FV.AMB_REWS,
170
- FV.REWS,
171
- FV.AMB_TI,
172
- FV.TI,
173
- FV.AMB_P,
174
- FV.P,
175
- FV.WD,
176
- FV.YAW,
177
- FV.YAWM,
164
+ # state-turbine results
165
+ farm_df = farm_results.to_dataframe()
166
+ print("\nFarm results data:\n")
167
+ print(
168
+ farm_df[
169
+ [
170
+ FV.X,
171
+ FV.AMB_REWS,
172
+ FV.REWS,
173
+ FV.AMB_TI,
174
+ FV.TI,
175
+ FV.AMB_P,
176
+ FV.P,
177
+ FV.WD,
178
+ FV.YAW,
179
+ FV.YAWM,
180
+ ]
178
181
  ]
179
- ]
180
- )
181
- print()
182
+ )
183
+ print()
182
184
 
183
- # results by turbine
184
- turbine_results = o.reduce_states(
185
- {
186
- FV.AMB_P: "weights",
187
- FV.P: "weights",
188
- FV.AMB_CAP: "weights",
189
- FV.CAP: "weights",
190
- }
191
- )
192
- turbine_results[FV.AMB_YLD] = o.calc_turbine_yield(
193
- algo=algo, annual=True, ambient=True
194
- )
195
- turbine_results[FV.YLD] = o.calc_turbine_yield(algo=algo, annual=True)
196
- turbine_results[FV.EFF] = turbine_results[FV.P] / turbine_results[FV.AMB_P]
197
- print("\nResults by turbine:\n")
198
- print(turbine_results)
185
+ # results by turbine
186
+ turbine_results = o.reduce_states(
187
+ {
188
+ FV.AMB_P: "weights",
189
+ FV.P: "weights",
190
+ FV.AMB_CAP: "weights",
191
+ FV.CAP: "weights",
192
+ }
193
+ )
194
+ turbine_results[FV.AMB_YLD] = o.calc_turbine_yield(
195
+ algo=algo, annual=True, ambient=True
196
+ )
197
+ turbine_results[FV.YLD] = o.calc_turbine_yield(algo=algo, annual=True)
198
+ turbine_results[FV.EFF] = turbine_results[FV.P] / turbine_results[FV.AMB_P]
199
+ print("\nResults by turbine:\n")
200
+ print(turbine_results)
199
201
 
200
- # power results
201
- P0 = o.calc_mean_farm_power(ambient=True)
202
- P = o.calc_mean_farm_power()
203
- print(f"\nFarm power : {P / 1000:.1f} MW")
204
- print(f"Farm ambient power: {P0 / 1000:.1f} MW")
205
- print(f"Farm efficiency : {o.calc_farm_efficiency() * 100:.2f} %")
206
- print(f"Annual farm yield : {turbine_results[FV.YLD].sum():.2f} GWh.")
202
+ # power results
203
+ P0 = o.calc_mean_farm_power(ambient=True)
204
+ P = o.calc_mean_farm_power()
205
+ print(f"\nFarm power : {P / 1000:.1f} MW")
206
+ print(f"Farm ambient power: {P0 / 1000:.1f} MW")
207
+ print(f"Farm efficiency : {o.calc_farm_efficiency() * 100:.2f} %")
208
+ print(f"Annual farm yield : {turbine_results[FV.YLD].sum():.2f} GWh.")
foxes/core/algorithm.py CHANGED
@@ -912,6 +912,7 @@ class Algorithm(Model):
912
912
  isel=isel,
913
913
  **kwargs,
914
914
  )
915
+ self.reset_chunk_store(chunk_store)
915
916
 
916
917
  # reset to not running:
917
918
  self.unset_running(
foxes/engines/pool.py CHANGED
@@ -187,6 +187,7 @@ class PoolEngine(Engine):
187
187
  The model results
188
188
 
189
189
  """
190
+
190
191
  # subset selection:
191
192
  model_data, farm_data, point_data = self.select_subsets(
192
193
  model_data, farm_data, point_data, sel=sel, isel=isel
@@ -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