@@ -364,9 +364,90 @@ async def clone(
364364
365365 @staticmethod
366366 def map (
367- coro : Callable [[_X ], Awaitable [_Y ]], iterable : Iterable [_X ]
367+ coro : Callable [[_X ], Awaitable [_Y ]], iterable : Iterable [_X ], /
368368 ) -> Iterable [_Y ]:
369- """Run an async function on each element of an iterable concurrently."""
369+ """
370+ Run an async function on each element of an iterable concurrently.
371+
372+ :param coro: An async function to run on each element.
373+ :param iterable: An iterable of arguments to pass to the function.
374+
375+ :return: An iterable of results from the function.
376+
377+ Example usage: ::
378+
379+ import os
380+ from pathlib import Path
381+ from foamlib import AsyncSlurmFoamCase
382+ from scipy.optimize import differential_evolution
383+
384+ # Set up base case for optimization
385+ base = AsyncSlurmFoamCase(Path(os.environ["FOAM_TUTORIALS"]) / "incompressible/simpleFoam/pitzDaily")
386+
387+ async def objective_function(x):
388+ async with base.clone() as case:
389+ # Set inlet velocity based on optimization parameters
390+ case[0]["U"].boundary_field["inlet"].value = [x[0], 0, 0]
391+
392+ # Run with fallback to local execution if Slurm unavailable
393+ await case.run(fallback=True)
394+
395+ # Return objective (minimize velocity magnitude at outlet)
396+ return abs(case[-1]["U"].internal_field[0][0])
397+
398+ # Run optimization with parallel jobs
399+ result = differential_evolution(
400+ objective_function,
401+ bounds=[(-1, 1)],
402+ workers=AsyncSlurmFoamCase.map,
403+ polish=False
404+ )
405+ print(f"Optimal inlet velocity: {result.x[0]}")
406+ """
370407 return asyncio .get_event_loop ().run_until_complete (
371408 asyncio .gather (* (coro (arg ) for arg in iterable ))
372409 )
410+
411+ @staticmethod
412+ async def run_all (cases : Iterable ["AsyncFoamCase | Awaitable[object]" ], / ) -> None :
413+ """
414+ Run multiple cases concurrently.
415+
416+ Note that maximum parallelism for :class:`AsyncFoamCase` is limited by the :attr:`max_cpus`
417+ attribute in order to avoid overloading the system.
418+
419+ :param cases: Instances of :class:`AsyncFoamCase` (:meth:`run` will be called) or arbitrary
420+ awaitables (will be awaited as-is).
421+
422+ Example usage: ::
423+ from foamlib import AsyncFoamCase
424+
425+ case1 = AsyncFoamCase("path/to/case1")
426+ case2 = AsyncFoamCase("path/to/case2")
427+
428+ await AsyncFoamCase.run_all([case1, case2]) # Cases will run in parallel (as much the :attr:`max_cpus` allows)
429+ """
430+ await asyncio .gather (
431+ * (case .run () if isinstance (case , AsyncFoamCase ) else case for case in cases )
432+ )
433+
434+ @staticmethod
435+ def run_all_wait (cases : Iterable ["AsyncFoamCase | Awaitable[object]" ], / ) -> None :
436+ """
437+ Run multiple cases concurrently, blocking until all are complete.
438+
439+ Note that maximum parallelism for :class:`AsyncFoamCase` is limited by the :attr:`max_cpus`
440+ attribute in order to avoid overloading the system.
441+
442+ :param cases: Instances of :class:`AsyncFoamCase` (:meth:`run` will be called) or arbitrary
443+ awaitables (will be awaited as-is).
444+
445+ Example usage: ::
446+ from foamlib import AsyncFoamCase
447+
448+ case1 = AsyncFoamCase("path/to/case1")
449+ case2 = AsyncFoamCase("path/to/case2")
450+
451+ AsyncFoamCase.run_all_wait([case1, case2]) # Cases will run in parallel (as much the :attr:`max_cpus` allows)
452+ """
453+ asyncio .get_event_loop ().run_until_complete (AsyncFoamCase .run_all (cases ))
0 commit comments