Skip to content

Commit e6bbd6e

Browse files
authored
Add parallel solve for sample_flow_at_points (#1059)
* Running sample_flow_at_points in all interface options. * Reducing code repetitions. * Reduce code repetition in run() call. * Add test for sample_flow_at_points
1 parent a14193b commit e6bbd6e

File tree

2 files changed

+139
-52
lines changed

2 files changed

+139
-52
lines changed

floris/par_floris_model.py

Lines changed: 113 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
from floris.core import State
1010
from floris.floris_model import FlorisModel
11+
from floris.type_dec import (
12+
NDArrayFloat,
13+
)
1114

1215

1316
class ParFlorisModel(FlorisModel):
@@ -123,81 +126,111 @@ def run(self) -> None:
123126
t0 = timerpc()
124127
super().run()
125128
t1 = timerpc()
126-
elif self.interface == "multiprocessing":
129+
self._print_timings(t0, t1, None, None)
130+
else:
127131
t0 = timerpc()
128132
self.core.initialize_domain()
129133
parallel_run_inputs = self._preprocessing()
130134
t1 = timerpc()
131-
if self.return_turbine_powers_only:
132-
with self._PoolExecutor(self.max_workers) as p:
133-
self._turbine_powers_split = p.starmap(
134-
_parallel_run_powers_only,
135+
if self.interface == "multiprocessing":
136+
if self.return_turbine_powers_only:
137+
with self._PoolExecutor(self.max_workers) as p:
138+
self._turbine_powers_split = p.starmap(
139+
_parallel_run_powers_only,
140+
parallel_run_inputs
141+
)
142+
else:
143+
with self._PoolExecutor(self.max_workers) as p:
144+
self._fmodels_split = p.starmap(_parallel_run, parallel_run_inputs)
145+
elif self.interface == "pathos":
146+
if self.return_turbine_powers_only:
147+
self._turbine_powers_split = self.pathos_pool.map(
148+
_parallel_run_powers_only_map,
135149
parallel_run_inputs
136150
)
137-
else:
138-
with self._PoolExecutor(self.max_workers) as p:
139-
self._fmodels_split = p.starmap(_parallel_run, parallel_run_inputs)
151+
else:
152+
self._fmodels_split = self.pathos_pool.map(
153+
_parallel_run_map,
154+
parallel_run_inputs
155+
)
156+
elif self.interface == "concurrent":
157+
if self.return_turbine_powers_only:
158+
with self._PoolExecutor(self.max_workers) as p:
159+
self._turbine_powers_split = p.map(
160+
_parallel_run_powers_only_map,
161+
parallel_run_inputs
162+
)
163+
self._turbine_powers_split = list(self._turbine_powers_split)
164+
else:
165+
with self._PoolExecutor(self.max_workers) as p:
166+
self._fmodels_split = p.map(
167+
_parallel_run_map,
168+
parallel_run_inputs
169+
)
170+
self._fmodels_split = list(self._fmodels_split)
140171
t2 = timerpc()
141172
self._postprocessing()
142173
self.core.farm.finalize(self.core.grid.unsorted_indices)
143174
self.core.state = State.USED
144175
t3 = timerpc()
145-
elif self.interface == "pathos":
176+
self._print_timings(t0, t1, t2, t3)
177+
178+
def sample_flow_at_points(self, x: NDArrayFloat, y: NDArrayFloat, z: NDArrayFloat):
179+
"""
180+
Sample the flow field at specified points.
181+
182+
Args:
183+
x: The x-coordinates of the points.
184+
y: The y-coordinates of the points.
185+
z: The z-coordinates of the points.
186+
187+
Returns:
188+
NDArrayFloat: The wind speeds at the specified points.
189+
"""
190+
if self.return_turbine_powers_only:
191+
raise NotImplementedError(
192+
"Sampling flow at points is not supported when "
193+
"return_turbine_powers_only is set to True on ParFlorisModel."
194+
)
195+
196+
if self.interface is None:
146197
t0 = timerpc()
147-
self.core.initialize_domain()
148-
parallel_run_inputs = self._preprocessing()
198+
sampled_wind_speeds = super().sample_flow_at_points(x, y, z)
149199
t1 = timerpc()
150-
if self.return_turbine_powers_only:
151-
self._turbine_powers_split = self.pathos_pool.map(
152-
_parallel_run_powers_only_map,
153-
parallel_run_inputs
154-
)
155-
else:
156-
self._fmodels_split = self.pathos_pool.map(
157-
_parallel_run_map,
158-
parallel_run_inputs
159-
)
160-
t2 = timerpc()
161-
self._postprocessing()
162-
self.core.farm.finalize(self.core.grid.unsorted_indices)
163-
self.core.state = State.USED
164-
t3 = timerpc()
165-
elif self.interface == "concurrent":
200+
self._print_timings(t0, t1, None, None)
201+
else:
166202
t0 = timerpc()
167203
self.core.initialize_domain()
168204
parallel_run_inputs = self._preprocessing()
205+
parallel_sample_flow_at_points_inputs = [
206+
(fmodel_dict, control_setpoints, x, y, z)
207+
for fmodel_dict, control_setpoints in parallel_run_inputs
208+
]
169209
t1 = timerpc()
170-
if self.return_turbine_powers_only:
210+
if self.interface == "multiprocessing":
171211
with self._PoolExecutor(self.max_workers) as p:
172-
self._turbine_powers_split = p.map(
173-
_parallel_run_powers_only_map,
174-
parallel_run_inputs
212+
sampled_wind_speeds_p = p.starmap(
213+
_parallel_sample_flow_at_points,
214+
parallel_sample_flow_at_points_inputs
175215
)
176-
self._turbine_powers_split = list(self._turbine_powers_split)
177-
else:
216+
elif self.interface == "pathos":
217+
sampled_wind_speeds_p = self.pathos_pool.map(
218+
_parallel_sample_flow_at_points_map,
219+
parallel_sample_flow_at_points_inputs
220+
)
221+
elif self.interface == "concurrent":
178222
with self._PoolExecutor(self.max_workers) as p:
179-
self._fmodels_split = p.map(
180-
_parallel_run_map,
181-
parallel_run_inputs
223+
sampled_wind_speeds_p = p.map(
224+
_parallel_sample_flow_at_points_map,
225+
parallel_sample_flow_at_points_inputs
182226
)
183-
self._fmodels_split = list(self._fmodels_split)
227+
sampled_wind_speeds_p = list(sampled_wind_speeds_p)
184228
t2 = timerpc()
185-
self._postprocessing()
186-
self.core.farm.finalize(self.core.grid.unsorted_indices)
187-
self.core.state = State.USED
229+
sampled_wind_speeds = np.concatenate(sampled_wind_speeds_p, axis=0)
188230
t3 = timerpc()
189-
if self.print_timings:
190-
print("===============================================================================")
191-
if self.interface is None:
192-
print(f"Total time spent for serial calculation (interface=None): {t1 - t0:.3f} s")
193-
else:
194-
print(
195-
"Total time spent for parallel calculation "
196-
f"({self.max_workers} workers): {t3-t0:.3f} s"
197-
)
198-
print(f" Time spent in parallel preprocessing: {t1-t0:.3f} s")
199-
print(f" Time spent in parallel loop execution: {t2-t1:.3f} s.")
200-
print(f" Time spent in parallel postprocessing: {t3-t2:.3f} s")
231+
self._print_timings(t0, t1, t2, t3)
232+
233+
return sampled_wind_speeds
201234

202235
def _preprocessing(self):
203236
"""
@@ -278,6 +311,23 @@ def _postprocessing(self):
278311
axis=0
279312
)
280313

314+
def _print_timings(self, t0, t1, t2, t3):
315+
"""
316+
Print the timings for the parallel execution.
317+
"""
318+
if self.print_timings:
319+
print("===============================================================================")
320+
if self.interface is None:
321+
print(f"Total time spent for serial calculation (interface=None): {t1 - t0:.3f} s")
322+
else:
323+
print(
324+
"Total time spent for parallel calculation "
325+
f"({self.max_workers} workers): {t3-t0:.3f} s"
326+
)
327+
print(f" Time spent in parallel preprocessing: {t1-t0:.3f} s")
328+
print(f" Time spent in parallel loop execution: {t2-t1:.3f} s.")
329+
print(f" Time spent in parallel postprocessing: {t3-t2:.3f} s")
330+
281331
def _get_turbine_powers(self):
282332
"""
283333
Calculates the power at each turbine in the wind farm.
@@ -364,3 +414,14 @@ def _parallel_run_powers_only_map(x):
364414
Wrapper for unpacking inputs to _parallel_run_powers_only() for use with map().
365415
"""
366416
return _parallel_run_powers_only(*x)
417+
418+
def _parallel_sample_flow_at_points(fmodel_dict, set_kwargs, x, y, z):
419+
fmodel = FlorisModel(fmodel_dict)
420+
fmodel.set(**set_kwargs)
421+
return fmodel.sample_flow_at_points(x, y, z)
422+
423+
def _parallel_sample_flow_at_points_map(x):
424+
"""
425+
Wrapper for unpacking inputs to _parallel_sample_flow_at_points() for use with map().
426+
"""
427+
return _parallel_sample_flow_at_points(*x)

tests/par_floris_model_unit_test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,29 @@ def test_control_setpoints(sample_inputs_fixture):
286286

287287
assert powers_fmodel.shape == powers_pfmodel.shape
288288
assert np.allclose(powers_fmodel, powers_pfmodel)
289+
290+
def test_sample_flow_at_points(sample_inputs_fixture):
291+
292+
sample_inputs_fixture.core["wake"]["model_strings"]["velocity_model"] = VELOCITY_MODEL
293+
sample_inputs_fixture.core["wake"]["model_strings"]["deflection_model"] = DEFLECTION_MODEL
294+
295+
fmodel = FlorisModel(sample_inputs_fixture.core)
296+
297+
wind_speeds = np.array([8.0, 8.0, 9.0])
298+
wind_directions = np.array([270.0, 270.0, 270.0])
299+
fmodel.set(
300+
wind_directions=wind_speeds.flatten(),
301+
wind_speeds=wind_directions.flatten(),
302+
turbulence_intensities=0.06 * np.ones_like(wind_speeds.flatten()),
303+
)
304+
305+
x_test = np.array([500.0, 750.0, 1000.0, 1250.0, 1500.0])
306+
y_test = np.array([0.0, 0.0, 0.0, 0.0, 0.0])
307+
z_test = np.array([90.0, 90.0, 90.0, 90.0, 90.0])
308+
309+
ws_base = fmodel.sample_flow_at_points(x_test, y_test, z_test)
310+
311+
for interface in ["multiprocessing", "pathos", "concurrent"]:
312+
pfmodel = ParFlorisModel(fmodel, max_workers=2, interface=interface)
313+
ws_test = pfmodel.sample_flow_at_points(x_test, y_test, z_test)
314+
assert np.allclose(ws_base, ws_test)

0 commit comments

Comments
 (0)