diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml
index 3c7fedaa..55ffd4e1 100644
--- a/.github/workflows/python-test.yml
+++ b/.github/workflows/python-test.yml
@@ -35,7 +35,7 @@ jobs:
coverage xml
genbadge coverage -i coverage.xml -o coverage.svg
- name: Verify Changed files
- uses: tj-actions/verify-changed-files@v16
+ uses: tj-actions/verify-changed-files@v19
id: verify-changed-files
with:
files: coverage.svg
diff --git a/README.md b/README.md
index 3f0675ff..5a9136fe 100644
--- a/README.md
+++ b/README.md
@@ -41,16 +41,20 @@ next generation using the fitness values of all candidates it evaluated and rece
It was already successfully applied in several accepted scientific publications. Applications include grid load
forecasting, remote sensing, and structural molecular biology:
-> D. Coquelin, R. Sedona, M. Riedel, and M. Götz. **Evolutionary Optimization of Neural Architectures in Remote Sensing
-> Classification Problems**. IEEE International Geoscience and Remote Sensing Symposium IGARSS, Brussels, Belgium,
-> pp. 1587-1590 (2021). https://doi.org/10.1109/IGARSS47720.2021.9554309
-
> O. Taubert, F. von der Lehr, A. Bazarova, et al. **RNA contact prediction by data efficient deep learning**. Commun
> Biol 6, 913 (2023). https://doi.org/10.1038/s42003-023-05244-9
> D. Coquelin, K. Flügel, M. Weiel, et al. **Harnessing Orthogonality to Train Low-Rank Neural Networks**. arXiv
> preprint (2023). https://doi.org/10.48550/arXiv.2401.08505
+> Y. Funk, M. Götz, & H. Anzt. **Prediction of optimal solvers for sparse linear systems using deep learning**.
+> Proceedings of the 2022 SIAM Conference on Parallel Processing for Scientific Computing (pp. 14-24). Society for
+> Industrial and Applied Mathematics (2022). https://doi.org/10.1137/1.9781611977141.2
+
+> D. Coquelin, R. Sedona, M. Riedel, and M. Götz. **Evolutionary Optimization of Neural Architectures in Remote Sensing
+> Classification Problems**. IEEE International Geoscience and Remote Sensing Symposium IGARSS, Brussels, Belgium,
+> pp. 1587-1590 (2021). https://doi.org/10.1109/IGARSS47720.2021.9554309
+
## In more technical terms
``Propulate`` is a massively parallel evolutionary hyperparameter optimizer based on the island model with asynchronous
diff --git a/coverage.svg b/coverage.svg
index bb1e512b..8ef24c92 100644
--- a/coverage.svg
+++ b/coverage.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/propulate/__init__.py b/propulate/__init__.py
index 172eef6e..28bfa465 100644
--- a/propulate/__init__.py
+++ b/propulate/__init__.py
@@ -1,14 +1,12 @@
-# -*- coding: utf-8 -*-
-from pkg_resources import DistributionNotFound, get_distribution
+from importlib.metadata import PackageNotFoundError, version
try:
# Change here if project is renamed and does not equal the package name
- dist_name = __name__
- __version__ = get_distribution(dist_name).version
-except DistributionNotFound:
+ __version__ = version(__name__)
+except PackageNotFoundError:
__version__ = "unknown"
finally:
- del get_distribution, DistributionNotFound
+ del version, PackageNotFoundError
from . import propagators
from .islands import Islands
diff --git a/tests/test_cmaes.py b/tests/test_cmaes.py
index d43d29b4..76c6a87b 100644
--- a/tests/test_cmaes.py
+++ b/tests/test_cmaes.py
@@ -1,56 +1,36 @@
import pathlib
import random
-from typing import Tuple
import pytest
from propulate import Propulator
-from propulate.propagators import BasicCMA, CMAPropagator
+from propulate.propagators import ActiveCMA, BasicCMA, CMAPropagator
from propulate.utils.benchmark_functions import get_function_search_space
-@pytest.fixture(
- params=[
- ("rosenbrock", 0.0),
- ("step", -25.0),
- ("quartic", 0.0),
- ("rastrigin", 0.0),
- ("griewank", 0.0),
- ("schwefel", 0.0),
- ("bisphere", 0.0),
- ("birastrigin", 0.0),
- ("bukin", 0.0),
- ("eggcrate", -1.0),
- ("himmelblau", 0.0),
- ("keane", 0.6736675),
- ("leon", 0.0),
- ("sphere", 0.0), # (fname, expected)
- ]
-)
-def function_parameters(request):
- """Define benchmark function parameter sets as used in tests."""
+@pytest.fixture(params=[BasicCMA(), ActiveCMA()])
+def cma_adapter(request):
+ """Iterate over CMA adapters (basic and active)."""
return request.param
-def test_cmaes(
- function_parameters: Tuple[str, float], mpi_tmp_path: pathlib.Path
-) -> None:
+def test_cmaes_basic(cma_adapter, mpi_tmp_path: pathlib.Path) -> None:
"""
- Test Propulator to optimize a benchmark function using a CMA-ES propagator.
+ Test Propulator to optimize a benchmark function using CMA-ES propagators.
This test is run both sequentially and in parallel.
Parameters
----------
- function_parameters : Tuple[str, float]
- The tuple containing each function name along with its global minimum.
+ cma_adapter : CMAAdapter
+ The CMA adapter used, either basic or active.
mpi_tmp_path : pathlib.Path
The temporary checkpoint directory.
"""
rng = random.Random(42) # Separate random number generator for optimization.
- function, limits = get_function_search_space(function_parameters[0])
+ function, limits = get_function_search_space("sphere")
# Set up evolutionary operator.
- adapter = BasicCMA()
+ adapter = cma_adapter
propagator = CMAPropagator(adapter, limits, rng=rng)
# Set up Propulator performing actual optimization.
diff --git a/tests/test_island.py b/tests/test_island.py
index a89fab65..368f9907 100644
--- a/tests/test_island.py
+++ b/tests/test_island.py
@@ -1,7 +1,7 @@
import copy
import pathlib
import random
-from typing import Tuple
+from typing import Callable, Dict, Tuple
import deepdiff
import numpy as np
@@ -9,60 +9,62 @@
from mpi4py import MPI
from propulate import Islands
+from propulate.propagators import Propagator
from propulate.utils import get_default_propagator, set_logger_config
from propulate.utils.benchmark_functions import get_function_search_space
+@pytest.fixture(scope="module")
+def global_variables():
+ """Get global variables used by most of the tests in this module."""
+ rng = random.Random(
+ 42 + MPI.COMM_WORLD.rank
+ ) # Set up separate random number generator for optimization.
+ function, limits = get_function_search_space(
+ "sphere"
+ ) # Get function and search space to optimize.
+ propagator = get_default_propagator(
+ pop_size=4,
+ limits=limits,
+ rng=rng,
+ ) # Set up evolutionary operator.
+ yield rng, function, limits, propagator
+
+
@pytest.fixture(
params=[
- ("rosenbrock", 0.0),
- ("step", -25.0),
- ("quartic", 0.0),
- ("rastrigin", 0.0),
- ("griewank", 0.0),
- ("schwefel", 0.0),
- ("bisphere", 0.0),
- ("birastrigin", 0.0),
- ("bukin", 0.0),
- ("eggcrate", -1.0),
- ("himmelblau", 0.0),
- ("keane", 0.6736675),
- ("leon", 0.0),
- ("sphere", 0.0), # (fname, expected)
+ True,
+ False,
]
)
-def function_parameters(request):
- """Define benchmark function parameter sets as used in tests."""
+def pollination(request):
+ """Iterate through pollination parameter."""
return request.param
@pytest.mark.mpi(min_size=4)
def test_islands(
- function_parameters: Tuple[str, float], mpi_tmp_path: pathlib.Path
+ global_variables: Tuple[
+ random.Random, Callable, Dict[str, Tuple[float, float]], Propagator
+ ],
+ pollination: bool,
+ mpi_tmp_path: pathlib.Path,
) -> None:
"""
Test basic island functionality (only run in parallel with at least four processes).
Parameters
----------
- function_parameters : Tuple[str, float]
- The tuple containing each function name along with its global minimum.
+ global_variables : Tuple[random.Random, Callable, Dict[str, Tuple[float, float]], propulate.Propagator]
+ Global variables used by most of the tests in this module.
+ pollination : bool
+ Whether pollination or real migration should be used.
mpi_tmp_path : pathlib.Path
The temporary checkpoint directory.
"""
- rng = random.Random(
- 42 + MPI.COMM_WORLD.rank
- ) # Separate random number generator for optimization
- function, limits = get_function_search_space(function_parameters[0])
+ rng, function, limits, propagator = global_variables
set_logger_config(log_file=mpi_tmp_path / "log.log")
- # Set up evolutionary operator.
- propagator = get_default_propagator(
- pop_size=4,
- limits=limits,
- rng=rng,
- )
-
# Set up island model.
islands = Islands(
loss_fn=function,
@@ -71,45 +73,36 @@ def test_islands(
generations=100,
num_islands=2,
migration_probability=0.9,
- pollination=False,
+ pollination=pollination,
checkpoint_path=mpi_tmp_path,
)
# Run actual optimization.
islands.evolve(
- top_n=1,
- logging_interval=10,
debug=2,
)
@pytest.mark.mpi(min_size=4)
def test_checkpointing_isolated(
- function_parameters: Tuple[str, float], mpi_tmp_path: pathlib.Path
+ global_variables: Tuple[
+ random.Random, Callable, Dict[str, Tuple[float, float]], Propagator
+ ],
+ mpi_tmp_path: pathlib.Path,
) -> None:
"""
Test isolated island checkpointing without migration (only run in parallel with at least four processes).
Parameters
----------
- function_parameters : Tuple[str, float]
- The tuple containing each function name along with its global minimum.
+ global_variables : Tuple[random.Random, Callable, Dict[str, Tuple[float, float]], propulate.Propagator]
+ Global variables used by most of the tests in this module.
mpi_tmp_path : pathlib.Path
The temporary checkpoint directory.
"""
- rng = random.Random(
- 42 + MPI.COMM_WORLD.rank
- ) # Separate random number generator for optimization
- function, limits = get_function_search_space(function_parameters[0])
+ rng, function, limits, propagator = global_variables
set_logger_config(log_file=mpi_tmp_path / "log.log")
- # Set up evolutionary operator.
- propagator = get_default_propagator(
- pop_size=4,
- limits=limits,
- rng=rng,
- )
-
# Set up island model.
islands = Islands(
loss_fn=function,
@@ -122,11 +115,7 @@ def test_checkpointing_isolated(
)
# Run actual optimization.
- islands.evolve(
- top_n=1,
- logging_interval=10,
- debug=2,
- )
+ islands.evolve(top_n=1, debug=2)
old_population = copy.deepcopy(islands.propulator.population)
del islands
@@ -152,102 +141,28 @@ def test_checkpointing_isolated(
@pytest.mark.mpi(min_size=4)
-def test_checkpointing_migration(
- function_parameters: Tuple[str, float], mpi_tmp_path: pathlib.Path
-) -> None:
- """
- Test island checkpointing with migration (only run in parallel with at least four processes).
-
- Parameters
- ----------
- function_parameters : Tuple[str, float]
- The tuple containing each function name along with its global minimum.
- mpi_tmp_path : pathlib.Path
- The temporary checkpoint directory.
- """
- rng = random.Random(
- 42 + MPI.COMM_WORLD.rank
- ) # Separate random number generator for optimization
- function, limits = get_function_search_space(function_parameters[0])
- set_logger_config(log_file=mpi_tmp_path / "log.log")
-
- # Set up evolutionary operator.
- propagator = get_default_propagator(
- pop_size=4,
- limits=limits,
- rng=rng,
- )
-
- # Set up island model.
- islands = Islands(
- loss_fn=function,
- propagator=propagator,
- rng=rng,
- generations=100,
- num_islands=2,
- migration_probability=0.9,
- pollination=False, # TODO fixtureize
- checkpoint_path=mpi_tmp_path,
- )
-
- # Run actual optimization.
- islands.evolve(
- top_n=1,
- logging_interval=10,
- debug=2,
- )
-
- old_population = copy.deepcopy(islands.propulator.population)
- del islands
-
- islands = Islands(
- loss_fn=function,
- propagator=propagator,
- rng=rng,
- generations=100,
- num_islands=2,
- migration_probability=0.9,
- pollination=False, # TODO fixtureize
- checkpoint_path=mpi_tmp_path,
- )
-
- assert (
- len(
- deepdiff.DeepDiff(
- old_population, islands.propulator.population, ignore_order=True
- )
- )
- == 0
- )
-
-
-@pytest.mark.mpi(min_size=4)
-def test_checkpointing_pollination(
- function_parameters: Tuple[str, float], mpi_tmp_path: pathlib.Path
+def test_checkpointing(
+ global_variables: Tuple[
+ random.Random, Callable, Dict[str, Tuple[float, float]], Propagator
+ ],
+ pollination: bool,
+ mpi_tmp_path: pathlib.Path,
) -> None:
"""
- Test island checkpointing with pollination (only run in parallel with at least four processes).
+ Test island checkpointing with migration and pollination (only run in parallel with at least four processes).
Parameters
----------
- function_parameters : Tuple[str, float]
- The tuple containing each function name along with its global minimum.
+ global_variables : Tuple[random.Random, Callable, Dict[str, Tuple[float, float]], propulate.Propagator]
+ Global variables used by most of the tests in this module.
+ pollination : bool
+ Whether pollination or real migration should be used.
mpi_tmp_path : pathlib.Path
The temporary checkpoint directory.
"""
- rng = random.Random(
- 42 + MPI.COMM_WORLD.rank
- ) # Separate random number generator for optimization
- function, limits = get_function_search_space(function_parameters[0])
+ rng, function, limits, propagator = global_variables
set_logger_config(log_file=mpi_tmp_path / "log.log")
- # Set up evolutionary operator.
- propagator = get_default_propagator(
- pop_size=4,
- limits=limits,
- rng=rng,
- )
-
# Set up island model.
islands = Islands(
loss_fn=function,
@@ -256,14 +171,13 @@ def test_checkpointing_pollination(
generations=100,
num_islands=2,
migration_probability=0.9,
- pollination=False, # TODO fixtureize
+ pollination=pollination,
checkpoint_path=mpi_tmp_path,
)
# Run actual optimization.
islands.evolve(
top_n=1,
- logging_interval=10,
debug=2,
)
@@ -277,7 +191,7 @@ def test_checkpointing_pollination(
generations=100,
num_islands=2,
migration_probability=0.9,
- pollination=True, # TODO fixtureize
+ pollination=pollination,
checkpoint_path=mpi_tmp_path,
)
@@ -293,31 +207,27 @@ def test_checkpointing_pollination(
@pytest.mark.mpi(min_size=8)
def test_checkpointing_unequal_populations(
- function_parameters: Tuple[str, float], mpi_tmp_path: pathlib.Path
+ global_variables: Tuple[
+ random.Random, Callable, Dict[str, Tuple[float, float]], Propagator
+ ],
+ pollination: bool,
+ mpi_tmp_path: pathlib.Path,
) -> None:
"""
Test island checkpointing for inhomogeneous island sizes (only run in parallel with at least eight processes).
Parameters
----------
- function_parameters : Tuple[str, float]
- The tuple containing each function name along with its global minimum.
+ global_variables : Tuple[random.Random, Callable, Dict[str, Tuple[float, float]], propulate.Propagator]
+ Global variables used by most of the tests in this module.
+ pollination : bool
+ Whether pollination or real migration should be used.
mpi_tmp_path : pathlib.Path
The temporary checkpoint directory.
"""
- rng = random.Random(
- 42 + MPI.COMM_WORLD.rank
- ) # Separate random number generator for optimization
- function, limits = get_function_search_space(function_parameters[0])
+ rng, function, limits, propagator = global_variables
set_logger_config(log_file=mpi_tmp_path / "log.log")
- # Set up evolutionary operator.
- propagator = get_default_propagator(
- pop_size=4,
- limits=limits,
- rng=rng,
- )
-
# Set up island model.
islands = Islands(
loss_fn=function,
@@ -327,14 +237,13 @@ def test_checkpointing_unequal_populations(
num_islands=2,
island_sizes=np.array([3, 5]),
migration_probability=0.9,
- pollination=False, # TODO fixtureize
+ pollination=pollination,
checkpoint_path=mpi_tmp_path,
)
# Run actual optimization.
islands.evolve(
top_n=1,
- logging_interval=10,
debug=2,
)
@@ -349,7 +258,7 @@ def test_checkpointing_unequal_populations(
num_islands=2,
island_sizes=np.array([3, 5]),
migration_probability=0.9,
- pollination=True, # TODO fixtureize
+ pollination=pollination,
checkpoint_path=mpi_tmp_path,
)
diff --git a/tests/test_propulator.py b/tests/test_propulator.py
index caff7e50..3b04db3e 100644
--- a/tests/test_propulator.py
+++ b/tests/test_propulator.py
@@ -1,7 +1,6 @@
import copy
import pathlib
import random
-from typing import Tuple
import deepdiff
import pytest
@@ -14,30 +13,28 @@
@pytest.fixture(
params=[
- ("rosenbrock", 0.0),
- ("step", -25.0),
- ("quartic", 0.0),
- ("rastrigin", 0.0),
- ("griewank", 0.0),
- ("schwefel", 0.0),
- ("bisphere", 0.0),
- ("birastrigin", 0.0),
- ("bukin", 0.0),
- ("eggcrate", -1.0),
- ("himmelblau", 0.0),
- ("keane", 0.6736675),
- ("leon", 0.0),
- ("sphere", 0.0), # (fname, expected)
+ "rosenbrock",
+ "step",
+ "quartic",
+ "rastrigin",
+ "griewank",
+ "schwefel",
+ "bisphere",
+ "birastrigin",
+ "bukin",
+ "eggcrate",
+ "himmelblau",
+ "keane",
+ "leon",
+ "sphere",
]
)
-def function_parameters(request):
+def function_name(request):
"""Define benchmark function parameter sets as used in tests."""
return request.param
-def test_propulator(
- function_parameters: Tuple[str, float], mpi_tmp_path: pathlib.Path
-) -> None:
+def test_propulator(function_name: str, mpi_tmp_path: pathlib.Path) -> None:
"""
Test standard Propulator to optimize the benchmark functions using the default genetic propagator.
@@ -45,15 +42,15 @@ def test_propulator(
Parameters
----------
- function_parameters : Tuple[str, float]
- The tuple containing each function name along with its global minimum.
+ function_name : str
+ The function name.
mpi_tmp_path : pathlib.Path
The temporary checkpoint directory.
"""
rng = random.Random(
42 + MPI.COMM_WORLD.rank
) # Random number generator for optimization
- function, limits = get_function_search_space(function_parameters[0])
+ function, limits = get_function_search_space(function_name)
set_logger_config(log_file=mpi_tmp_path / "log.log")
propagator = get_default_propagator(
pop_size=4,
@@ -94,7 +91,7 @@ def test_propulator_checkpointing(mpi_tmp_path: pathlib.Path) -> None:
propulator = Propulator(
loss_fn=function,
propagator=propagator,
- generations=1000,
+ generations=100,
checkpoint_path=mpi_tmp_path,
rng=rng,
) # Set up propulator performing actual optimization.
diff --git a/tests/test_pso.py b/tests/test_pso.py
index e3049a76..24ca5690 100644
--- a/tests/test_pso.py
+++ b/tests/test_pso.py
@@ -1,68 +1,80 @@
import pathlib
import random
-from typing import Tuple
import pytest
from mpi4py import MPI
from propulate import Propulator
from propulate.propagators import Conditional
-from propulate.propagators.pso import BasicPSO, InitUniformPSO
-from propulate.utils.benchmark_functions import get_function_search_space
+from propulate.propagators.pso import (
+ BasicPSO,
+ CanonicalPSO,
+ ConstrictionPSO,
+ InitUniformPSO,
+ VelocityClampingPSO,
+)
+from propulate.utils.benchmark_functions import get_function_search_space, sphere
+
+limits = get_function_search_space("sphere")[1]
+rank = MPI.COMM_WORLD.rank
+rng = random.Random(42 + rank)
@pytest.fixture(
params=[
- ("rosenbrock", 0.0),
- ("step", -25.0),
- ("quartic", 0.0),
- ("rastrigin", 0.0),
- ("griewank", 0.0),
- ("schwefel", 0.0),
- ("bisphere", 0.0),
- ("birastrigin", 0.0),
- ("bukin", 0.0),
- ("eggcrate", -1.0),
- ("himmelblau", 0.0),
- ("keane", 0.6736675),
- ("leon", 0.0),
- ("sphere", 0.0), # (fname, expected)
+ BasicPSO(
+ inertia=0.729,
+ c_cognitive=1.49445,
+ c_social=1.49445,
+ rank=rank,
+ limits=limits,
+ rng=rng,
+ ),
+ VelocityClampingPSO(
+ inertia=0.729,
+ c_cognitive=1.49445,
+ c_social=1.49445,
+ rank=rank,
+ limits=limits,
+ rng=rng,
+ v_limits=0.6,
+ ),
+ ConstrictionPSO(
+ c_cognitive=2.05,
+ c_social=2.05,
+ rank=rank,
+ limits=limits,
+ rng=rng,
+ ),
+ CanonicalPSO(
+ c_cognitive=2.05, c_social=2.05, rank=rank, limits=limits, rng=rng
+ ),
]
)
-def function_parameters(request):
- """Define benchmark function parameter sets as used in tests."""
+def pso_propagator(request):
+ """Iterate over PSO propagator variants."""
return request.param
@pytest.mark.mpi
-def test_pso(function_parameters: Tuple[str, float, float], mpi_tmp_path: pathlib.Path):
+def test_pso(pso_propagator, mpi_tmp_path: pathlib.Path):
"""
Test single worker using Propulator to optimize a benchmark function using the default genetic propagator.
Parameters
----------
- function_parameters : Tuple
- The tuple containing each function name along with its global minimum.
+ pso_propagator : BasicPSO
+ The PSO propagator variant to test.
mpi_tmp_path : pathlib.Path
The temporary checkpoint directory.
"""
- rng = random.Random(42) # Separate random number generator for optimization.
- function, limits = get_function_search_space(function_parameters[0])
# Set up evolutionary operator.
- pso_propagator = BasicPSO(
- inertia=0.729,
- c_cognitive=1.49334,
- c_social=1.49445,
- rank=MPI.COMM_WORLD.rank, # MPI rank
- limits=limits,
- rng=rng,
- )
- init = InitUniformPSO(limits, rng=rng, rank=MPI.COMM_WORLD.rank)
+ init = InitUniformPSO(limits, rng=rng, rank=rank)
propagator = Conditional(1, pso_propagator, init)
# Set up propulator performing actual optimization.
propulator = Propulator(
- loss_fn=function,
+ loss_fn=sphere,
propagator=propagator,
rng=rng,
generations=100,
diff --git a/tests/test_surrogate.py b/tests/test_surrogate.py
index 8a08b6a9..59bec367 100644
--- a/tests/test_surrogate.py
+++ b/tests/test_surrogate.py
@@ -144,6 +144,8 @@ def get_data_loaders(batch_size: int, root=Path) -> Tuple[DataLoader, DataLoader
----------
batch_size : int
The batch size.
+ root : Path
+ The root path.
Returns
-------
@@ -161,7 +163,7 @@ def get_data_loaders(batch_size: int, root=Path) -> Tuple[DataLoader, DataLoader
shuffle=False,
)
- if MPI.COMM_WORLD.Get_rank() == 0: # Only root downloads data.
+ if MPI.COMM_WORLD.rank == 0: # Only root downloads data.
train_loader = DataLoader(
dataset=MNIST(
download=True, root=root, transform=data_transform, train=True
@@ -175,7 +177,7 @@ def get_data_loaders(batch_size: int, root=Path) -> Tuple[DataLoader, DataLoader
setattr(get_data_loaders, "barrier_called", True)
- if MPI.COMM_WORLD.Get_rank() != 0:
+ if MPI.COMM_WORLD.rank != 0:
train_loader = DataLoader(
dataset=MNIST(
download=False, root=root, transform=data_transform, train=True
@@ -203,6 +205,8 @@ def ind_loss(
----------
params : Dict[str, int | float | str]
The parameters to be optimized.
+ root : Path
+ The root path.
Returns
-------
@@ -346,6 +350,10 @@ def test_mnist_static(mpi_tmp_path):
delattr(get_data_loaders, "barrier_called")
+@pytest.mark.filterwarnings(
+ "ignore::DeprecationWarning",
+ match="Assigning the 'data' attribute is an inherently unsafe operation and will be removed in the future.",
+)
@pytest.mark.mpi(min_size=4)
def test_mnist_dynamic(mpi_tmp_path):
"""Test static surrogate using a torch convolutional network on the MNIST dataset."""