foxes 0.8.2__py3-none-any.whl → 1.0__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 (174) hide show
  1. docs/source/conf.py +353 -0
  2. examples/abl_states/run.py +160 -0
  3. examples/compare_rotors_pwakes/run.py +217 -0
  4. examples/compare_wakes/run.py +241 -0
  5. examples/dyn_wakes/run.py +311 -0
  6. examples/field_data_nc/run.py +121 -0
  7. examples/induction_RHB/run.py +201 -0
  8. examples/multi_height/run.py +113 -0
  9. examples/power_mask/run.py +249 -0
  10. examples/random_timeseries/run.py +210 -0
  11. examples/scan_row/run.py +193 -0
  12. examples/sector_management/run.py +162 -0
  13. examples/sequential/run.py +209 -0
  14. examples/single_state/run.py +201 -0
  15. examples/states_lookup_table/run.py +137 -0
  16. examples/streamline_wakes/run.py +138 -0
  17. examples/tab_file/run.py +142 -0
  18. examples/timelines/run.py +267 -0
  19. examples/timeseries/run.py +183 -0
  20. examples/timeseries_slurm/run.py +185 -0
  21. examples/wind_rose/run.py +141 -0
  22. examples/windio/run.py +29 -0
  23. examples/yawed_wake/run.py +196 -0
  24. foxes/__init__.py +4 -8
  25. foxes/algorithms/__init__.py +1 -1
  26. foxes/algorithms/downwind/downwind.py +232 -101
  27. foxes/algorithms/downwind/models/farm_wakes_calc.py +11 -6
  28. foxes/algorithms/downwind/models/init_farm_data.py +1 -1
  29. foxes/algorithms/downwind/models/point_wakes_calc.py +5 -6
  30. foxes/algorithms/downwind/models/reorder_farm_output.py +0 -1
  31. foxes/algorithms/downwind/models/set_amb_point_results.py +4 -2
  32. foxes/algorithms/iterative/iterative.py +73 -33
  33. foxes/algorithms/iterative/models/farm_wakes_calc.py +11 -6
  34. foxes/algorithms/sequential/models/plugin.py +1 -1
  35. foxes/algorithms/sequential/sequential.py +126 -255
  36. foxes/constants.py +17 -2
  37. foxes/core/__init__.py +1 -0
  38. foxes/core/algorithm.py +631 -146
  39. foxes/core/data.py +252 -20
  40. foxes/core/data_calc_model.py +13 -289
  41. foxes/core/engine.py +630 -0
  42. foxes/core/farm_controller.py +37 -9
  43. foxes/core/farm_data_model.py +15 -0
  44. foxes/core/model.py +133 -80
  45. foxes/core/point_data_model.py +15 -0
  46. foxes/core/rotor_model.py +27 -21
  47. foxes/core/states.py +16 -0
  48. foxes/core/turbine_type.py +28 -0
  49. foxes/core/wake_frame.py +22 -4
  50. foxes/core/wake_model.py +2 -3
  51. foxes/data/windio/windio_5turbines_timeseries.yaml +23 -1
  52. foxes/engines/__init__.py +16 -0
  53. foxes/engines/dask.py +975 -0
  54. foxes/engines/default.py +75 -0
  55. foxes/engines/futures.py +72 -0
  56. foxes/engines/mpi.py +38 -0
  57. foxes/engines/multiprocess.py +74 -0
  58. foxes/engines/numpy.py +185 -0
  59. foxes/engines/pool.py +263 -0
  60. foxes/engines/single.py +139 -0
  61. foxes/input/farm_layout/__init__.py +1 -0
  62. foxes/input/farm_layout/from_csv.py +4 -0
  63. foxes/input/farm_layout/from_json.py +1 -1
  64. foxes/input/farm_layout/grid.py +2 -2
  65. foxes/input/farm_layout/ring.py +65 -0
  66. foxes/input/farm_layout/row.py +2 -2
  67. foxes/input/states/__init__.py +6 -0
  68. foxes/input/states/create/random_abl_states.py +1 -1
  69. foxes/input/states/field_data_nc.py +157 -32
  70. foxes/input/states/multi_height.py +127 -13
  71. foxes/input/states/one_point_flow.py +577 -0
  72. foxes/input/states/scan_ws.py +73 -2
  73. foxes/input/states/states_table.py +204 -35
  74. foxes/input/windio/__init__.py +1 -1
  75. foxes/input/windio/get_states.py +44 -23
  76. foxes/input/windio/read_attributes.py +41 -16
  77. foxes/input/windio/read_farm.py +116 -102
  78. foxes/input/windio/read_fields.py +13 -6
  79. foxes/input/windio/read_outputs.py +63 -22
  80. foxes/input/windio/runner.py +31 -17
  81. foxes/input/windio/windio.py +36 -22
  82. foxes/models/ground_models/wake_mirror.py +8 -4
  83. foxes/models/model_book.py +29 -18
  84. foxes/models/partial_wakes/rotor_points.py +3 -3
  85. foxes/models/rotor_models/centre.py +4 -0
  86. foxes/models/rotor_models/grid.py +22 -23
  87. foxes/models/rotor_models/levels.py +4 -5
  88. foxes/models/turbine_models/calculator.py +0 -2
  89. foxes/models/turbine_models/lookup_table.py +27 -2
  90. foxes/models/turbine_models/rotor_centre_calc.py +4 -3
  91. foxes/models/turbine_models/set_farm_vars.py +103 -34
  92. foxes/models/turbine_types/PCt_file.py +24 -0
  93. foxes/models/turbine_types/PCt_from_two.py +24 -0
  94. foxes/models/turbine_types/__init__.py +1 -0
  95. foxes/models/turbine_types/lookup.py +316 -0
  96. foxes/models/turbine_types/null_type.py +50 -0
  97. foxes/models/turbine_types/wsrho2PCt_from_two.py +24 -0
  98. foxes/models/turbine_types/wsti2PCt_from_two.py +24 -0
  99. foxes/models/vertical_profiles/data_profile.py +1 -1
  100. foxes/models/wake_frames/__init__.py +1 -0
  101. foxes/models/wake_frames/dynamic_wakes.py +424 -0
  102. foxes/models/wake_frames/farm_order.py +23 -3
  103. foxes/models/wake_frames/rotor_wd.py +4 -2
  104. foxes/models/wake_frames/seq_dynamic_wakes.py +56 -63
  105. foxes/models/wake_frames/streamlines.py +19 -20
  106. foxes/models/wake_frames/timelines.py +328 -127
  107. foxes/models/wake_frames/yawed_wakes.py +4 -1
  108. foxes/models/wake_models/dist_sliced.py +1 -3
  109. foxes/models/wake_models/induction/rankine_half_body.py +4 -4
  110. foxes/models/wake_models/induction/rathmann.py +2 -2
  111. foxes/models/wake_models/induction/self_similar.py +2 -2
  112. foxes/models/wake_models/induction/vortex_sheet.py +2 -2
  113. foxes/models/wake_models/ti/iec_ti.py +34 -17
  114. foxes/models/wake_models/top_hat.py +1 -1
  115. foxes/models/wake_models/wind/bastankhah14.py +2 -2
  116. foxes/models/wake_models/wind/bastankhah16.py +8 -7
  117. foxes/models/wake_models/wind/jensen.py +1 -1
  118. foxes/models/wake_models/wind/turbopark.py +2 -2
  119. foxes/output/__init__.py +4 -1
  120. foxes/output/farm_layout.py +2 -2
  121. foxes/output/flow_plots_2d/__init__.py +0 -1
  122. foxes/output/flow_plots_2d/flow_plots.py +70 -30
  123. foxes/output/grids.py +91 -21
  124. foxes/output/seq_plugins/__init__.py +2 -0
  125. foxes/output/{flow_plots_2d → seq_plugins}/seq_flow_ani_plugin.py +62 -20
  126. foxes/output/seq_plugins/seq_wake_debug_plugin.py +145 -0
  127. foxes/output/slice_data.py +131 -111
  128. foxes/output/state_turbine_map.py +18 -13
  129. foxes/output/state_turbine_table.py +19 -19
  130. foxes/utils/__init__.py +1 -1
  131. foxes/utils/dev_utils.py +42 -0
  132. foxes/utils/dict.py +1 -1
  133. foxes/utils/factory.py +147 -52
  134. foxes/utils/pandas_helpers.py +4 -3
  135. foxes/utils/wind_dir.py +0 -2
  136. foxes/utils/xarray_utils.py +23 -13
  137. foxes/variables.py +37 -0
  138. {foxes-0.8.2.dist-info → foxes-1.0.dist-info}/METADATA +71 -33
  139. foxes-1.0.dist-info/RECORD +307 -0
  140. {foxes-0.8.2.dist-info → foxes-1.0.dist-info}/WHEEL +1 -1
  141. foxes-1.0.dist-info/top_level.txt +4 -0
  142. tests/0_consistency/iterative/test_iterative.py +92 -0
  143. tests/0_consistency/partial_wakes/test_partial_wakes.py +90 -0
  144. tests/1_verification/flappy_0_6/PCt_files/flappy/run.py +85 -0
  145. tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +103 -0
  146. tests/1_verification/flappy_0_6/abl_states/flappy/run.py +85 -0
  147. tests/1_verification/flappy_0_6/abl_states/test_abl_states.py +87 -0
  148. tests/1_verification/flappy_0_6/partial_top_hat/flappy/run.py +82 -0
  149. tests/1_verification/flappy_0_6/partial_top_hat/test_partial_top_hat.py +82 -0
  150. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/flappy/run.py +92 -0
  151. tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +93 -0
  152. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/flappy/run.py +92 -0
  153. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +96 -0
  154. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/flappy/run.py +94 -0
  155. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +122 -0
  156. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/flappy/run.py +94 -0
  157. tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +122 -0
  158. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/flappy/run.py +92 -0
  159. tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +93 -0
  160. tests/1_verification/flappy_0_6_2/grid_rotors/flappy/run.py +85 -0
  161. tests/1_verification/flappy_0_6_2/grid_rotors/test_grid_rotors.py +130 -0
  162. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/flappy/run.py +96 -0
  163. tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +116 -0
  164. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/flappy/run.py +93 -0
  165. tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +99 -0
  166. tests/3_examples/test_examples.py +34 -0
  167. foxes/VERSION +0 -1
  168. foxes/output/flow_plots_2d.py +0 -0
  169. foxes/utils/runners/__init__.py +0 -1
  170. foxes/utils/runners/runners.py +0 -280
  171. foxes-0.8.2.dist-info/RECORD +0 -247
  172. foxes-0.8.2.dist-info/top_level.txt +0 -1
  173. foxes-0.8.2.dist-info/zip-safe +0 -1
  174. {foxes-0.8.2.dist-info → foxes-1.0.dist-info}/LICENSE +0 -0
@@ -44,15 +44,23 @@ class SetFarmVars(TurbineModel):
44
44
  The data, shape: (n_states, n_turbines)
45
45
 
46
46
  """
47
+ if self.initialized:
48
+ raise ValueError(
49
+ f"Model '{self.name}': Cannot add_var after initialization"
50
+ )
51
+ if self.running:
52
+ raise ValueError(f"Model '{self.name}': Cannot add_var while running")
47
53
  self.vars.append(var)
48
- self._vdata.append(np.asarray(data, dtype=FC.DTYPE))
54
+ self.__vdata.append(np.asarray(data, dtype=FC.DTYPE))
49
55
 
50
56
  def reset(self):
51
57
  """
52
58
  Remove all variables.
53
59
  """
60
+ if self.running:
61
+ raise ValueError(f"Model '{self.name}': Cannot reset while running")
54
62
  self.vars = []
55
- self._vdata = []
63
+ self.__vdata = []
56
64
 
57
65
  def output_farm_vars(self, algo):
58
66
  """
@@ -98,7 +106,7 @@ class SetFarmVars(TurbineModel):
98
106
 
99
107
  for i, v in enumerate(self.vars):
100
108
  data = np.full((algo.n_states, algo.n_turbines), np.nan, dtype=FC.DTYPE)
101
- vdata = self._vdata[i]
109
+ vdata = self.__vdata[i]
102
110
 
103
111
  # handle special case of call during vectorized optimization:
104
112
  if (
@@ -110,14 +118,105 @@ class SetFarmVars(TurbineModel):
110
118
  n_ost = algo.states.states.size()
111
119
  n_trb = algo.n_turbines
112
120
  vdata = np.zeros((n_pop, n_ost, n_trb), dtype=FC.DTYPE)
113
- vdata[:] = self._vdata[i][None, :]
121
+ vdata[:] = self.__vdata[i][None, :]
114
122
  vdata = vdata.reshape(n_pop * n_ost, n_trb)
115
123
 
116
124
  data[:] = vdata
117
125
  idata["data_vars"][self.var(v)] = ((FC.STATE, FC.TURBINE), data)
118
126
 
127
+ # special case of turbine positions:
128
+ if v in [FV.X, FV.Y]:
129
+ i = [FV.X, FV.Y].index(v)
130
+ for ti in range(algo.n_turbines):
131
+ t = algo.farm.turbines[ti]
132
+ if len(t.xy.shape) == 1:
133
+ xy = np.zeros((algo.n_states, 2), dtype=FC.DTYPE)
134
+ xy[:] = t.xy[None, :]
135
+ t.xy = xy
136
+ t.xy[:, i] = np.where(
137
+ np.isnan(data[:, ti]), t.xy[:, i], data[:, ti]
138
+ )
139
+
140
+ # special case of rotor diameter and hub height:
141
+ if v in [FV.D, FV.H]:
142
+ for ti in range(algo.n_turbines):
143
+ t = algo.farm.turbines[ti]
144
+ x = np.zeros(algo.n_states, dtype=FC.DTYPE)
145
+ if v == FV.D:
146
+ x[:] = t.D
147
+ t.D = x
148
+ else:
149
+ x[:] = t.H
150
+ t.H = x
151
+ x[:] = np.where(np.isnan(data[:, ti]), x, data[:, ti])
152
+
119
153
  return idata
120
154
 
155
+ def set_running(
156
+ self,
157
+ algo,
158
+ data_stash,
159
+ sel=None,
160
+ isel=None,
161
+ verbosity=0,
162
+ ):
163
+ """
164
+ Sets this model status to running, and moves
165
+ all large data to stash.
166
+
167
+ The stashed data will be returned by the
168
+ unset_running() function after running calculations.
169
+
170
+ Parameters
171
+ ----------
172
+ algo: foxes.core.Algorithm
173
+ The calculation algorithm
174
+ data_stash: dict
175
+ Large data stash, this function adds data here.
176
+ Key: model name. Value: dict, large model data
177
+ sel: dict, optional
178
+ The subset selection dictionary
179
+ isel: dict, optional
180
+ The index subset selection dictionary
181
+ verbosity: int
182
+ The verbosity level, 0 = silent
183
+
184
+ """
185
+ super().set_running(algo, data_stash, sel, isel, verbosity)
186
+
187
+ data_stash[self.name]["vdata"] = self.__vdata
188
+ del self.__vdata
189
+
190
+ def unset_running(
191
+ self,
192
+ algo,
193
+ data_stash,
194
+ sel=None,
195
+ isel=None,
196
+ verbosity=0,
197
+ ):
198
+ """
199
+ Sets this model status to not running, recovering large data
200
+ from stash
201
+
202
+ Parameters
203
+ ----------
204
+ algo: foxes.core.Algorithm
205
+ The calculation algorithm
206
+ data_stash: dict
207
+ Large data stash, this function adds data here.
208
+ Key: model name. Value: dict, large model data
209
+ sel: dict, optional
210
+ The subset selection dictionary
211
+ isel: dict, optional
212
+ The index subset selection dictionary
213
+ verbosity: int
214
+ The verbosity level, 0 = silent
215
+
216
+ """
217
+ super().unset_running(algo, data_stash, sel, isel, verbosity)
218
+ self.__vdata = data_stash[self.name].pop("vdata")
219
+
121
220
  def calculate(self, algo, mdata, fdata, st_sel):
122
221
  """
123
222
  The main model calculation.
@@ -159,36 +258,6 @@ class SetFarmVars(TurbineModel):
159
258
  hsel = ~np.isnan(data)
160
259
  tsel = bsel & hsel
161
260
 
162
- # special case of turbine positions:
163
- if v in [FV.X, FV.Y]:
164
- i = [FV.X, FV.Y].index(v)
165
- for ti in np.where(tsel)[1]:
166
- t = algo.farm.turbines[ti]
167
- if len(t.xy.shape) == 1:
168
- xy = np.zeros((algo.n_states, 2), dtype=FC.DTYPE)
169
- xy[:] = t.xy[None, :]
170
- t.xy = xy
171
- i0 = fdata.states_i0()
172
- hsel = tsel[:, ti]
173
- ssel = i0 + np.where(hsel)[0]
174
- t.xy[ssel, i] = data[hsel, ti]
175
-
176
- # special case of rotor diameter and hub height:
177
- if v in [FV.D, FV.H]:
178
- for ti in np.where(tsel)[1]:
179
- t = algo.farm.turbines[ti]
180
- x = np.zeros(algo.n_states, dtype=FC.DTYPE)
181
- if v == FV.D:
182
- x[:] = t.D
183
- t.D = x
184
- else:
185
- x[:] = t.H
186
- t.H = x
187
- i0 = fdata.states_i0()
188
- hsel = tsel[:, ti]
189
- ssel = i0 + np.where(hsel)[0]
190
- x[ssel] = data[hsel, ti]
191
-
192
261
  fdata[v][tsel] = data[tsel]
193
262
 
194
263
  return {v: fdata[v] for v in self.vars}
@@ -106,6 +106,30 @@ class PCtFile(TurbineType):
106
106
  a += f", var_ws_ct={self.WSCT}, var_ws_P={self.WSP}"
107
107
  return f"{type(self).__name__}({a})"
108
108
 
109
+ def needs_rews2(self):
110
+ """
111
+ Returns flag for requirering REWS2 variable
112
+
113
+ Returns
114
+ -------
115
+ flag: bool
116
+ True if REWS2 is required
117
+
118
+ """
119
+ return self.WSCT == FV.REWS2 or self.WSP == FV.REWS2
120
+
121
+ def needs_rews3(self):
122
+ """
123
+ Returns flag for requirering REWS3 variable
124
+
125
+ Returns
126
+ -------
127
+ flag: bool
128
+ True if REWS3 is required
129
+
130
+ """
131
+ return self.WSCT == FV.REWS3 or self.WSP == FV.REWS3
132
+
109
133
  def output_farm_vars(self, algo):
110
134
  """
111
135
  The variables which are being modified by the model.
@@ -126,6 +126,30 @@ class PCtFromTwo(TurbineType):
126
126
  a += f", var_ws_ct={self.WSCT}, var_ws_P={self.WSP}"
127
127
  return f"{type(self).__name__}({a})"
128
128
 
129
+ def needs_rews2(self):
130
+ """
131
+ Returns flag for requirering REWS2 variable
132
+
133
+ Returns
134
+ -------
135
+ flag: bool
136
+ True if REWS2 is required
137
+
138
+ """
139
+ return self.WSCT == FV.REWS2 or self.WSP == FV.REWS2
140
+
141
+ def needs_rews3(self):
142
+ """
143
+ Returns flag for requirering REWS3 variable
144
+
145
+ Returns
146
+ -------
147
+ flag: bool
148
+ True if REWS3 is required
149
+
150
+ """
151
+ return self.WSCT == FV.REWS3 or self.WSP == FV.REWS3
152
+
129
153
  def output_farm_vars(self, algo):
130
154
  """
131
155
  The variables which are being modified by the model.
@@ -9,3 +9,4 @@ from .CpCt_from_two import CpCtFromTwo
9
9
  from .null_type import NullType
10
10
  from .wsrho2PCt_from_two import WsRho2PCtFromTwo
11
11
  from .wsti2PCt_from_two import WsTI2PCtFromTwo
12
+ from .lookup import FromLookupTable
@@ -0,0 +1,316 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from foxes.core import TurbineType, FData
5
+ from foxes.data import parse_Pct_file_name
6
+ from foxes.models.turbine_models import LookupTable
7
+ import foxes.variables as FV
8
+
9
+
10
+ class FromLookupTable(TurbineType):
11
+ """
12
+ Calculate power and ct by interpolating
13
+ by using a lookup-table
14
+
15
+ Attributes
16
+ ----------
17
+ source: str or pandas.DataFrame
18
+ The file path, static name, or data
19
+ rho: float
20
+ The air densitiy for which the data is valid
21
+ or None for no correction
22
+ WSCT: str
23
+ The wind speed variable for ct lookup
24
+ WSP: str
25
+ The wind speed variable for power lookup
26
+ rpars: dict, optional
27
+ Parameters for pandas file reading
28
+
29
+ :group: models.turbine_types
30
+
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ data_source,
36
+ input_vars,
37
+ varmap={},
38
+ lookup_pars={},
39
+ rho=None,
40
+ p_ct=1.0,
41
+ p_P=1.88,
42
+ var_ws_ct=FV.REWS2,
43
+ var_ws_P=FV.REWS3,
44
+ pd_file_read_pars={},
45
+ interpn_args={},
46
+ **parameters,
47
+ ):
48
+ """
49
+ Constructor.
50
+
51
+ Parameters
52
+ ----------
53
+ data_source: str or pandas.DataFrame
54
+ The file path, static name, or data
55
+ input_vars: list of str
56
+ The foxes input variables
57
+ varmap: dict
58
+ Mapping from foxes variable names
59
+ to column names in the data_source
60
+ lookup_pars: dict
61
+ Additional parameters for the LookupTable model
62
+ rho: float, optional
63
+ The air densitiy for which the data is valid
64
+ or None for no correction
65
+ p_ct: float
66
+ The exponent for yaw dependency of ct
67
+ p_P: float
68
+ The exponent for yaw dependency of P
69
+ var_ws_ct: str
70
+ The wind speed variable for ct lookup
71
+ var_ws_P: str
72
+ The wind speed variable for power lookup
73
+ pd_file_read_pars: dict
74
+ Parameters for pandas file reading
75
+ interpn_args: dict
76
+ Parameters for scipy intern or interp1d
77
+ parameters: dict, optional
78
+ Additional parameters for TurbineType class
79
+
80
+ """
81
+ if not isinstance(data_source, pd.DataFrame):
82
+ pars = parse_Pct_file_name(data_source)
83
+ pars.update(parameters)
84
+ else:
85
+ pars = parameters
86
+
87
+ super().__init__(**pars)
88
+
89
+ self.source = data_source
90
+ self.rho = rho
91
+ self.p_ct = p_ct
92
+ self.p_P = p_P
93
+ self.WSCT = var_ws_ct
94
+ self.WSP = var_ws_P
95
+ self.rpars = pd_file_read_pars
96
+
97
+ if FV.REWS not in input_vars or len(
98
+ set(input_vars).intersection([FV.WS, FV.REWS2, FV.REWS3])
99
+ ):
100
+ raise KeyError(
101
+ f"Turbine type '{self.name}': Expecting '{FV.REWS}' as wind speed variable in inputv_vars, got {input_vars}"
102
+ )
103
+
104
+ iargs = dict(bounds_error=False, fill_value=0)
105
+ iargs.update(interpn_args)
106
+ self._lookup = LookupTable(
107
+ data_source=data_source,
108
+ input_vars=input_vars,
109
+ output_vars=[FV.P, FV.CT],
110
+ varmap=varmap,
111
+ interpn_args=iargs,
112
+ **lookup_pars,
113
+ )
114
+
115
+ def __repr__(self):
116
+ a = f"D={self.D}, H={self.H}, P_nominal={self.P_nominal}, P_unit={self.P_unit}, rho={self.rho}"
117
+ a += f", var_ws_ct={self.WSCT}, var_ws_P={self.WSP}"
118
+ return f"{type(self).__name__}({a})"
119
+
120
+ def needs_rews2(self):
121
+ """
122
+ Returns flag for requirering REWS2 variable
123
+
124
+ Returns
125
+ -------
126
+ flag: bool
127
+ True if REWS2 is required
128
+
129
+ """
130
+ return self.WSCT == FV.REWS2 or self.WSP == FV.REWS2
131
+
132
+ def needs_rews3(self):
133
+ """
134
+ Returns flag for requirering REWS3 variable
135
+
136
+ Returns
137
+ -------
138
+ flag: bool
139
+ True if REWS3 is required
140
+
141
+ """
142
+ return self.WSCT == FV.REWS3 or self.WSP == FV.REWS3
143
+
144
+ def sub_models(self):
145
+ """
146
+ List of all sub-models
147
+
148
+ Returns
149
+ -------
150
+ smdls: list of foxes.core.Model
151
+ All sub models
152
+
153
+ """
154
+ return [self._lookup]
155
+
156
+ def output_farm_vars(self, algo):
157
+ """
158
+ The variables which are being modified by the model.
159
+
160
+ Parameters
161
+ ----------
162
+ algo: foxes.core.Algorithm
163
+ The calculation algorithm
164
+
165
+ Returns
166
+ -------
167
+ output_vars: list of str
168
+ The output variable names
169
+
170
+ """
171
+ return [FV.P, FV.CT]
172
+
173
+ def initialize(self, algo, verbosity=0, force=False):
174
+ """
175
+ Initializes the model.
176
+
177
+ Parameters
178
+ ----------
179
+ algo: foxes.core.Algorithm
180
+ The calculation algorithm
181
+ verbosity: int
182
+ The verbosity level, 0 = silent
183
+ force: bool
184
+ Overwrite existing data
185
+
186
+ """
187
+ super().initialize(algo, verbosity, force)
188
+ if self.P_nominal is None:
189
+ col_P = self._lookup.varmap.get(FV.P, FV.P)
190
+ self.P_nominal = np.max(self._lookup._data[col_P].to_numpy())
191
+
192
+ def modify_cutin(
193
+ self,
194
+ modify_ct,
195
+ modify_P,
196
+ steps=20,
197
+ iterations=100,
198
+ a=0.55,
199
+ b=0.55,
200
+ ):
201
+ """
202
+ Modify the data such that a discontinuity
203
+ at cutin wind speed is avoided
204
+
205
+ Parameters
206
+ ----------
207
+ variable: str
208
+ The target variable
209
+ modify_ct: bool
210
+ Flag for modification of the ct curve
211
+ modify_P: bool
212
+ Flag for modification of the power curve
213
+ steps: int
214
+ The number of wind speed steps between 0 and
215
+ the cutin wind speed
216
+ iterations: int
217
+ The number of iterations
218
+ a: float
219
+ Coefficient for iterative mixing
220
+ b: float
221
+ Coefficient for iterative mixing
222
+
223
+ """
224
+ if modify_ct or modify_P:
225
+ raise NotImplementedError
226
+
227
+ else:
228
+ super().modify_cutin(modify_ct, modify_P)
229
+
230
+ def calculate(self, algo, mdata, fdata, st_sel):
231
+ """ "
232
+ The main model calculation.
233
+
234
+ This function is executed on a single chunk of data,
235
+ all computations should be based on numpy arrays.
236
+
237
+ Parameters
238
+ ----------
239
+ algo: foxes.core.Algorithm
240
+ The calculation algorithm
241
+ mdata: foxes.core.MData
242
+ The model data
243
+ fdata: foxes.core.FData
244
+ The farm data
245
+ st_sel: numpy.ndarray of bool
246
+ The state-turbine selection,
247
+ shape: (n_states, n_turbines)
248
+
249
+ Returns
250
+ -------
251
+ results: dict
252
+ The resulting data, keys: output variable str.
253
+ Values: numpy.ndarray with shape (n_states, n_turbines)
254
+
255
+ """
256
+ # prepare data for lookup:
257
+ input_vars = self._lookup.input_vars
258
+ fdata_lookup = FData(
259
+ data={v: fdata[v] for v in input_vars},
260
+ dims={v: fdata.dims[v] for v in input_vars},
261
+ loop_dims=fdata.loop_dims,
262
+ )
263
+ for v in self.output_farm_vars(algo):
264
+ fdata_lookup.add(v, fdata[v], fdata.dims[v])
265
+
266
+ rews2 = None
267
+ rews3 = None
268
+ if self.WSP != FV.REWS or self.WSCT != FV.REWS:
269
+ rews2 = fdata[self.WSCT].copy()
270
+ rews3 = fdata[self.WSP].copy()
271
+
272
+ # apply air density correction:
273
+ if self.rho is not None:
274
+ if rews2 is None:
275
+ rews2 = fdata[self.WSCT].copy()
276
+ rews3 = fdata[self.WSP].copy()
277
+
278
+ # correct wind speed by air density, such
279
+ # that in the partial load region the
280
+ # correct value is reconstructed:
281
+ rho = fdata[FV.RHO][st_sel]
282
+ rews3[st_sel] *= (self.rho / rho) ** (1.0 / 3.0)
283
+ del rho
284
+
285
+ # in yawed case, calc yaw corrected wind speed:
286
+ if FV.YAWM in fdata and (self.p_P is not None or self.p_ct is not None):
287
+ if rews2 is None:
288
+ rews2 = fdata[self.WSCT].copy()
289
+ rews3 = fdata[self.WSP].copy()
290
+
291
+ # calculate corrected wind speed wsc,
292
+ # gives ws**3 * cos**p_P in partial load region
293
+ # and smoothly deals with full load region:
294
+ yawm = fdata[FV.YAWM][st_sel]
295
+ if np.any(np.isnan(yawm)):
296
+ raise ValueError(
297
+ f"{self.name}: Found NaN values for variable '{FV.YAWM}'. Maybe change order in turbine_models?"
298
+ )
299
+ cosm = np.cos(yawm / 180 * np.pi)
300
+ if self.p_ct is not None:
301
+ rews2[st_sel] *= (cosm**self.p_ct) ** 0.5
302
+ if self.p_P is not None:
303
+ rews3[st_sel] *= (cosm**self.p_P) ** (1.0 / 3.0)
304
+ del yawm, cosm
305
+
306
+ # run lookup:
307
+ if rews2 is None:
308
+ out = self._lookup.calculate(algo, mdata, fdata_lookup, st_sel)
309
+ else:
310
+ fdata_lookup[FV.REWS] = rews2
311
+ ct = self._lookup.calculate(algo, mdata, fdata_lookup, st_sel)[FV.CT]
312
+ fdata_lookup[FV.REWS] = rews3
313
+ out = self._lookup.calculate(algo, mdata, fdata_lookup, st_sel)
314
+ out[FV.CT] = ct
315
+
316
+ return out
@@ -9,6 +9,56 @@ class NullType(TurbineType):
9
9
 
10
10
  """
11
11
 
12
+ def __init__(
13
+ self,
14
+ *args,
15
+ needs_rews2=False,
16
+ needs_rews3=False,
17
+ **kwargs,
18
+ ):
19
+ """
20
+ Constructor.
21
+
22
+ Parameters
23
+ ----------
24
+ args: tuple, optional
25
+ Additional parameters for TurbineType class
26
+ needs_rews2: bool
27
+ Flag for runs that require the REWS2 variable
28
+ needs_rews3: bool
29
+ Flag for runs that require the REWS3 variable
30
+ kwargs: dict, optional
31
+ Additional parameters for TurbineType class
32
+
33
+ """
34
+ super().__init__(*args, **kwargs)
35
+ self._rews2 = needs_rews2
36
+ self._rews3 = needs_rews3
37
+
38
+ def needs_rews2(self):
39
+ """
40
+ Returns flag for requirering REWS2 variable
41
+
42
+ Returns
43
+ -------
44
+ flag: bool
45
+ True if REWS2 is required
46
+
47
+ """
48
+ return self._rews2
49
+
50
+ def needs_rews3(self):
51
+ """
52
+ Returns flag for requirering REWS3 variable
53
+
54
+ Returns
55
+ -------
56
+ flag: bool
57
+ True if REWS3 is required
58
+
59
+ """
60
+ return self._rews3
61
+
12
62
  def output_farm_vars(self, algo):
13
63
  """
14
64
  The variables which are being modified by the model.
@@ -115,6 +115,30 @@ class WsRho2PCtFromTwo(TurbineType):
115
115
  self._P = None
116
116
  self._ct = None
117
117
 
118
+ def needs_rews2(self):
119
+ """
120
+ Returns flag for requirering REWS2 variable
121
+
122
+ Returns
123
+ -------
124
+ flag: bool
125
+ True if REWS2 is required
126
+
127
+ """
128
+ return self.WSCT == FV.REWS2 or self.WSP == FV.REWS2
129
+
130
+ def needs_rews3(self):
131
+ """
132
+ Returns flag for requirering REWS3 variable
133
+
134
+ Returns
135
+ -------
136
+ flag: bool
137
+ True if REWS3 is required
138
+
139
+ """
140
+ return self.WSCT == FV.REWS3 or self.WSP == FV.REWS3
141
+
118
142
  def output_farm_vars(self, algo):
119
143
  """
120
144
  The variables which are being modified by the model.
@@ -127,6 +127,30 @@ class WsTI2PCtFromTwo(TurbineType):
127
127
  a = f"D={self.D}, H={self.H}, P_nominal={self.P_nominal}, P_unit={self.P_unit}, rho={self.rho}"
128
128
  return f"{type(self).__name__}({a})"
129
129
 
130
+ def needs_rews2(self):
131
+ """
132
+ Returns flag for requirering REWS2 variable
133
+
134
+ Returns
135
+ -------
136
+ flag: bool
137
+ True if REWS2 is required
138
+
139
+ """
140
+ return self.WSCT == FV.REWS2 or self.WSP == FV.REWS2
141
+
142
+ def needs_rews3(self):
143
+ """
144
+ Returns flag for requirering REWS3 variable
145
+
146
+ Returns
147
+ -------
148
+ flag: bool
149
+ True if REWS3 is required
150
+
151
+ """
152
+ return self.WSCT == FV.REWS3 or self.WSP == FV.REWS3
153
+
130
154
  def output_farm_vars(self, algo):
131
155
  """
132
156
  The variables which are being modified by the model.
@@ -31,7 +31,7 @@ class DataProfile(VerticalProfile):
31
31
  col_z=None,
32
32
  col_var=None,
33
33
  pd_read_pars={},
34
- **interp_pars
34
+ **interp_pars,
35
35
  ):
36
36
  """
37
37
  Constructor
@@ -8,3 +8,4 @@ from .timelines import Timelines
8
8
  from .yawed_wakes import YawedWakes
9
9
  from .farm_order import FarmOrder
10
10
  from .seq_dynamic_wakes import SeqDynamicWakes
11
+ from .dynamic_wakes import DynamicWakes