foxes 0.6.1__py3-none-any.whl → 0.7__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 (129) hide show
  1. foxes/VERSION +1 -1
  2. foxes/algorithms/downwind/downwind.py +131 -65
  3. foxes/algorithms/downwind/models/__init__.py +2 -1
  4. foxes/algorithms/downwind/models/farm_wakes_calc.py +87 -55
  5. foxes/algorithms/downwind/models/init_farm_data.py +134 -0
  6. foxes/algorithms/downwind/models/point_wakes_calc.py +54 -65
  7. foxes/algorithms/downwind/models/{calc_order.py → reorder_farm_output.py} +28 -26
  8. foxes/algorithms/iterative/iterative.py +100 -51
  9. foxes/algorithms/iterative/models/convergence.py +3 -3
  10. foxes/algorithms/iterative/models/farm_wakes_calc.py +55 -48
  11. foxes/algorithms/sequential/models/seq_state.py +7 -6
  12. foxes/algorithms/sequential/sequential.py +81 -44
  13. foxes/constants.py +33 -18
  14. foxes/core/__init__.py +2 -2
  15. foxes/core/algorithm.py +31 -12
  16. foxes/core/data.py +335 -41
  17. foxes/core/data_calc_model.py +27 -23
  18. foxes/core/farm_controller.py +27 -28
  19. foxes/core/farm_data_model.py +26 -4
  20. foxes/core/model.py +186 -129
  21. foxes/core/partial_wakes_model.py +84 -81
  22. foxes/core/point_data_model.py +51 -18
  23. foxes/core/rotor_model.py +59 -77
  24. foxes/core/states.py +6 -6
  25. foxes/core/turbine_model.py +4 -4
  26. foxes/core/turbine_type.py +24 -0
  27. foxes/core/vertical_profile.py +3 -3
  28. foxes/core/wake_frame.py +91 -50
  29. foxes/core/wake_model.py +74 -43
  30. foxes/core/wake_superposition.py +29 -26
  31. foxes/input/farm_layout/__init__.py +1 -0
  32. foxes/input/farm_layout/from_random.py +49 -0
  33. foxes/input/states/__init__.py +1 -1
  34. foxes/input/states/create/__init__.py +1 -0
  35. foxes/input/states/create/random_abl_states.py +6 -2
  36. foxes/input/states/create/random_timeseries.py +56 -0
  37. foxes/input/states/field_data_nc.py +12 -8
  38. foxes/input/states/multi_height.py +24 -14
  39. foxes/input/states/scan_ws.py +13 -17
  40. foxes/input/states/single.py +28 -20
  41. foxes/input/states/states_table.py +22 -18
  42. foxes/models/axial_induction_models/betz.py +1 -1
  43. foxes/models/farm_models/turbine2farm.py +2 -2
  44. foxes/models/model_book.py +40 -14
  45. foxes/models/partial_wakes/__init__.py +2 -2
  46. foxes/models/partial_wakes/axiwake.py +73 -200
  47. foxes/models/partial_wakes/centre.py +40 -0
  48. foxes/models/partial_wakes/grid.py +7 -63
  49. foxes/models/partial_wakes/rotor_points.py +53 -147
  50. foxes/models/partial_wakes/segregated.py +158 -0
  51. foxes/models/partial_wakes/top_hat.py +88 -196
  52. foxes/models/point_models/set_uniform_data.py +4 -4
  53. foxes/models/point_models/tke2ti.py +4 -4
  54. foxes/models/point_models/wake_deltas.py +4 -4
  55. foxes/models/rotor_models/centre.py +15 -19
  56. foxes/models/rotor_models/grid.py +2 -1
  57. foxes/models/rotor_models/levels.py +2 -1
  58. foxes/models/turbine_models/__init__.py +0 -1
  59. foxes/models/turbine_models/calculator.py +11 -7
  60. foxes/models/turbine_models/kTI_model.py +13 -11
  61. foxes/models/turbine_models/lookup_table.py +22 -9
  62. foxes/models/turbine_models/power_mask.py +81 -51
  63. foxes/models/turbine_models/rotor_centre_calc.py +17 -20
  64. foxes/models/turbine_models/sector_management.py +5 -6
  65. foxes/models/turbine_models/set_farm_vars.py +49 -20
  66. foxes/models/turbine_models/table_factors.py +5 -5
  67. foxes/models/turbine_models/thrust2ct.py +9 -5
  68. foxes/models/turbine_models/yaw2yawm.py +7 -13
  69. foxes/models/turbine_models/yawm2yaw.py +7 -11
  70. foxes/models/turbine_types/PCt_file.py +84 -3
  71. foxes/models/turbine_types/PCt_from_two.py +7 -3
  72. foxes/models/turbine_types/null_type.py +2 -2
  73. foxes/models/turbine_types/wsrho2PCt_from_two.py +2 -2
  74. foxes/models/turbine_types/wsti2PCt_from_two.py +6 -2
  75. foxes/models/wake_frames/farm_order.py +26 -22
  76. foxes/models/wake_frames/rotor_wd.py +32 -31
  77. foxes/models/wake_frames/seq_dynamic_wakes.py +112 -64
  78. foxes/models/wake_frames/streamlines.py +51 -47
  79. foxes/models/wake_frames/timelines.py +59 -47
  80. foxes/models/wake_frames/yawed_wakes.py +63 -40
  81. foxes/models/wake_models/axisymmetric.py +31 -35
  82. foxes/models/wake_models/dist_sliced.py +50 -56
  83. foxes/models/wake_models/gaussian.py +33 -35
  84. foxes/models/wake_models/induction/rankine_half_body.py +79 -87
  85. foxes/models/wake_models/induction/rathmann.py +56 -63
  86. foxes/models/wake_models/induction/self_similar.py +59 -62
  87. foxes/models/wake_models/ti/crespo_hernandez.py +83 -74
  88. foxes/models/wake_models/ti/iec_ti.py +65 -75
  89. foxes/models/wake_models/top_hat.py +60 -69
  90. foxes/models/wake_models/wake_mirror.py +49 -54
  91. foxes/models/wake_models/wind/bastankhah14.py +44 -66
  92. foxes/models/wake_models/wind/bastankhah16.py +84 -111
  93. foxes/models/wake_models/wind/jensen.py +67 -89
  94. foxes/models/wake_models/wind/turbopark.py +93 -133
  95. foxes/models/wake_superpositions/ti_linear.py +33 -27
  96. foxes/models/wake_superpositions/ti_max.py +33 -27
  97. foxes/models/wake_superpositions/ti_pow.py +35 -27
  98. foxes/models/wake_superpositions/ti_quadratic.py +33 -27
  99. foxes/models/wake_superpositions/ws_linear.py +39 -32
  100. foxes/models/wake_superpositions/ws_max.py +40 -33
  101. foxes/models/wake_superpositions/ws_pow.py +39 -32
  102. foxes/models/wake_superpositions/ws_product.py +35 -28
  103. foxes/models/wake_superpositions/ws_quadratic.py +39 -32
  104. foxes/opt/constraints/min_dist.py +1 -1
  105. foxes/opt/objectives/farm_vars.py +1 -1
  106. foxes/opt/problems/layout/farm_layout.py +38 -97
  107. foxes/output/__init__.py +1 -0
  108. foxes/output/farm_results_eval.py +1 -1
  109. foxes/output/flow_plots_2d/flow_plots.py +2 -0
  110. foxes/output/flow_plots_2d/get_fig.py +2 -0
  111. foxes/output/grids.py +1 -1
  112. foxes/output/rose_plot.py +3 -3
  113. foxes/output/rotor_point_plots.py +117 -0
  114. foxes/output/turbine_type_curves.py +2 -2
  115. foxes/utils/__init__.py +2 -1
  116. foxes/utils/load.py +29 -0
  117. foxes/utils/random_xy.py +56 -0
  118. foxes/utils/runners/runners.py +13 -1
  119. foxes/utils/windrose_plot.py +1 -1
  120. foxes/variables.py +10 -0
  121. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/METADATA +13 -7
  122. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/RECORD +126 -122
  123. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/WHEEL +1 -1
  124. foxes/models/partial_wakes/distsliced.py +0 -322
  125. foxes/models/partial_wakes/mapped.py +0 -252
  126. foxes/models/turbine_models/set_XYHD.py +0 -130
  127. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/LICENSE +0 -0
  128. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/top_level.txt +0 -0
  129. {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/zip-safe +0 -0
@@ -0,0 +1,134 @@
1
+ import numpy as np
2
+
3
+ from foxes.core import FarmDataModel, TData
4
+ import foxes.variables as FV
5
+ import foxes.constants as FC
6
+
7
+
8
+ class InitFarmData(FarmDataModel):
9
+ """
10
+ Sets basic turbine data and applies downwind order
11
+
12
+ :group: algorithms.downwind.models
13
+
14
+ """
15
+
16
+ def __init__(self):
17
+ """
18
+ Constructor.
19
+ """
20
+ super().__init__(pre_rotor=True)
21
+
22
+ def output_farm_vars(self, algo):
23
+ """
24
+ The variables which are being modified by the model.
25
+
26
+ Parameters
27
+ ----------
28
+ algo: foxes.core.Algorithm
29
+ The calculation algorithm
30
+
31
+ Returns
32
+ -------
33
+ output_vars: list of str
34
+ The output variable names
35
+
36
+ """
37
+ return [
38
+ FV.X,
39
+ FV.Y,
40
+ FV.H,
41
+ FV.D,
42
+ FV.WD,
43
+ FV.YAW,
44
+ FV.ORDER,
45
+ FV.WEIGHT,
46
+ FV.ORDER_SSEL,
47
+ FV.ORDER_INV,
48
+ ]
49
+
50
+ def calculate(self, algo, mdata, fdata):
51
+ """ "
52
+ The main model calculation.
53
+
54
+ This function is executed on a single chunk of data,
55
+ all computations should be based on numpy arrays.
56
+
57
+ Parameters
58
+ ----------
59
+ algo: foxes.core.Algorithm
60
+ The calculation algorithm
61
+ mdata: foxes.core.Data
62
+ The model data
63
+ fdata: foxes.core.Data
64
+ The farm data
65
+
66
+ Returns
67
+ -------
68
+ results: dict
69
+ The resulting data, keys: output variable str.
70
+ Values: numpy.ndarray with shape (n_states, n_turbines)
71
+
72
+ """
73
+ # prepare:
74
+ n_states = fdata.n_states
75
+ n_turbines = algo.n_turbines
76
+
77
+ # define FV.TXYH as vector [X, Y, H]:
78
+ fdata[FV.TXYH] = np.full((n_states, n_turbines, 3), np.nan, dtype=FC.DTYPE)
79
+ fdata.dims[FV.TXYH] = (FC.STATE, FC.TURBINE, FC.XYH)
80
+ for i, v in enumerate([FV.X, FV.Y, FV.H]):
81
+ fdata[v] = fdata[FV.TXYH][..., i]
82
+ fdata.dims[v] = (FC.STATE, FC.TURBINE)
83
+
84
+ # set X, Y, H, D:
85
+ fdata[FV.D] = np.zeros((n_states, n_turbines), dtype=FC.DTYPE)
86
+ for ti, t in enumerate(algo.farm.turbines):
87
+
88
+ if len(t.xy.shape) == 1:
89
+ fdata[FV.TXYH][:, ti, :2] = t.xy[None, :]
90
+ else:
91
+ i0 = fdata.states_i0()
92
+ s = np.s_[i0 : i0 + fdata.n_states]
93
+ fdata[FV.TXYH][:, ti, :2] = t.xy[s]
94
+
95
+ H = t.H
96
+ if H is None:
97
+ H = algo.farm_controller.turbine_types[ti].H
98
+ fdata[FV.TXYH][:, ti, 2] = H
99
+
100
+ D = t.D
101
+ if D is None:
102
+ D = algo.farm_controller.turbine_types[ti].D
103
+ fdata[FV.D][:, ti] = D
104
+
105
+ # calc WD and YAW at rotor centres:
106
+ tdata = TData.from_points(points=fdata[FV.TXYH])
107
+ sres = algo.states.calculate(algo, mdata, fdata, tdata)
108
+ fdata[FV.WD] = sres[FV.WD][:, :, 0]
109
+ del tdata, sres
110
+
111
+ # calculate and inverse:
112
+ order = algo.wake_frame.calc_order(algo, mdata, fdata)
113
+ ssel = np.zeros_like(order)
114
+ ssel[:] = np.arange(n_states)[:, None]
115
+ fdata[FV.ORDER] = order
116
+ fdata[FV.ORDER_SSEL] = ssel
117
+ fdata[FV.ORDER_INV] = np.zeros_like(order)
118
+ fdata[FV.ORDER_INV][ssel, order] = np.arange(n_turbines)[None, :]
119
+
120
+ # apply downwind order to all data:
121
+ fdata[FV.TXYH] = fdata[FV.TXYH][ssel, order]
122
+ for i, v in enumerate([FV.X, FV.Y, FV.H]):
123
+ fdata[v] = fdata[FV.TXYH][..., i]
124
+ for v in [FV.D, FV.WD, FV.WEIGHT]:
125
+ if np.any(fdata[v] != fdata[v][0, 0, None, None]):
126
+ fdata[v] = fdata[v][ssel, order]
127
+ fdata[FV.YAW] = fdata[FV.WD].copy()
128
+ for k in mdata.keys():
129
+ if tuple(mdata.dims[k][:2]) == (FC.STATE, FC.TURBINE) and np.any(
130
+ mdata[k] != mdata[k][0, 0, None, None]
131
+ ):
132
+ mdata[k] = mdata[k][ssel, order]
133
+
134
+ return {v: fdata[v] for v in self.output_farm_vars(algo)}
@@ -16,7 +16,7 @@ class PointWakesCalculation(PointDataModel):
16
16
  emodels_cpars: list of dict
17
17
  The calculation parameters for extra models
18
18
  wake_models: list of foxes.core.WakeModel
19
- The wake models, default: from algo
19
+ The wake models to be used
20
20
 
21
21
  :group: algorithms.downwind.models
22
22
 
@@ -33,7 +33,7 @@ class PointWakesCalculation(PointDataModel):
33
33
  emodels_cpars: list of dict, optional
34
34
  The calculation parameters for extra models
35
35
  wake_models: list of foxes.core.WakeModel, optional
36
- The wake models, default: from algo
36
+ Specific wake models to be used
37
37
 
38
38
  """
39
39
  super().__init__()
@@ -86,14 +86,14 @@ class PointWakesCalculation(PointDataModel):
86
86
  """
87
87
  return self.pvars
88
88
 
89
- def contribute_to_wake_deltas(
89
+ def contribute(
90
90
  self,
91
91
  algo,
92
92
  mdata,
93
93
  fdata,
94
- pdata,
95
- states_source_turbine,
96
- wmodels,
94
+ tdata,
95
+ downwind_index,
96
+ wmodel,
97
97
  wdeltas,
98
98
  ):
99
99
  """
@@ -107,30 +107,23 @@ class PointWakesCalculation(PointDataModel):
107
107
  The model data
108
108
  fdata: foxes.core.Data
109
109
  The farm data
110
- pdata: foxes.core.Data
111
- The point data
112
- states_source_turbine: numpy.ndarray
113
- For each state, one turbine index for the
114
- wake causing turbine. Shape: (n_states,)
115
- wmodels: list of foxes.core.WakeModel
116
- The wake models
110
+ tdata: foxes.core.Data
111
+ The target point data
112
+ downwind_index: int
113
+ The index in the downwind order
114
+ wmodel: foxes.core.WakeModel
115
+ The wake model
117
116
  wdeltas: dict
118
117
  The wake deltas, are being modified ob the fly.
119
118
  Key: Variable name str, for which the
120
119
  wake delta applies, values: numpy.ndarray with
121
- shape (n_states, n_points, ...)
120
+ shape (n_states, n_targets, n_tpoints, ...)
122
121
 
123
122
  """
124
- wcoos = algo.wake_frame.get_wake_coos(
125
- algo, mdata, fdata, pdata, states_source_turbine
126
- )
127
-
128
- for w in wmodels:
129
- w.contribute_to_wake_deltas(
130
- algo, mdata, fdata, pdata, states_source_turbine, wcoos, wdeltas
131
- )
123
+ wcoos = algo.wake_frame.get_wake_coos(algo, mdata, fdata, tdata, downwind_index)
124
+ wmodel.contribute(algo, mdata, fdata, tdata, downwind_index, wcoos, wdeltas)
132
125
 
133
- def calculate(self, algo, mdata, fdata, pdata, states_source_turbine=None):
126
+ def calculate(self, algo, mdata, fdata, tdata, downwind_index=None):
134
127
  """ "
135
128
  The main model calculation.
136
129
 
@@ -145,55 +138,51 @@ class PointWakesCalculation(PointDataModel):
145
138
  The model data
146
139
  fdata: foxes.core.Data
147
140
  The farm data
148
- pdata: foxes.core.Data
149
- The point data
150
- states_source_turbine: numpy.ndarray, optional
151
- For each state, one turbine index for the
152
- wake causing turbine. Shape: (n_states,).
153
- Default: include all turbines
141
+ tdata: foxes.core.Data
142
+ The target point data
143
+ downwind_index: int
144
+ The index in the downwind order of the wake
145
+ causing turbine
154
146
 
155
147
  Returns
156
- -------
157
148
  results: dict
158
149
  The resulting data, keys: output variable str.
159
- Values: numpy.ndarray with shape (n_states, n_points)
150
+ Values: numpy.ndarray with shape
151
+ (n_states, n_targets, n_tpoints)
160
152
 
161
153
  """
162
154
 
163
- torder = fdata[FV.ORDER].astype(FC.ITYPE)
164
- n_order = torder.shape[1]
165
- wake_models = algo.wake_models if self.wake_models is None else self.wake_models
166
-
167
- wdeltas = {}
168
- wmodels = []
169
- for w in wake_models:
170
- hdeltas = {}
171
- w.init_wake_deltas(algo, mdata, fdata, pdata, hdeltas)
172
- if len(set(self.pvars).intersection(hdeltas.keys())):
173
- wdeltas.update(hdeltas)
174
- wmodels.append(w)
175
- del hdeltas
176
-
177
- if states_source_turbine is None:
178
- for oi in range(n_order):
179
- o = torder[:, oi]
180
- self.contribute_to_wake_deltas(
181
- algo, mdata, fdata, pdata, o, wmodels, wdeltas
182
- )
183
- else:
184
- self.contribute_to_wake_deltas(
185
- algo, mdata, fdata, pdata, states_source_turbine, wmodels, wdeltas
186
- )
187
-
188
- amb_res = {v: pdata[FV.var2amb[v]] for v in wdeltas if v in FV.var2amb}
189
- for w in wmodels:
190
- w.finalize_wake_deltas(algo, mdata, fdata, pdata, amb_res, wdeltas)
191
-
192
- for v in self.pvars:
193
- if v in wdeltas:
194
- pdata[v] = amb_res[v] + wdeltas[v]
155
+ res = {}
156
+ wmodels = (
157
+ algo.wake_models.values() if self.wake_models is None else self.wake_models
158
+ )
159
+ for wmodel in wmodels:
160
+ wdeltas = wmodel.new_wake_deltas(algo, mdata, fdata, tdata)
161
+ if len(set(self.pvars).intersection(wdeltas.keys())):
162
+
163
+ if downwind_index is None:
164
+ for oi in range(fdata.n_turbines):
165
+ self.contribute(algo, mdata, fdata, tdata, oi, wmodel, wdeltas)
166
+
167
+ else:
168
+ self.contribute(
169
+ algo, mdata, fdata, tdata, downwind_index, wmodel, wdeltas
170
+ )
171
+
172
+ for v in self.pvars:
173
+ if v not in res and v in tdata:
174
+ res[v] = tdata[v].copy()
175
+
176
+ wmodel.finalize_wake_deltas(algo, mdata, fdata, res, wdeltas)
177
+
178
+ for v in res.keys():
179
+ if v in wdeltas:
180
+ res[v] += wdeltas[v]
181
+
182
+ for v in res.keys():
183
+ tdata[v] = res[v]
195
184
 
196
185
  if self.emodels is not None:
197
- self.emodels.calculate(algo, mdata, fdata, pdata, self.emodels_cpars)
186
+ self.emodels.calculate(algo, mdata, fdata, tdata, self.emodels_cpars)
198
187
 
199
- return {v: pdata[v] for v in self.pvars}
188
+ return {v: tdata[v] for v in self.pvars}
@@ -1,42 +1,34 @@
1
- import foxes.variables as FV
1
+ import numpy as np
2
+
2
3
  from foxes.core import FarmDataModel
4
+ import foxes.variables as FV
3
5
 
4
6
 
5
- class CalcOrder(FarmDataModel):
7
+ class ReorderFarmOutput(FarmDataModel):
6
8
  """
7
- This model calculates the turbine evaluation
8
- order, via wake frames.
9
+ Reorders final results to state-turbine dimensions
10
+
11
+ Attributes
12
+ ----------
13
+ outputs: list of str, optional
14
+ The output variables, or None for defaults
9
15
 
10
16
  :group: algorithms.downwind.models
11
17
 
12
18
  """
13
19
 
14
- def sub_models(self):
20
+ def __init__(self, outputs):
15
21
  """
16
- List of all sub-models
17
-
18
- Returns
19
- -------
20
- smdls: list of foxes.core.Model
21
- Names of all sub models
22
-
23
- """
24
- return [self._wframe]
25
-
26
- def initialize(self, algo, verbosity=0):
27
- """
28
- Initializes the model.
22
+ Constructor
29
23
 
30
24
  Parameters
31
25
  ----------
32
- algo: foxes.core.Algorithm
33
- The calculation algorithm
34
- verbosity: int
35
- The verbosity level, 0 = silent
26
+ outputs: list of str, optional
27
+ The output variables, or None for defaults
36
28
 
37
29
  """
38
- self._wframe = algo.wake_frame
39
- super().initialize(algo, verbosity)
30
+ super().__init__(pre_rotor=False)
31
+ self.outputs = outputs
40
32
 
41
33
  def output_farm_vars(self, algo):
42
34
  """
@@ -53,7 +45,7 @@ class CalcOrder(FarmDataModel):
53
45
  The output variable names
54
46
 
55
47
  """
56
- return [FV.ORDER]
48
+ return self.outputs if self.outputs is not None else algo.farm_vars
57
49
 
58
50
  def calculate(self, algo, mdata, fdata):
59
51
  """ "
@@ -78,4 +70,14 @@ class CalcOrder(FarmDataModel):
78
70
  Values: numpy.ndarray with shape (n_states, n_turbines)
79
71
 
80
72
  """
81
- return {FV.ORDER: self._wframe.calc_order(algo, mdata, fdata)}
73
+ ssel = fdata[FV.ORDER_SSEL]
74
+ order_inv = fdata[FV.ORDER_INV]
75
+
76
+ out = {}
77
+ for v in self.output_farm_vars(algo):
78
+ if v != FV.ORDER and np.any(fdata[v] != fdata[v][0, 0, None, None]):
79
+ out[v] = fdata[v][ssel, order_inv]
80
+ else:
81
+ out[v] = fdata[v]
82
+
83
+ return out
@@ -44,7 +44,7 @@ class Iterative(Downwind):
44
44
  except AttributeError:
45
45
  return super().get_model(name)
46
46
 
47
- def __init__(self, *args, max_it=None, conv_crit=None, **kwargs):
47
+ def __init__(self, *args, max_it=None, conv_crit="default", mod_cutin={}, **kwargs):
48
48
  """
49
49
  Constructor.
50
50
 
@@ -56,6 +56,8 @@ class Iterative(Downwind):
56
56
  The maximal number of iterations
57
57
  conv_crit: foxes.algorithms.iterative.ConvCrit, optional
58
58
  The convergence criteria
59
+ mod_cutin: dict, optional
60
+ Parameters for cutin modification
59
61
  kwargs: dict, optional
60
62
  Keyword arguments for Downwind
61
63
 
@@ -64,15 +66,16 @@ class Iterative(Downwind):
64
66
 
65
67
  self.max_it = 2 * self.farm.n_turbines if max_it is None else max_it
66
68
  self.conv_crit = (
67
- self.get_model("DefaultConv")() if conv_crit is None else conv_crit
69
+ self.get_model("DefaultConv")() if conv_crit == "default" else conv_crit
68
70
  )
69
71
  self.prev_farm_results = None
70
72
  self._it = None
71
73
  self._mlist = None
72
74
  self._reamb = False
73
- self._urelax = Dict(
74
- first={}, pre_rotor={}, post_rotor={}, pre_wake={FV.CT: 0.5}, last={}
75
- )
75
+ self._urelax = None
76
+
77
+ self._mod_cutin = dict(modify_ct=True, modify_P=False)
78
+ self._mod_cutin.update(mod_cutin)
76
79
 
77
80
  def set_urelax(self, entry_point, **urel):
78
81
  """
@@ -89,8 +92,25 @@ class Iterative(Downwind):
89
92
  """
90
93
  if self.initialized:
91
94
  raise ValueError(f"Attempt to set_urelax after initialization")
95
+ if self._urelax is None:
96
+ self._urelax = Dict(
97
+ first={},
98
+ pre_rotor={},
99
+ post_rotor={},
100
+ pre_wake={},
101
+ last={},
102
+ )
92
103
  self._urelax[entry_point].update(urel)
93
104
 
105
+ def initialize(self):
106
+ """
107
+ Initializes the algorithm.
108
+ """
109
+ super().initialize()
110
+ if len(self._mod_cutin):
111
+ for t in self.farm_controller.turbine_types:
112
+ t.modify_cutin(**self._mod_cutin)
113
+
94
114
  @property
95
115
  def urelax(self):
96
116
  """
@@ -119,46 +139,50 @@ class Iterative(Downwind):
119
139
 
120
140
  def _collect_farm_models(
121
141
  self,
142
+ outputs,
122
143
  calc_parameters,
123
144
  ambient,
124
145
  ):
125
146
  """
126
147
  Helper function that creates model list
127
148
  """
128
-
129
149
  if self._it == 0:
130
150
  self._mlist0, self._calc_pars0 = super()._collect_farm_models(
131
- calc_parameters,
151
+ outputs=False,
152
+ calc_parameters=calc_parameters,
132
153
  ambient=ambient,
133
154
  )
134
155
 
135
156
  n = 0
136
- if len(self._urelax["first"]):
137
- self._mlist0.insert(0, mdls.URelax(**self._urelax["first"]))
138
- self._calc_pars0.insert(0, {})
139
- self._reamb = True
140
- n += 1
141
-
142
- if len(self._urelax["pre_rotor"]):
143
- self._mlist0.insert(2 + n, mdls.URelax(**self._urelax["pre_rotor"]))
144
- self._calc_pars0.insert(2 + n, {})
145
- self._reamb = True
146
- n += 1
147
-
148
- if len(self._urelax["post_rotor"]):
149
- self._mlist0.insert(4 + n, mdls.URelax(**self._urelax["post_rotor"]))
150
- self._calc_pars0.insert(4 + n, {})
151
- self._reamb = True
152
- n += 1
153
-
154
- if len(self._urelax["pre_wake"]):
155
- self._mlist0.models[7 + n].urelax = mdls.URelax(
156
- **self._urelax["pre_wake"]
157
- )
158
-
159
- if len(self._urelax["last"]):
160
- self._mlist0.append(mdls.URelax(**self._urelax["last"]))
161
- self._calc_pars0.append({})
157
+ if self._urelax is not None:
158
+ if len(self._urelax["first"]):
159
+ self._mlist0.insert(0, mdls.URelax(**self._urelax["first"]))
160
+ self._calc_pars0.insert(0, {})
161
+ self._reamb = True
162
+ n += 1
163
+
164
+ if len(self._urelax["pre_rotor"]):
165
+ self._mlist0.insert(2 + n, mdls.URelax(**self._urelax["pre_rotor"]))
166
+ self._calc_pars0.insert(2 + n, {})
167
+ self._reamb = True
168
+ n += 1
169
+
170
+ if len(self._urelax["post_rotor"]):
171
+ self._mlist0.insert(
172
+ 4 + n, mdls.URelax(**self._urelax["post_rotor"])
173
+ )
174
+ self._calc_pars0.insert(4 + n, {})
175
+ self._reamb = True
176
+ n += 1
177
+
178
+ if len(self._urelax["pre_wake"]):
179
+ self._mlist0.models[5 + n].urelax = mdls.URelax(
180
+ **self._urelax["pre_wake"]
181
+ )
182
+
183
+ if len(self._urelax["last"]):
184
+ self._mlist0.append(mdls.URelax(**self._urelax["last"]))
185
+ self._calc_pars0.append({})
162
186
 
163
187
  return self._mlist0, self._calc_pars0
164
188
 
@@ -170,19 +194,34 @@ class Iterative(Downwind):
170
194
  calc_pars = []
171
195
  mlist = FarmDataModelList(models=[])
172
196
 
173
- # add under-relaxation during wake calculation:
174
- urelax = None
175
- if len(self._urelax["pre_wake"]):
176
- urelax = mdls.URelax(**self._urelax["pre_wake"])
197
+ # do not rotate back from downwind order:
198
+ if not self._final_run:
177
199
 
178
- # add model that calculates wake effects:
179
- mlist.models.append(self.get_model("FarmWakesCalculation")(urelax=urelax))
180
- calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
200
+ # add under-relaxation during wake calculation:
201
+ urelax = None
202
+ if self._urelax is not None and len(self._urelax["pre_wake"]):
203
+ urelax = mdls.URelax(**self._urelax["pre_wake"])
181
204
 
182
- # add under-relaxation:
183
- if len(self._urelax["last"]):
184
- mlist.append(mdls.URelax(**self._urelax["last"]))
185
- calc_pars.append({})
205
+ # add model that calculates wake effects:
206
+ mlist.models.append(
207
+ self.get_model("FarmWakesCalculation")(urelax=urelax)
208
+ )
209
+ calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
210
+
211
+ # add under-relaxation:
212
+ if self._urelax is not None and len(self._urelax["last"]):
213
+ mlist.append(mdls.URelax(**self._urelax["last"]))
214
+ calc_pars.append({})
215
+
216
+ # rotate back from downwind order:
217
+ else:
218
+
219
+ # add model that calculates wake effects:
220
+ # mlist.models.append(self.get_model("FarmWakesCalculation")(urelax=None))
221
+ # calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
222
+
223
+ mlist.models.append(self.get_model("ReorderFarmOutput")(outputs))
224
+ calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
186
225
 
187
226
  return mlist, calc_pars
188
227
 
@@ -218,23 +257,33 @@ class Iterative(Downwind):
218
257
  dimensions (state, turbine)
219
258
 
220
259
  """
260
+ outputs = kwargs.pop("outputs", self.DEFAULT_FARM_OUTPUTS)
261
+ outputs = list(set(outputs + [FV.ORDER_SSEL, FV.ORDER_INV, FV.WEIGHT]))
262
+
221
263
  fres = None
222
264
  self._it = -1
265
+ self._final_run = False
223
266
  while self._it < self.max_it:
224
267
  self._it += 1
225
268
 
226
269
  self.print(f"\nAlgorithm {self.name}: Iteration {self._it}\n", vlim=0)
227
270
 
228
271
  self.prev_farm_results = fres
229
- fres = super().calc_farm(finalize=False, **kwargs)
272
+ fres = super().calc_farm(outputs=None, finalize=False, **kwargs)
230
273
 
231
- conv = self.conv_crit.check_converged(
232
- self, self.prev_farm_results, fres, verbosity=self.verbosity + 1
233
- )
274
+ if self.conv_crit is not None:
275
+ conv = self.conv_crit.check_converged(
276
+ self, self.prev_farm_results, fres, verbosity=self.verbosity + 1
277
+ )
234
278
 
235
- if conv:
236
- self.print(f"\nAlgorithm {self.name}: Convergence reached.\n", vlim=0)
237
- break
279
+ if conv:
280
+ self.print(
281
+ f"\nAlgorithm {self.name}: Convergence reached.\n", vlim=0
282
+ )
283
+ self.print("Starting final run")
284
+ self._final_run = True
285
+ fres = super().calc_farm(outputs=outputs, finalize=False, **kwargs)
286
+ break
238
287
 
239
288
  if self._it == 0:
240
289
  self.verbosity -= 1
@@ -273,8 +273,8 @@ class DefaultConv(ConvVarDelta):
273
273
  """
274
274
  super().__init__(
275
275
  {
276
- FV.REWS: 1e-5,
277
- FV.TI: 1e-6,
278
- FV.CT: 1e-6,
276
+ FV.REWS: 1e-6,
277
+ FV.TI: 1e-7,
278
+ FV.CT: 1e-7,
279
279
  }
280
280
  )