Skip to content

Core

The abstract base classes every algorithm, problem, and solution in jMetalPy builds on.

Problem

See Defining Problems for the full jmetal.core.problem reference alongside a worked example of extending Problem.

algorithm

This module defines the core algorithm interfaces for optimization in JMetalPy.

It provides abstract base classes for different types of optimization algorithms, including evolutionary algorithms and particle swarm optimization, with support for both single-objective and multi-objective optimization problems.

AlgorithmProtocol

Bases: Protocol[R]

Structural contract satisfied by any algorithm, classic or component-based.

Defined independently of threading.Thread. Consumers that only need "something that can run and report progress" -- e.g. jmetal.lab.experiment.Job -- can type against this instead of requiring a threading.Thread subclass. Every Algorithm subclass and jmetal.component.algorithm.evolutionary_algorithm.EvolutionaryAlgorithm already satisfy it.

run()

Run the algorithm to completion.

Source code in src/jmetal/core/algorithm.py
def run(self) -> None:
    """Run the algorithm to completion."""
    ...

result()

Return the algorithm's result.

Source code in src/jmetal/core/algorithm.py
def result(self) -> R:
    """Return the algorithm's result."""
    ...

get_name()

Return the algorithm's name.

Source code in src/jmetal/core/algorithm.py
def get_name(self) -> str:
    """Return the algorithm's name."""
    ...

observable_data()

Return the data broadcast to observers on each progress update.

Source code in src/jmetal/core/algorithm.py
def observable_data(self) -> dict:
    """Return the data broadcast to observers on each progress update."""
    ...

Algorithm()

Bases: Generic[S, R], ABC

Abstract base class for all optimization algorithms in JMetalPy.

This class serves as the foundation for implementing various optimization algorithms, implementing the template method pattern through its abstract methods. It does not inherit from threading.Thread: nothing in jMetalPy calls start()/join() on an algorithm -- run() is always called directly -- so that inheritance only added unpicklable internal state (locks, Event objects) that jmetal.lab.experiment.Job had to work around to send algorithms across a process boundary via ProcessPoolExecutor. Use run_in_thread() below if an algorithm genuinely needs to run in the background.

Attributes:

Name Type Description
solutions list[S]

List of solutions found by the algorithm.

evaluations

Number of solution evaluations performed.

start_computing_time

Timestamp when the algorithm started running.

total_computing_time

Total time taken by the algorithm (in seconds).

observable

Observer pattern implementation for monitoring algorithm progress.

Initialize the algorithm with default values.

Source code in src/jmetal/core/algorithm.py
def __init__(self):
    """Initialize the algorithm with default values."""
    self.solutions: list[S] = []
    self.evaluations = 0
    self.start_computing_time = 0
    self.total_computing_time = 0
    self.observable = DefaultObservable()

create_initial_solutions() abstractmethod

Creates the initial list of solutions of a metaheuristic.

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def create_initial_solutions(self) -> list[S]:
    """Creates the initial list of solutions of a metaheuristic."""
    pass

evaluate(solution_list) abstractmethod

Evaluates a solution list.

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def evaluate(self, solution_list: list[S]) -> list[S]:
    """Evaluates a solution list."""
    pass

init_progress() abstractmethod

Initialize the algorithm.

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def init_progress(self) -> None:
    """Initialize the algorithm."""
    pass

stopping_condition_is_met() abstractmethod

The stopping condition is met or not.

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def stopping_condition_is_met(self) -> bool:
    """The stopping condition is met or not."""
    pass

step() abstractmethod

Performs one iteration/step of the algorithm's loop.

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def step(self) -> None:
    """Performs one iteration/step of the algorithm's loop."""
    pass

update_progress() abstractmethod

Update the progress after each iteration.

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def update_progress(self) -> None:
    """Update the progress after each iteration."""
    pass

observable_data() abstractmethod

Get observable data, with the information that will be seng to all observers each time.

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def observable_data(self) -> dict:
    """Get observable data, with the information that will be seng to all observers each time."""
    pass

run()

Execute the algorithm.

Source code in src/jmetal/core/algorithm.py
def run(self):
    """Execute the algorithm."""
    self.start_computing_time = time.time()

    logger.debug("Creating initial set of solutions...")
    self.solutions = self.create_initial_solutions()

    logger.debug("Evaluating solutions...")
    self.solutions = self.evaluate(self.solutions)

    logger.debug("Initializing progress...")
    self.init_progress()

    logger.debug("Running main loop until termination criteria is met")
    while not self.stopping_condition_is_met():
        self.step()
        self.update_progress()

    logger.debug("Finished!")

    self.total_computing_time = time.time() - self.start_computing_time

DynamicAlgorithm()

Bases: Algorithm[S, R], ABC

Abstract base class for algorithms that can handle dynamic optimization problems.

Dynamic optimization problems are those where the fitness function, constraints, or other problem characteristics may change over time. This class extends the base Algorithm with methods to handle such changes.

Subclasses must implement the restart method to define how the algorithm should respond to changes in the problem definition.

Source code in src/jmetal/core/algorithm.py
def __init__(self):
    """Initialize the algorithm with default values."""
    self.solutions: list[S] = []
    self.evaluations = 0
    self.start_computing_time = 0
    self.total_computing_time = 0
    self.observable = DefaultObservable()

restart() abstractmethod

Restart the algorithm in response to changes in the problem.

This method is called when a change in the problem is detected. Implementations should reset or adapt the algorithm's state to handle the new problem conditions.

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def restart(self) -> None:
    """Restart the algorithm in response to changes in the problem.

    This method is called when a change in the problem is detected. Implementations
    should reset or adapt the algorithm's state to handle the new problem conditions.
    """
    pass

EvolutionaryAlgorithm(problem, population_size, offspring_population_size)

Bases: Algorithm[S, R], ABC

Abstract base class for evolutionary algorithms.

This class implements the core structure of an evolutionary algorithm, including the evolutionary cycle of selection, reproduction, and replacement. Subclasses must implement the specific selection, reproduction, and replacement strategies.

Attributes:

Name Type Description
problem

The optimization problem to solve.

population_size

Number of solutions in the population.

offspring_population_size

Number of offspring solutions generated each generation.

Initialize the evolutionary algorithm.

Parameters:

Name Type Description Default
problem Problem[S]

The optimization problem to solve.

required
population_size int

Number of solutions in the population.

required
offspring_population_size int

Number of offspring solutions to generate each generation.

required
Source code in src/jmetal/core/algorithm.py
def __init__(self, problem: Problem[S], population_size: int, offspring_population_size: int):
    """Initialize the evolutionary algorithm.

    Args:
        problem: The optimization problem to solve.
        population_size: Number of solutions in the population.
        offspring_population_size: Number of offspring solutions to generate each generation.
    """
    super().__init__()
    self.problem = problem
    self.population_size = population_size
    self.offspring_population_size = offspring_population_size

selection(population) abstractmethod

Select the best-fit individuals for reproduction (parents).

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def selection(self, population: list[S]) -> list[S]:
    """Select the best-fit individuals for reproduction (parents)."""
    pass

reproduction(population) abstractmethod

Breed new individuals through crossover and mutation operations to give birth to offspring.

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def reproduction(self, population: list[S]) -> list[S]:
    """Breed new individuals through crossover and mutation operations to give birth to offspring."""
    pass

replacement(population, offspring_population) abstractmethod

Replace least-fit population with new individuals.

Source code in src/jmetal/core/algorithm.py
@abstractmethod
def replacement(self, population: list[S], offspring_population: list[S]) -> list[S]:
    """Replace least-fit population with new individuals."""
    pass

ParticleSwarmOptimization(problem, swarm_size)

Bases: Algorithm[FloatSolution, list[FloatSolution]], ABC

Abstract base class for Particle Swarm Optimization (PSO) algorithms.

This class implements the core structure of a PSO algorithm, where a population of candidate solutions (particles) move through the search space according to simple mathematical formulae over the particle's position and velocity.

Attributes:

Name Type Description
problem

The optimization problem to solve.

swarm_size

Number of particles in the swarm.

Initialize the PSO algorithm.

Parameters:

Name Type Description Default
problem Problem[S]

The optimization problem to solve.

required
swarm_size int

Number of particles in the swarm.

required
Source code in src/jmetal/core/algorithm.py
def __init__(self, problem: Problem[S], swarm_size: int):
    """Initialize the PSO algorithm.

    Args:
        problem: The optimization problem to solve.
        swarm_size: Number of particles in the swarm.
    """
    super().__init__()
    self.problem = problem
    self.swarm_size = swarm_size

thread_rng_into_operators(rng, *operators)

Best-effort propagation of an algorithm's rng into its operators.

Mirrors jmetal.component.algorithm.evolutionary_algorithm.EvolutionaryAlgorithm. _thread_rng_into_components for the classic algorithm hierarchy. Does nothing when rng is None, so a classic algorithm built without an explicit rng keeps its exact prior behavior (operators fall back to their own defaults, population creation keeps consuming the global random/numpy.random state). When rng is given, it is only assigned to operators exposing a rng/_rng attribute that is still None -- an operator the caller already seeded explicitly is left untouched.

Source code in src/jmetal/core/algorithm.py
def thread_rng_into_operators(rng: np.random.Generator | None, *operators) -> None:
    """Best-effort propagation of an algorithm's `rng` into its operators.

    Mirrors `jmetal.component.algorithm.evolutionary_algorithm.EvolutionaryAlgorithm.
    _thread_rng_into_components` for the classic algorithm hierarchy. Does nothing when
    `rng` is None, so a classic algorithm built without an explicit `rng` keeps its exact
    prior behavior (operators fall back to their own defaults, population creation keeps
    consuming the global `random`/`numpy.random` state). When `rng` is given, it is only
    assigned to operators exposing a `rng`/`_rng` attribute that is still `None` -- an
    operator the caller already seeded explicitly is left untouched.
    """
    if rng is None:
        return
    for operator in operators:
        if operator is None:
            continue
        if hasattr(operator, "rng") and operator.rng is None:
            operator.rng = rng
        elif hasattr(operator, "_rng") and operator._rng is None:
            operator._rng = rng

run_in_thread(algorithm)

Run an algorithm in a background thread.

Algorithm no longer inherits from threading.Thread, so algorithm.run() must be called directly or, if background execution is genuinely needed -- e.g. a live-plotting loop driven from the main thread while the algorithm keeps running -- via this explicit helper instead.

Parameters:

Name Type Description Default
algorithm AlgorithmProtocol

Any object satisfying AlgorithmProtocol.

required

Returns:

Type Description
Thread

The threading.Thread running algorithm.run(), already started.

Source code in src/jmetal/core/algorithm.py
def run_in_thread(algorithm: AlgorithmProtocol) -> threading.Thread:
    """Run an algorithm in a background thread.

    Algorithm no longer inherits from `threading.Thread`, so `algorithm.run()` must
    be called directly or, if background execution is genuinely needed -- e.g. a
    live-plotting loop driven from the main thread while the algorithm keeps
    running -- via this explicit helper instead.

    Args:
        algorithm: Any object satisfying `AlgorithmProtocol`.

    Returns:
        The `threading.Thread` running `algorithm.run()`, already started.
    """
    thread = threading.Thread(target=algorithm.run)
    thread.start()

    return thread

solution

This module defines the core solution representations used in evolutionary computation. It provides abstract and concrete implementations of solutions for different types of optimization problems.

Solution(number_of_variables, number_of_objectives, number_of_constraints=0)

Bases: Generic[S], ABC

Abstract base class for all solution representations in the optimization framework.

This class defines the common interface and functionality for all solution types. Subclasses must implement the abstract methods to provide specific variable storage and manipulation mechanisms.

Attributes:

Name Type Description
number_of_variables

Number of decision variables in the solution.

number_of_objectives

Number of objective values to optimize.

number_of_constraints

Number of constraint values (default: 0).

_objectives list[float]

List storing the objective values of the solution.

_constraints list[float]

List storing the constraint values of the solution.

attributes dict[str, Any]

Dictionary for storing additional solution metadata.

Initialize a new solution with the specified dimensions.

Parameters:

Name Type Description Default
number_of_variables int

The number of decision variables.

required
number_of_objectives int

The number of objective values.

required
number_of_constraints int

The number of constraint values (default: 0).

0
Source code in src/jmetal/core/solution.py
def __init__(
    self, number_of_variables: int, number_of_objectives: int, number_of_constraints: int = 0
) -> None:
    """Initialize a new solution with the specified dimensions.

    Args:
        number_of_variables: The number of decision variables.
        number_of_objectives: The number of objective values.
        number_of_constraints: The number of constraint values (default: 0).
    """
    self.number_of_variables = number_of_variables
    self.number_of_objectives = number_of_objectives
    self.number_of_constraints = number_of_constraints

    # Initialize internal storage with default values
    self._objectives: list[float] = [0.0] * number_of_objectives
    self._constraints: list[float] = [0.0] * number_of_constraints
    self.attributes: dict[str, Any] = {}

variables abstractmethod property writable

Return the decision variables as a list.

Must return a list-like object where: - len(variables) == number_of_variables - variables[i] returns the i-th variable of type S

BinarySolution(number_of_variables, number_of_objectives, number_of_constraints=0)

Bases: Solution[bool]

A solution representation for binary-encoded optimization problems.

This class provides an efficient implementation of binary solutions using NumPy arrays for storage and operations. It's particularly suited for problems where solutions are represented as bit strings, such as binary-encoded combinatorial optimization problems.

The implementation uses NumPy's boolean arrays for compact storage and efficient bitwise operations. It maintains both a NumPy array for performance and provides a Python list interface for compatibility.

Attributes:

Name Type Description
_bits

NumPy array storing the binary values (internal representation).

Example

solution = BinarySolution(number_of_variables=10, number_of_objectives=2) solution.variables = [True, False] * 5 # Set variables solution[0] = False # Modify a single bit distance = solution.hamming_distance(other_solution) # Calculate distance

Initialize a binary solution with the given dimensions.

Source code in src/jmetal/core/solution.py
def __init__(
    self, number_of_variables: int, number_of_objectives: int, number_of_constraints: int = 0
) -> None:
    """Initialize a binary solution with the given dimensions."""
    super().__init__(number_of_variables, number_of_objectives, number_of_constraints)
    self._bits = np.zeros(number_of_variables, dtype=bool)

variables property writable

Return the decision variables as a list of booleans.

Returns:

Type Description
list[bool]

A list where each element represents a bit in the solution.

bits property writable

Direct access to the underlying NumPy array for high-performance operations.

Returns:

Type Description
ndarray

A read-only view of the internal bit array.

get_total_number_of_bits()

Get the total number of bits in the solution.

Returns:

Type Description
int

The number of variables (bits) in the solution

Source code in src/jmetal/core/solution.py
def get_total_number_of_bits(self) -> int:
    """Get the total number of bits in the solution.

    Returns:
        The number of variables (bits) in the solution
    """
    return self.number_of_variables

get_binary_string()

Get a binary string representation of the solution.

Returns:

Type Description
str

A string of '0's and '1's representing the solution

Source code in src/jmetal/core/solution.py
def get_binary_string(self) -> str:
    """Get a binary string representation of the solution.

    Returns:
        A string of '0's and '1's representing the solution
    """
    return "".join(np.where(self._bits, "1", "0"))

cardinality()

Count the number of bits set to True.

Also known as the Hamming weight or population count.

Returns:

Type Description
int

The number of bits set to True

Source code in src/jmetal/core/solution.py
def cardinality(self) -> int:
    """Count the number of bits set to True.

    Also known as the Hamming weight or population count.

    Returns:
        The number of bits set to True
    """
    return int(np.sum(self._bits))

flip_bit(index)

Flip the bit at the specified index.

Parameters:

Name Type Description Default
index int

The index of the bit to flip

required

Raises:

Type Description
IndexError

If the index is out of bounds

Source code in src/jmetal/core/solution.py
def flip_bit(self, index: int) -> None:
    """Flip the bit at the specified index.

    Args:
        index: The index of the bit to flip

    Raises:
        IndexError: If the index is out of bounds
    """
    self._bits[index] ^= True

hamming_distance(other)

Calculate the Hamming distance to another binary solution.

The Hamming distance is the number of bit positions at which the corresponding bits are different.

Parameters:

Name Type Description Default
other BinarySolution

Another BinarySolution to compare with

required

Returns:

Type Description
int

The number of differing bits

Raises:

Type Description
TypeError

If other is not a BinarySolution

ValueError

If solutions have different lengths

Source code in src/jmetal/core/solution.py
def hamming_distance(self, other: BinarySolution) -> int:
    """Calculate the Hamming distance to another binary solution.

    The Hamming distance is the number of bit positions at which the
    corresponding bits are different.

    Args:
        other: Another BinarySolution to compare with

    Returns:
        The number of differing bits

    Raises:
        TypeError: If other is not a BinarySolution
        ValueError: If solutions have different lengths
    """
    if not isinstance(other, BinarySolution):
        raise TypeError(
            f"Cannot compute Hamming distance between "
            f"{self.__class__.__name__} and {type(other).__name__}"
        )
    if self.number_of_variables != other.number_of_variables:
        raise ValueError(
            f"Solutions must have the same number of variables: "
            f"{self.number_of_variables} != {other.number_of_variables}"
        )
    return int(np.sum(self._bits != other._bits))

FloatSolution(lower_bound, upper_bound, number_of_objectives, number_of_constraints=0)

Bases: Solution[float]

A solution representation for continuous optimization problems with float variables.

This class implements a solution where each decision variable is a floating-point value constrained by lower and upper bounds. It's suitable for continuous optimization problems where variables can take any real value within specified ranges.

The solution maintains the following properties: - Each variable has independent lower and upper bounds - Variables are stored as a list of floats - Bounds checking is performed when variables are set

Attributes:

Name Type Description
lower_bound

List of lower bounds for each variable.

upper_bound

List of upper bounds for each variable.

_variables

Internal storage for the decision variables.

Source code in src/jmetal/core/solution.py
def __init__(
    self,
    lower_bound: list[float],
    upper_bound: list[float],
    number_of_objectives: int,
    number_of_constraints: int = 0,
) -> None:
    super().__init__(len(lower_bound), number_of_objectives, number_of_constraints)
    if len(lower_bound) != len(upper_bound):
        raise ValueError("lower_bound and upper_bound must have the same length")

    self.lower_bound = lower_bound.copy()
    self.upper_bound = upper_bound.copy()
    self._variables = [0.0] * self.number_of_variables

variables property writable

Get the decision variables as a list of floats.

Returns:

Type Description
list[float]

A list of float values representing the solution's variables.

IntegerSolution(lower_bound, upper_bound, number_of_objectives, number_of_constraints=0)

Bases: Solution[int]

A solution representation for integer-constrained optimization problems.

This class is designed for optimization problems where decision variables must take integer values within specified bounds. It's suitable for: - Pure integer programming problems - Mixed-integer problems (when used with other solution types) - Combinatorial optimization with integer-encoded solutions

The implementation ensures that all variables remain within their specified bounds and are stored as integers. Bounds checking is performed when variables are modified.

Attributes:

Name Type Description
lower_bound

List of lower bounds for each variable (inclusive).

upper_bound

List of upper bounds for each variable (inclusive).

_variables

Internal storage for the integer decision variables.

Source code in src/jmetal/core/solution.py
def __init__(
    self,
    lower_bound: list[int],
    upper_bound: list[int],
    number_of_objectives: int,
    number_of_constraints: int = 0,
) -> None:
    super().__init__(len(lower_bound), number_of_objectives, number_of_constraints)
    if len(lower_bound) != len(upper_bound):
        raise ValueError("lower_bound and upper_bound must have the same length")

    self.lower_bound = lower_bound.copy()
    self.upper_bound = upper_bound.copy()
    self._variables = [0] * self.number_of_variables

variables property writable

Get the decision variables as a list of integers.

Returns:

Type Description
list[int]

A list of integer values representing the solution's variables.

CompositeSolution(solutions)

Bases: Solution[Solution]

A solution composed of multiple heterogeneous solution types.

This class enables the creation of complex solutions by combining multiple solution objects of different types (e.g., binary, integer, float) into a single composite solution. This is particularly useful for: - Multi-encoding optimization problems - Decomposition-based optimization approaches - Problems with mixed variable types

All constituent solutions must have the same number of objectives and constraints to maintain consistency in the optimization process.

Example

Create a composite solution with binary and float parts

binary_part = BinarySolution(10, 2) float_part = FloatSolution([0.0]5, [1.0]5, 2) composite = CompositeSolution([binary_part, float_part])

Attributes:

Name Type Description
_solutions

List of solution objects that compose this composite solution.

Initialize a composite solution.

Parameters:

Name Type Description Default
solutions list[Solution]

List of Solution objects to compose this solution from.

required

Raises:

Type Description
ValueError

If solutions is empty or solutions have inconsistent numbers of objectives or constraints.

Source code in src/jmetal/core/solution.py
def __init__(self, solutions: list[Solution]) -> None:
    """Initialize a composite solution.

    Args:
        solutions: List of Solution objects to compose this solution from.

    Raises:
        ValueError: If solutions is empty or solutions have inconsistent
                   numbers of objectives or constraints.
    """
    Check.is_not_none(solutions)
    Check.collection_is_not_empty(solutions)

    # Validate all solutions have same number of objectives and constraints
    first = solutions[0]
    for solution in solutions[1:]:
        if len(solution.objectives) != len(first.objectives):
            raise ValueError(
                f"All solutions must have the same number of objectives. "
                f"Found {len(first.objectives)} and {len(solution.objectives)}"
            )
        if len(solution.constraints) != len(first.constraints):
            raise ValueError(
                f"All solutions must have the same number of constraints. "
                f"Found {len(first.constraints)} and {len(solution.constraints)}"
            )

    super().__init__(
        number_of_variables=len(solutions),
        number_of_objectives=len(first.objectives),
        number_of_constraints=len(first.constraints),
    )

    # Make defensive copies of all solutions
    self._solutions = [copy.copy(sol) for sol in solutions]

variables property writable

Get the list of solutions that compose this composite solution.

Returns:

Type Description
list[Solution]

A list of Solution objects.

PermutationSolution(number_of_variables, number_of_objectives, number_of_constraints=0)

Bases: Solution[int]

A solution representation for permutation-based optimization problems.

This class is designed for problems where solutions are represented as permutations of integers, such as: - Traveling Salesman Problem (TSP) - Job Shop Scheduling - Quadratic Assignment Problem (QAP) - Any problem where the order of elements matters

The solution maintains a permutation of integers from 0 to n-1, where n is the number of variables. The implementation ensures that the permutation remains valid (no duplicates, all numbers in range) at all times.

Attributes:

Name Type Description
_variables

List storing the permutation of integers.

Example

Create a permutation solution for a 5-city TSP

solution = PermutationSolution(5, 1) # 5 cities, 1 objective

The initial permutation is [0, 1, 2, 3, 4]

solution.variables = [4, 2, 0, 1, 3] # Set a specific tour

Initialize a permutation solution.

Parameters:

Name Type Description Default
number_of_variables int

Length of the permutation.

required
number_of_objectives int

Number of objective values.

required
number_of_constraints int

Number of constraint values (default: 0).

0
Source code in src/jmetal/core/solution.py
def __init__(
    self, number_of_variables: int, number_of_objectives: int, number_of_constraints: int = 0
) -> None:
    """Initialize a permutation solution.

    Args:
        number_of_variables: Length of the permutation.
        number_of_objectives: Number of objective values.
        number_of_constraints: Number of constraint values (default: 0).
    """
    super().__init__(number_of_variables, number_of_objectives, number_of_constraints)
    # Initialize with identity permutation
    self._variables = list(range(number_of_variables))

variables property writable

Get the permutation as a list of integers.

Returns:

Type Description
list[int]

A list representing the current permutation.

quality_indicator

This module provides quality indicators for evaluating multi-objective optimization results.

Quality indicators are essential for comparing and assessing the performance of multi-objective optimization algorithms. This module includes various indicators such as Generational Distance (GD), Inverted Generational Distance (IGD), and Hypervolume (HV).

EpsilonIndicator = AdditiveEpsilonIndicator module-attribute

Legacy alias for AdditiveEpsilonIndicator.

This alias is maintained for backward compatibility. New code should use AdditiveEpsilonIndicator directly.

QualityIndicator(is_minimization)

Bases: ABC

Abstract base class for all quality indicators.

Quality indicators are used to assess the performance of multi-objective optimization algorithms by quantifying different aspects of the obtained solution sets, such as convergence, diversity, and spread.

Parameters:

Name Type Description Default
is_minimization bool

If True, lower indicator values indicate better quality. If False, higher values are better.

required

Initialize the quality indicator with optimization direction.

Source code in src/jmetal/core/quality_indicator.py
def __init__(self, is_minimization: bool):
    """Initialize the quality indicator with optimization direction."""
    self.is_minimization = is_minimization

compute(solutions) abstractmethod

Compute the quality indicator value for the given solutions.

Parameters:

Name Type Description Default
solutions ndarray

A 2D numpy array of shape (m, n) where m is the number of solutions and n is the number of objectives.

required

Returns:

Type Description
float

The computed quality indicator value.

Raises:

Type Description
ValueError

If the input is invalid (e.g., empty array, wrong dimensions).

Source code in src/jmetal/core/quality_indicator.py
@abstractmethod
def compute(self, solutions: np.ndarray) -> float:
    """Compute the quality indicator value for the given solutions.

    Args:
        solutions: A 2D numpy array of shape (m, n) where m is the number of
                 solutions and n is the number of objectives.

    Returns:
        The computed quality indicator value.

    Raises:
        ValueError: If the input is invalid (e.g., empty array, wrong dimensions).
    """
    pass

get_name() abstractmethod

Get the full name of the quality indicator.

Returns:

Type Description
str

A string representing the full name of the indicator.

Source code in src/jmetal/core/quality_indicator.py
@abstractmethod
def get_name(self) -> str:
    """Get the full name of the quality indicator.

    Returns:
        A string representing the full name of the indicator.
    """
    pass

get_short_name() abstractmethod

Get a short name or abbreviation for the quality indicator.

Returns:

Type Description
str

A short string abbreviation for the indicator (e.g., 'GD', 'IGD', 'HV').

Source code in src/jmetal/core/quality_indicator.py
@abstractmethod
def get_short_name(self) -> str:
    """Get a short name or abbreviation for the quality indicator.

    Returns:
        A short string abbreviation for the indicator (e.g., 'GD', 'IGD', 'HV').
    """
    pass

FitnessValue(is_minimization=True)

Bases: QualityIndicator

A simple fitness-based quality indicator.

This indicator computes the average objective value of the solutions, which is useful for single-objective optimization or when a scalarization of multiple objectives is needed.

Note

For multi-objective optimization, this indicator may not provide meaningful comparisons between solution sets.

Initialize the fitness value indicator.

Parameters:

Name Type Description Default
is_minimization bool

If True, lower fitness values are better.

True
Source code in src/jmetal/core/quality_indicator.py
def __init__(self, is_minimization: bool = True):
    """Initialize the fitness value indicator.

    Args:
        is_minimization: If True, lower fitness values are better.
    """
    super().__init__(is_minimization=is_minimization)

compute(solutions)

Compute the average fitness value of the solutions.

Parameters:

Name Type Description Default
solutions ndarray

Array of solution objects with 'objectives' attribute.

required

Returns:

Type Description
float

The mean of the objective values, with sign adjusted based on

float

the optimization direction.

Source code in src/jmetal/core/quality_indicator.py
def compute(self, solutions: np.ndarray) -> float:
    """Compute the average fitness value of the solutions.

    Args:
        solutions: Array of solution objects with 'objectives' attribute.

    Returns:
        The mean of the objective values, with sign adjusted based on
        the optimization direction.
    """
    if self.is_minimization:
        mean = np.mean([s.objectives for s in solutions])
    else:
        mean = -np.mean([s.objectives for s in solutions])

    return mean

GenerationalDistance(reference_front=None)

Bases: QualityIndicator

Generational Distance (GD) quality indicator.

GD measures the average distance from each solution in the obtained front to the nearest solution in the reference front. Lower values indicate better convergence to the reference front.

Note
  • GD = 0 indicates that all solutions are in the reference front.
  • Lower values indicate better convergence.
Reference

Van Veldhuizen, D.A., Lamont, G.B. (1998): Multiobjective Evolutionary Algorithm Research: A History and Analysis. Technical Report TR-98-03, Dept. Elec. Comput. Eng., Air Force Inst. Technol.

Initialize the Generational Distance indicator.

Parameters:

Name Type Description Default
reference_front ndarray

The reference front (Pareto front or approximation). Each row represents a solution in the objective space.

None
Source code in src/jmetal/core/quality_indicator.py
def __init__(self, reference_front: np.ndarray = None):
    """Initialize the Generational Distance indicator.

    Args:
        reference_front: The reference front (Pareto front or approximation).
                       Each row represents a solution in the objective space.
    """
    super().__init__(is_minimization=True)
    self.reference_front = reference_front

compute(solutions)

Compute the Generational Distance value.

Parameters:

Name Type Description Default
solutions ndarray

A 2D numpy array of shape (m, n) where m is the number of solutions and n is the number of objectives.

required

Returns:

Type Description
float

The Generational Distance value (lower is better).

Raises:

Type Description
ValueError

If the reference front is not set or if the input is invalid.

Source code in src/jmetal/core/quality_indicator.py
def compute(self, solutions: np.ndarray) -> float:
    """Compute the Generational Distance value.

    Args:
        solutions: A 2D numpy array of shape (m, n) where m is the number of
                 solutions and n is the number of objectives.

    Returns:
        The Generational Distance value (lower is better).

    Raises:
        ValueError: If the reference front is not set or if the input is invalid.
    """
    if self.reference_front is None:
        raise ValueError("Reference front must be set before computing GD")
    if solutions.size == 0:
        raise ValueError("Solutions array cannot be empty")

    # Compute pairwise distances between solutions and reference front
    distances = spatial.distance.cdist(solutions, self.reference_front)

    # For each solution, find the minimum distance to the reference front
    min_distances = np.min(distances, axis=1)

    # GD is the average of these minimum distances
    return float(np.mean(min_distances))

get_short_name()

Get the short name of the indicator.

Returns:

Type Description
str

'GD' for Generational Distance.

Source code in src/jmetal/core/quality_indicator.py
def get_short_name(self) -> str:
    """Get the short name of the indicator.

    Returns:
        'GD' for Generational Distance.
    """
    return "GD"

get_name()

Get the full name of the indicator.

Returns:

Type Description
str

'Generational Distance'.

Source code in src/jmetal/core/quality_indicator.py
def get_name(self) -> str:
    """Get the full name of the indicator.

    Returns:
        'Generational Distance'.
    """
    return "Generational Distance"

InvertedGenerationalDistance(reference_front=None, pow=2.0)

Bases: QualityIndicator

Inverted Generational Distance (IGD) quality indicator.

IGD measures the average distance from each point in the reference front to the closest point in the solution front. Lower values indicate better performance.

Reference: Van Veldhuizen, D.A., Lamont, G.B. (1998): Multiobjective Evolutionary Algorithm Research: A History and Analysis. Technical Report TR-98-03, Dept. Elec. Comput. Eng., Air Force Inst. Technol.

Initialize the IGD indicator.

Parameters:

Name Type Description Default
reference_front array

Reference front matrix (each row is a solution). May be left as None and set later (e.g. by an Experiment that assigns a different reference front per problem before each compute() call).

None
pow float

Power parameter for the Lp-norm (default: 2.0 for Euclidean distance)

2.0
Source code in src/jmetal/core/quality_indicator.py
def __init__(self, reference_front: np.array = None, pow: float = 2.0):
    """
    Initialize the IGD indicator.

    Args:
        reference_front: Reference front matrix (each row is a solution). May be left
                        as None and set later (e.g. by an Experiment that assigns a
                        different reference front per problem before each compute() call).
        pow: Power parameter for the Lp-norm (default: 2.0 for Euclidean distance)
    """
    super().__init__(is_minimization=True)
    self.reference_front = reference_front
    self.pow = pow

compute(solutions)

Compute the IGD indicator value.

Parameters:

Name Type Description Default
solutions array

Solution front matrix (each row is a solution)

required

Returns:

Type Description
float

The IGD indicator value

Raises:

Type Description
ValueError

If the reference front is not set, or if solutions is empty or has different dimensionality than reference front

Source code in src/jmetal/core/quality_indicator.py
def compute(self, solutions: np.array) -> float:
    """
    Compute the IGD indicator value.

    Args:
        solutions: Solution front matrix (each row is a solution)

    Returns:
        The IGD indicator value

    Raises:
        ValueError: If the reference front is not set, or if solutions is empty or has
                  different dimensionality than reference front
    """
    if self.reference_front is None:
        raise ValueError("Reference front must be set before computing IGD")
    if len(self.reference_front) == 0:
        raise ValueError("Reference front cannot be empty")
    if solutions is None or len(solutions) == 0:
        raise ValueError("Solutions front cannot be None or empty")

    if solutions.shape[1] != self.reference_front.shape[1]:
        raise ValueError(
            "Solutions and reference front must have the same number of objectives"
        )

    # Compute distances from each reference point to closest solution point
    distances = spatial.distance.cdist(self.reference_front, solutions)
    min_distances = np.min(distances, axis=1)

    # Apply jMetal's IGD formula: IGD = (Σ(d^pow))^(1/pow) / N
    # where d is the minimum distance from each reference point to the solution front
    # This implementation matches exactly with jMetal's invertedGenerationalDistance method
    powered_distances = np.power(min_distances, self.pow)
    sum_root = np.power(np.sum(powered_distances), 1.0 / self.pow)
    return sum_root / len(self.reference_front)

InvertedGenerationalDistancePlus(reference_front=None)

Bases: QualityIndicator

Inverted Generational Distance Plus (IGD+) quality indicator.

IGD+ improves upon the standard IGD by using dominance-based distance calculation, making it more suitable for cases where the reference front may not be optimal.

Reference: Ishibuchi et al. (2015): "A Study on Performance Evaluation Ability of a Modified Inverted Generational Distance Indicator", GECCO 2015

Initialize the IGD+ indicator.

Parameters:

Name Type Description Default
reference_front array

Reference front matrix (each row is a solution). May be left as None and set later (e.g. by an Experiment that assigns a different reference front per problem before each compute() call).

None
Source code in src/jmetal/core/quality_indicator.py
def __init__(self, reference_front: np.array = None):
    """
    Initialize the IGD+ indicator.

    Args:
        reference_front: Reference front matrix (each row is a solution). May be left
                        as None and set later (e.g. by an Experiment that assigns a
                        different reference front per problem before each compute() call).
    """
    super().__init__(is_minimization=True)
    self.reference_front = reference_front

compute(solutions)

Compute the IGD+ indicator value.

Delegates the actual computation to moocore.igd_plus for efficiency: the previous implementation was a pure-Python double loop (no numpy vectorization at all), which moocore's C implementation improves on substantially. Kept our own guard clauses in front of it rather than relying on moocore's: moocore.igd_plus returns 0.0 for an empty solutions front and inf for an empty reference front instead of raising, which would silently change this class's documented contract.

Parameters:

Name Type Description Default
solutions array

Solution front matrix (each row is a solution)

required

Returns:

Type Description
float

The IGD+ indicator value

Raises:

Type Description
ValueError

If the reference front is not set, or if solutions is empty or has different dimensionality than reference front

Source code in src/jmetal/core/quality_indicator.py
def compute(self, solutions: np.array) -> float:
    """
    Compute the IGD+ indicator value.

    Delegates the actual computation to `moocore.igd_plus` for efficiency: the
    previous implementation was a pure-Python double loop (no numpy
    vectorization at all), which moocore's C implementation improves on
    substantially. Kept our own guard clauses in front of it rather than
    relying on moocore's: `moocore.igd_plus` returns 0.0 for an empty
    `solutions` front and `inf` for an empty reference front instead of
    raising, which would silently change this class's documented contract.

    Args:
        solutions: Solution front matrix (each row is a solution)

    Returns:
        The IGD+ indicator value

    Raises:
        ValueError: If the reference front is not set, or if solutions is empty or has
                  different dimensionality than reference front
    """
    if self.reference_front is None:
        raise ValueError("Reference front must be set before computing IGD+")
    if len(self.reference_front) == 0:
        raise ValueError("Reference front cannot be empty")
    if solutions is None or len(solutions) == 0:
        raise ValueError("Solutions front cannot be None or empty")

    if solutions.shape[1] != self.reference_front.shape[1]:
        raise ValueError(
            "Solutions and reference front must have the same number of objectives"
        )

    return float(moocore.igd_plus(solutions, ref=self.reference_front))

AverageHausdorffDistance(reference_front=None)

Bases: QualityIndicator

Average Hausdorff Distance (AHD) quality indicator.

AHD measures the average distance between the solution front and the reference front. It is defined as the maximum of GD and IGD.

Reference: Schutze, O., Esquivel, X., Lara, A., & Coello Coello, C. A. (2012). Using the averaged Hausdorff distance as a performance measure in evolutionary multiobjective optimization. IEEE Transactions on Evolutionary Computation, 16(4), 504-522.

Initialize the AHD indicator.

Parameters:

Name Type Description Default
reference_front ndarray

Reference front matrix (each row is a solution). May be left as None and set later (e.g. by an Experiment that assigns a different reference front per problem before each compute() call).

None
Source code in src/jmetal/core/quality_indicator.py
def __init__(self, reference_front: np.ndarray = None):
    """
    Initialize the AHD indicator.

    Args:
        reference_front: Reference front matrix (each row is a solution). May be left
                        as None and set later (e.g. by an Experiment that assigns a
                        different reference front per problem before each compute() call).
    """
    super().__init__(is_minimization=True)
    self.reference_front = reference_front

compute(solutions)

Compute the AHD indicator value.

Parameters:

Name Type Description Default
solutions ndarray

Solution front matrix (each row is a solution)

required

Returns:

Type Description
float

The AHD indicator value

Raises:

Type Description
ValueError

If the reference front is not set, or if solutions is empty or has different dimensionality than reference front

Source code in src/jmetal/core/quality_indicator.py
def compute(self, solutions: np.ndarray) -> float:
    """
    Compute the AHD indicator value.

    Args:
        solutions: Solution front matrix (each row is a solution)

    Returns:
        The AHD indicator value

    Raises:
        ValueError: If the reference front is not set, or if solutions is empty or has
                  different dimensionality than reference front
    """
    if self.reference_front is None:
        raise ValueError("Reference front must be set before computing AHD")
    if len(self.reference_front) == 0:
        raise ValueError("Reference front cannot be empty")
    if solutions is None or len(solutions) == 0:
        raise ValueError("Solutions front cannot be None or empty")

    if solutions.shape[1] != self.reference_front.shape[1]:
        raise ValueError(
            "Solutions and reference front must have the same number of objectives"
        )

    # Compute pairwise distances between solutions and reference front
    distances = spatial.distance.cdist(solutions, self.reference_front)

    # GD: average of minimum distances from solutions to reference front
    # axis=1 finds min distance for each solution to any point in reference front
    min_distances_gd = np.min(distances, axis=1)
    gd = np.mean(min_distances_gd)

    # IGD: average of minimum distances from reference front to solutions
    # axis=0 finds min distance for each reference point to any point in solutions
    min_distances_igd = np.min(distances, axis=0)
    igd = np.mean(min_distances_igd)

    return max(gd, igd)

AdditiveEpsilonIndicator(reference_front=None)

Bases: QualityIndicator

Additive Epsilon (ε) quality indicator.

Computes the additive epsilon indicator between two fronts, following the definition of Zitzler et al. (2003). The returned value is the minimum value ε such that, for each point in the reference front, there exists a point in the solution front shifted by ε that weakly dominates the reference point (assuming minimization).

Reference: E. Zitzler, L. Thiele, M. Laumanns, C.M. Fonseca, V.G. Da Fonseca (2003): Performance Assessment of Multiobjective Optimizers: An Analysis and Review. IEEE Transactions on Evolutionary Computation, 7(2), 117-132.

Initialize the Additive Epsilon indicator.

Parameters:

Name Type Description Default
reference_front array

Reference front matrix (each row is a solution). May be left as None and set later (e.g. by an Experiment that assigns a different reference front per problem before each compute() call).

None
Source code in src/jmetal/core/quality_indicator.py
def __init__(self, reference_front: np.array = None):
    """
    Initialize the Additive Epsilon indicator.

    Args:
        reference_front: Reference front matrix (each row is a solution). May be left
                        as None and set later (e.g. by an Experiment that assigns a
                        different reference front per problem before each compute() call).
    """
    super().__init__(is_minimization=True)
    self.reference_front = reference_front

compute(front)

Compute the additive epsilon indicator value.

Delegates the actual computation to moocore.epsilon_additive for efficiency: the previous implementation was a pure-Python double loop with per-point generator expressions, which moocore's C implementation improves on substantially. Our own guard clauses in front of it are not just for a consistent contract but for safety: moocore.epsilon_additive segfaults (not a catchable Python exception) on an empty front or an empty reference_front, so those cases must never reach it.

Parameters:

Name Type Description Default
front array

Solution front matrix (each row is a solution)

required

Returns:

Type Description
float

The additive epsilon indicator value

Raises:

Type Description
ValueError

If the reference front is not set, or if front is empty or has different dimensionality than reference front

Source code in src/jmetal/core/quality_indicator.py
def compute(self, front: np.array) -> float:
    """
    Compute the additive epsilon indicator value.

    Delegates the actual computation to `moocore.epsilon_additive` for
    efficiency: the previous implementation was a pure-Python double loop with
    per-point generator expressions, which moocore's C implementation improves
    on substantially. Our own guard clauses in front of it are not just for a
    consistent contract but for safety: `moocore.epsilon_additive` segfaults
    (not a catchable Python exception) on an empty `front` or an empty
    `reference_front`, so those cases must never reach it.

    Args:
        front: Solution front matrix (each row is a solution)

    Returns:
        The additive epsilon indicator value

    Raises:
        ValueError: If the reference front is not set, or if front is empty or has
                  different dimensionality than reference front
    """
    if self.reference_front is None:
        raise ValueError("Reference front must be set before computing EP")
    if len(self.reference_front) == 0:
        raise ValueError("Reference front cannot be empty")
    if front is None or len(front) == 0:
        raise ValueError("Solution front cannot be None or empty")

    if front.shape[1] != self.reference_front.shape[1]:
        raise ValueError("Solution and reference front must have the same number of objectives")

    return float(moocore.epsilon_additive(front, ref=self.reference_front))

HyperVolume(reference_point=None, reference_front=None, reference_point_offset=0.0)

Bases: QualityIndicator

Hypervolume (HV) quality indicator.

The hypervolume indicator measures the volume of the objective space that is
dominated by the solution set, bounded by a reference point. It is a widely
used indicator for multi-objective optimization as it captures both
convergence and diversity in a single scalar value.

This implementation delegates computation to the `moocore` library for
efficiency. The class maintains an internal `moocore.Hypervolume` instance
which is recreated whenever the reference point or the configured offset
changes.

Notes on the API and conventions:
        - Higher hypervolume values indicate better quality (this indicator is
            treated as a maximization measure).
        - By convention this implementation assumes minimization problems when
            deriving a reference point from a `reference_front` (see
            `set_reference_front`).
        - The `reference_point_offset` is a scalar that is added to every
            objective of the reference point before creating the internal
            `moocore.Hypervolume`. Using a small positive offset ensures that
            solutions equal to the extreme points of the reference front still
            contribute positively to the hypervolume.
Reference

Zitzler, E., & Thiele, L. (1998). Multiobjective optimization using evolutionary algorithms - A comparative case study. In International Conference on Parallel Problem Solving from Nature (pp. 292-301).

Initialize the hypervolume indicator.

Parameters:

Name Type Description Default
reference_point list[float] | None

Optional explicit reference point (sequence of objective values). If provided, it takes precedence over reference_front.

None
reference_front ndarray | None

Optional 2D array-like reference front. When provided and reference_point is not, the reference point is derived as the element-wise maximum across the reference front (suitable for minimization problems) and then the scalar reference_point_offset is added.

None
reference_point_offset float

Scalar offset added to each objective of the reference point when constructing the internal moocore.Hypervolume. Default is 0.0.

0.0
Source code in src/jmetal/core/quality_indicator.py
def __init__(
    self,
    reference_point: list[float] | None = None,
    reference_front: np.ndarray | None = None,
    reference_point_offset: float = 0.0,
):
    """Initialize the hypervolume indicator.

    Args:
        reference_point: Optional explicit reference point (sequence of
                 objective values). If provided, it takes precedence
                 over `reference_front`.
        reference_front: Optional 2D array-like reference front. When
                 provided and `reference_point` is not, the
                 reference point is derived as the element-wise
                 maximum across the reference front (suitable for
                 minimization problems) and then the scalar
                 `reference_point_offset` is added.
        reference_point_offset: Scalar offset added to each objective of the
                    reference point when constructing the internal
                    `moocore.Hypervolume`. Default is `0.0`.
    """
    super().__init__(is_minimization=False)
    # store reference point in a private attribute and build the moocore
    # Hypervolume instance via the property setter so they stay in sync
    self._reference_point = None
    self.hv = None
    # offset (scalar) to be added to each objective of the reference point
    self._reference_point_offset = float(reference_point_offset)
    # allow deriving reference point from a provided reference front
    self._reference_front: np.ndarray | None = None

    # use the property to ensure synchronization
    if reference_point is not None:
        self.reference_point = reference_point
    elif reference_front is not None:
        self.set_reference_front(reference_front)
    else:
        # leave uninitialized until the user sets a reference
        self._reference_point = None
        self.hv = None

reference_point property writable

Reference point getter.

Returns the stored reference point as a list of floats or None.

reference_point_offset property writable

Scalar offset added to each objective of the reference point when creating the internal moocore.Hypervolume. Useful to ensure the reference point is strictly worse than the extreme points of a front.

set_reference_front(reference_front)

Derive a reference point from a reference front and set it.

The derivation uses the element-wise maximum across the reference front (suitable for minimization problems) and then applies the scalar offset.

Source code in src/jmetal/core/quality_indicator.py
def set_reference_front(self, reference_front: np.ndarray) -> None:
    """Derive a reference point from a reference front and set it.

    The derivation uses the element-wise maximum across the reference front
    (suitable for minimization problems) and then applies the scalar offset.
    """
    if reference_front is None:
        raise ValueError("reference_front cannot be None")
    arr = np.asarray(reference_front, dtype=float)
    if arr.ndim != 2 or arr.shape[0] == 0:
        raise ValueError("reference_front must be a non-empty 2D array")

    # store the front
    self._reference_front = arr

    # derive reference point as element-wise maxima (minimization convention)
    derived = np.max(arr, axis=0)
    # use the property setter which will apply the offset and recreate hv
    self.reference_point = derived.tolist()

compute(solutions)

Compute the hypervolume indicator value.

Parameters:

Name Type Description Default
solutions ndarray

A 2D numpy array of shape (m, n) where m is the number of solutions and n is the number of objectives.

required

Returns:

Type Description
float

The hypervolume value (higher is better).

Raises:

Type Description
ValueError

If the reference point is not set or if any solution is not dominated by the reference point.

Source code in src/jmetal/core/quality_indicator.py
def compute(self, solutions: np.ndarray) -> float:
    """Compute the hypervolume indicator value.

    Args:
        solutions: A 2D numpy array of shape (m, n) where m is the number of
                 solutions and n is the number of objectives.

    Returns:
        The hypervolume value (higher is better).

    Raises:
        ValueError: If the reference point is not set or if any solution is
                   not dominated by the reference point.
    """
    if self._reference_point is None:
        raise ValueError("Reference point must be set before computing hypervolume")

    # mypy/static checkers may not infer that self.hv is set; assert for clarity
    assert self.hv is not None

    # The moocore.Hypervolume class uses __call__ for computation
    return float(self.hv(solutions))

NormalizedHyperVolume(reference_point=None, reference_front=None, reference_point_offset=0.0)

Bases: QualityIndicator

Normalized Hypervolume (NHV) quality indicator.

The normalized hypervolume is calculated as

NHV = 1 - (HV of the front / HV of the reference front)

This indicator is useful for comparing solution sets when the absolute scale of the objectives is not known in advance. It assumes minimization of the indicator value (lower is better).

The reference front should be a high-quality approximation of the true Pareto front for meaningful normalization.

Note
  • NHV = 0 when the front has the same hypervolume as the reference front.
  • NHV approaches 1 as the front quality decreases.
  • Negative values indicate the front is better than the reference front.

Initialize the normalized hypervolume indicator.

Parameters:

Name Type Description Default
reference_point list[float]

The reference point for hypervolume computation. Must be worse than all solutions in all objectives.

None
Source code in src/jmetal/core/quality_indicator.py
def __init__(
    self,
    reference_point: list[float] = None,
    reference_front: np.ndarray | None = None,
    reference_point_offset: float = 0.0,
):
    """Initialize the normalized hypervolume indicator.

    Args:
        reference_point: The reference point for hypervolume computation.
                       Must be worse than all solutions in all objectives.
    """
    super().__init__(is_minimization=True)
    # If a reference_point is provided prefer it; otherwise derive from reference_front
    if reference_point is not None:
        self.reference_point = reference_point
        self._hv = HyperVolume(
            reference_point=reference_point, reference_point_offset=reference_point_offset
        )
    elif reference_front is not None:
        # create HyperVolume deriving the reference point from the front
        self._hv = HyperVolume(
            reference_front=reference_front, reference_point_offset=reference_point_offset
        )
    else:
        # leave hv uninitialized until user sets reference
        self._hv = HyperVolume(
            reference_point=None, reference_point_offset=reference_point_offset
        )

    self._reference_hypervolume = None  # Will be set by set_reference_front()

set_reference_front(reference_front)

Set the reference front and compute its hypervolume.

Parameters:

Name Type Description Default
reference_front ndarray

The reference front used for normalization.

required

Raises:

Type Description
ValueError

If the reference front results in zero hypervolume.

Source code in src/jmetal/core/quality_indicator.py
def set_reference_front(self, reference_front: np.ndarray) -> None:
    """Set the reference front and compute its hypervolume.

    Args:
        reference_front: The reference front used for normalization.

    Raises:
        ValueError: If the reference front results in zero hypervolume.
    """
    # compute and cache the hypervolume of the provided reference front
    self._reference_hypervolume = self._hv.compute(reference_front)
    if self._reference_hypervolume == 0:
        # A zero hypervolume for the reference front makes normalization
        # impossible (division by zero) and indicates an invalid
        # reference front for HV-based indicators. Raise a clear
        # ValueError so callers can handle/report this explicitly.
        raise ValueError(
            "Hypervolume of reference front is zero: reference front invalid for HV-based normalization"
        )

compute(solutions)

Compute the normalized hypervolume indicator value.

Parameters:

Name Type Description Default
solutions ndarray

A 2D numpy array of shape (m, n) where m is the number of solutions and n is the number of objectives.

required

Returns:

Type Description
float

The normalized hypervolume value (lower is better).

Raises:

Type Description
RuntimeError

If the reference front has not been set.

Source code in src/jmetal/core/quality_indicator.py
def compute(self, solutions: np.ndarray) -> float:
    """Compute the normalized hypervolume indicator value.

    Args:
        solutions: A 2D numpy array of shape (m, n) where m is the number of
                 solutions and n is the number of objectives.

    Returns:
        The normalized hypervolume value (lower is better).

    Raises:
        RuntimeError: If the reference front has not been set.
    """
    if self._reference_hypervolume is None:
        raise RuntimeError(
            "Reference front must be set before computing normalized hypervolume"
        )

    hv = self._hv.compute(solutions=solutions)
    return 1.0 - (hv / self._reference_hypervolume)

get_short_name()

Get the short name of the indicator.

Returns:

Type Description
str

'NHV' for Normalized Hypervolume.

Source code in src/jmetal/core/quality_indicator.py
def get_short_name(self) -> str:
    """Get the short name of the indicator.

    Returns:
        'NHV' for Normalized Hypervolume.
    """
    return "NHV"

get_name()

Get the full name of the indicator.

Returns:

Type Description
str

'Normalized Hypervolume'.

Source code in src/jmetal/core/quality_indicator.py
def get_name(self) -> str:
    """Get the full name of the indicator.

    Returns:
        'Normalized Hypervolume'.
    """
    return "Normalized Hypervolume"

operator

This module defines the core operator interfaces for optimization in JMetalPy.

Operators are the building blocks of evolutionary algorithms, including mutation, crossover, and selection operators. These operators are used to create variation in the population and guide the search towards better solutions.

Operator

Bases: Generic[S, R], ABC

Abstract base class for all operators in JMetalPy.

An operator transforms one or more input solutions into one or more output solutions. This is the base class for all variation operators like mutation, crossover, and selection.

Subclasses must implement the execute() method to define the operator's behavior and get_name() to provide a string identifier.

execute(source) abstractmethod

Execute the operator on the source solution(s).

Parameters:

Name Type Description Default
source S

The input solution or list of solutions to be transformed.

required

Returns:

Type Description
R

The transformed solution or list of solutions.

Note

The exact type and number of input and output solutions depend on the specific operator implementation.

Source code in src/jmetal/core/operator.py
@abstractmethod
def execute(self, source: S) -> R:
    """Execute the operator on the source solution(s).

    Args:
        source: The input solution or list of solutions to be transformed.

    Returns:
        The transformed solution or list of solutions.

    Note:
        The exact type and number of input and output solutions depend on the
        specific operator implementation.
    """
    pass

get_name() abstractmethod

Get the name of the operator.

Returns:

Type Description
str

A string identifier for the operator (e.g., 'SBX', 'PolynomialMutation').

Source code in src/jmetal/core/operator.py
@abstractmethod
def get_name(self) -> str:
    """Get the name of the operator.

    Returns:
        A string identifier for the operator (e.g., 'SBX', 'PolynomialMutation').
    """
    pass

Mutation(probability)

Bases: Operator[S, S], ABC

Abstract base class for mutation operators.

Mutation operators introduce small random changes to a solution to maintain diversity in the population. Each solution has a probability of being mutated.

Attributes:

Name Type Description
probability

The probability that a solution will be mutated (0.0 to 1.0).

Initialize the mutation operator with a given probability.

Parameters:

Name Type Description Default
probability float

The probability of applying the mutation to a solution. Must be between 0.0 and 1.0.

required
Source code in src/jmetal/core/operator.py
@check_valid_probability_value
def __init__(self, probability: float):
    """Initialize the mutation operator with a given probability.

    Args:
        probability: The probability of applying the mutation to a solution.
                    Must be between 0.0 and 1.0.
    """
    self.probability = probability

Crossover(probability)

Bases: Operator[list[S], list[R]], ABC

Abstract base class for crossover operators.

Crossover operators combine genetic information from two or more parent solutions to produce new offspring solutions. This mimics biological recombination.

Attributes:

Name Type Description
probability

The probability of applying the crossover to a set of parents.

Initialize the crossover operator with a given probability.

Parameters:

Name Type Description Default
probability float

The probability of applying the crossover to a set of parents. Must be between 0.0 and 1.0.

required
Source code in src/jmetal/core/operator.py
@check_valid_probability_value
def __init__(self, probability: float):
    """Initialize the crossover operator with a given probability.

    Args:
        probability: The probability of applying the crossover to a set of parents.
                    Must be between 0.0 and 1.0.
    """
    self.probability = probability

get_number_of_parents() abstractmethod

Get the number of parent solutions required by this crossover.

Returns:

Type Description
int

The number of parent solutions needed (typically 2 for most crossovers).

Source code in src/jmetal/core/operator.py
@abstractmethod
def get_number_of_parents(self) -> int:
    """Get the number of parent solutions required by this crossover.

    Returns:
        The number of parent solutions needed (typically 2 for most crossovers).
    """
    pass

get_number_of_children() abstractmethod

Get the number of offspring solutions produced by this crossover.

Returns:

Type Description
int

The number of offspring solutions generated (often equal to the number of parents).

Source code in src/jmetal/core/operator.py
@abstractmethod
def get_number_of_children(self) -> int:
    """Get the number of offspring solutions produced by this crossover.

    Returns:
        The number of offspring solutions generated (often equal to the number of parents).
    """
    pass

Selection()

Bases: Operator[list[S], R], ABC

Abstract base class for selection operators.

Selection operators are used to choose solutions from a population for reproduction. Different selection strategies can affect the exploration/exploitation balance.

Initialize the selection operator.

Source code in src/jmetal/core/operator.py
def __init__(self):
    """Initialize the selection operator."""
    pass

check_valid_probability_value(func)

Decorator to validate that a probability value is between 0 and 1.

This decorator is used to ensure that probability values passed to operator constructors are within the valid range [0.0, 1.0].

Parameters:

Name Type Description Default
func Callable

The function to be decorated (typically init of an operator).

required

Returns:

Type Description
Callable

The wrapped function with probability validation.

Raises:

Type Description
ValueError

If the probability is outside the [0.0, 1.0] range.

Source code in src/jmetal/core/operator.py
def check_valid_probability_value(func: Callable) -> Callable:
    """Decorator to validate that a probability value is between 0 and 1.

    This decorator is used to ensure that probability values passed to operator
    constructors are within the valid range [0.0, 1.0].

    Args:
        func: The function to be decorated (typically __init__ of an operator).

    Returns:
        The wrapped function with probability validation.

    Raises:
        ValueError: If the probability is outside the [0.0, 1.0] range.
    """

    @wraps(func)
    def func_wrapper(self, probability: float) -> Any:
        if probability > 1.0:
            raise ValueError(f"The probability is greater than one: {probability}")
        elif probability < 0.0:
            raise ValueError(f"The probability is lower than zero: {probability}")
        return func(self, probability)

    return func_wrapper