Skip to content

Evaluate solutions

The lifecycle of metaheuristics often requires evaluating a list of solutions on every iteration. In evolutionary algorithms, for example, this list of solutions is known as population.

In order to evaluate a population, NSGA-II (and in general, any generational algorithm in jMetalPy) uses an evaluator object.

Sequential

The default evaluator runs in a sequential fashion (i.e., one solution at a time):

from jmetal.util.evaluator import SequentialEvaluator

algorithm = NSGAII(
   problem=problem,
   population_size=100,
   offspring_population_size=100,
   ...
   population_evaluator = SequentialEvaluator(),
 )

API

SequentialEvaluator

Bases: Evaluator[S]

Cached evaluations

EvaluatorWithCache wraps any evaluator and avoids evaluating the same decision variables more than once. It is useful when each evaluation calls an expensive simulator. Cache misses are passed to the wrapped evaluator, so the same wrapper works with sequential and parallel evaluators:

from jmetal.util.evaluator import EvaluatorWithCache, MultiprocessEvaluator

evaluator = EvaluatorWithCache(MultiprocessEvaluator(processes=8))

algorithm = NSGAII(
   problem=problem,
   population_size=100,
   offspring_population_size=100,
   ...
   population_evaluator=evaluator,
)

The wrapper restores both objective and constraint values for cached solutions. The algorithm's evaluation counter still advances by the population size, including cache hits, so use a time-based termination criterion when comparing the simulator work saved by caching.

API

EvaluatorWithCache(base_evaluator)

Bases: Evaluator[S]

Cache evaluations while delegating cache misses to another evaluator.

The cache key is built from a solution's type and decision variables. Objective and constraint values are copied into the cache, so later mutations of an evaluated solution do not alter cached results. A cache is scoped to one problem instance and is cleared when the evaluator is used with a different problem.

Parameters:

Name Type Description Default
base_evaluator Evaluator[S]

Evaluator used for solutions that are not already cached.

required
Source code in src/jmetal/util/evaluator.py
def __init__(self, base_evaluator: Evaluator[S]) -> None:
    self.base_evaluator = base_evaluator
    self.cache: dict[Hashable, tuple[tuple[float, ...], tuple[float, ...]]] = {}
    self._problem: Problem | None = None

Parallel

Solutions can also be evaluated in parallel, using threads or processes:

from jmetal.util.evaluator import MapEvaluator
from jmetal.util.evaluator import MultiprocessEvaluator

jMetalPy also includes evaluators based on Apache Spark and Dask, useful when a single solution evaluation is itself expensive (e.g. simulation-based problems):

from jmetal.util.evaluator import SparkEvaluator

algorithm = NSGAII(
   problem=problem,
   population_size=100,
   offspring_population_size=100,
   ...
   population_evaluator = SparkEvaluator(processes=8),
 )

Or by means of Dask:

from jmetal.util.evaluator import DaskEvaluator

algorithm = NSGAII(
   problem=problem,
   population_size=100,
   offspring_population_size=100,
   ...
   population_evaluator = DaskEvaluator(),
 )

Warning

SparkEvaluator and DaskEvaluator require pySpark and Dask, respectively (install via pip install "jmetalpy[distributed]"). Both currently run against a local Spark/Dask scheduler (local[n]) — they parallelize evaluation across the cores of one machine, not across a cluster, regardless of the processes argument.

API

MapEvaluator(processes=None)

Bases: Evaluator[S]

Source code in src/jmetal/util/evaluator.py
def __init__(self, processes: int = None):
    self.pool = ThreadPool(processes)

MultiprocessEvaluator(processes=None)

Bases: Evaluator[S]

Source code in src/jmetal/util/evaluator.py
def __init__(self, processes: int = None):
    super().__init__()
    self.pool = Pool(processes)

SparkEvaluator(processes=8)

Bases: Evaluator[S]

Source code in src/jmetal/util/evaluator.py
def __init__(self, processes: int = 8):
    self.spark_conf = SparkConf().setAppName("jmetalpy").setMaster(f"local[{processes}]")
    self.spark_context = SparkContext(conf=self.spark_conf)

    logger = self.spark_context._jvm.org.apache.log4j
    logger.LogManager.getLogger("org").setLevel(logger.Level.WARN)

DaskEvaluator(scheduler='processes', number_of_cores=4)

Bases: Evaluator[S]

Source code in src/jmetal/util/evaluator.py
def __init__(self, scheduler="processes", number_of_cores=4):
    self.scheduler = scheduler
    self.number_of_cores = number_of_cores