Defining new problems¶
To include a problem in jMetalPy, it must implement the Problem interface from the
jmetal.core.problem module.
Use case: Subset Sum¶
The goal is to find a subset S of W (list of non-negative integers) whose elements sum is closest to (without exceeding) C. For example, for the input \(W=\{3, 34, 4, 12, 5, 2\}\) and \(C=9\), one output could be \(S=\{4, 5\}\) (as it is a subset with sum 9).
In jMetalPy, this problem can be encoded as a binary problem with one objective (to be maximized) and one bit per element of W, indicating whether that element is selected:
import numpy as np
from jmetal.core.problem import BinaryProblem
from jmetal.core.solution import BinarySolution
class SubsetSum(BinaryProblem):
def __init__(self, C: int, W: list):
super().__init__()
self.C = C
self.W = np.array(W, dtype=float)
self.number_of_bits = len(self.W)
self.obj_directions = [self.MAXIMIZE]
self.obj_labels = ['Sum']
def number_of_variables(self) -> int:
return self.number_of_bits
def number_of_objectives(self) -> int:
return 1
def number_of_constraints(self) -> int:
return 0
def evaluate(self, solution: BinarySolution) -> BinarySolution:
pass
def create_solution(self) -> BinarySolution:
pass
def name(self) -> str:
return 'Subset Sum'
Now we have to define the abstract methods evaluate and create_solution from the
jmetal.core.problem.Problem class.
Note that each solution consists of one objective function to be maximized to be as close as possible to \(C\):
Taking this into account, one solution could be created and evaluated as follows:
Note
jMetalPy assumes minimization by default. Therefore, we will have to negate the solution objective.
def evaluate(self, solution: BinarySolution) -> BinarySolution:
selected_mask = solution.bits
total_sum = np.sum(self.W[selected_mask])
if total_sum > self.C:
total_sum = self.C - (total_sum - self.C)
if total_sum < 0.0:
total_sum = 0.0
solution.objectives[0] = -total_sum
return solution
def create_solution(self) -> BinarySolution:
solution = BinarySolution(
number_of_variables=self.number_of_bits,
number_of_objectives=self.number_of_objectives(),
)
solution.bits = np.random.choice([True, False], size=self.number_of_bits)
return solution
BinarySolution stores its bits as a NumPy boolean array. Use the bits property to read or
assign the whole array at once (as above), or the list-based variables property for
element-by-element access.
Use case: Multi-objective Subset Sum¶
The former problem can be formulated as a multi-objective binary problem whose objectives are as follows:
- Maximize the sum of subsets to be as close as possible to \(C\) and
- Minimize the number of elements selected from \(W\).
This can be done by returning 2 from number_of_objectives, setting a second objective direction
and label, and computing both objectives in evaluate:
class SubsetSum(BinaryProblem):
def __init__(self, C: int, W: list):
super().__init__()
self.C = C
self.W = np.array(W, dtype=float)
self.number_of_bits = len(self.W)
- self.obj_directions = [self.MAXIMIZE]
- self.obj_labels = ['Sum']
+ self.obj_directions = [self.MAXIMIZE, self.MINIMIZE]
+ self.obj_labels = ['Sum', 'No. of Objects']
def number_of_variables(self) -> int:
return self.number_of_bits
def number_of_objectives(self) -> int:
- return 1
+ return 2
def number_of_constraints(self) -> int:
return 0
def evaluate(self, solution: BinarySolution) -> BinarySolution:
selected_mask = solution.bits
total_sum = np.sum(self.W[selected_mask])
+ number_of_objects = np.count_nonzero(selected_mask)
if total_sum > self.C:
total_sum = self.C - (total_sum - self.C)
if total_sum < 0.0:
total_sum = 0.0
solution.objectives[0] = -total_sum
+ solution.objectives[1] = number_of_objects
return solution
Both variants are available out of the box as
jmetal.problem.singleobjective.unconstrained.SubsetSum and
jmetal.problem.multiobjective.unconstrained.SubsetSum.
API¶
problem
¶
This module defines the core problem interfaces for optimization in JMetalPy.
It provides abstract base classes for defining optimization problems of various types, including binary, float, integer, and permutation problems, as well as utilities for creating problems on the fly.
Problem()
¶
Bases: Generic[S], ABC
Abstract base class for all optimization problems.
This class defines the common interface that all optimization problems must implement. It serves as the foundation for defining problems with different variable types (binary, float, integer, permutation) and characteristics (single/multi-objective, constrained/unconstrained).
Class constants
MINIMIZE: Constant indicating minimization of an objective. MAXIMIZE: Constant indicating maximization of an objective.
Initialize the problem with empty reference front, directions, and labels.
Source code in src/jmetal/core/problem.py
create_solution(rng=None)
abstractmethod
¶
Creates a random_search solution to the problem.
:param rng: Optional random generator for reproducible solution creation. When
None, falls back to the global random/numpy.random state (the historical
default), not a freshly created generator -- unlike the RNG-aware operators
(SBXCrossover, etc.), which fall back to a new np.random.default_rng().
This asymmetry is deliberate: it keeps create_solution() with no arguments
behaviorally identical to before this parameter existed, since
random.seed()-based reproducibility (used throughout the classic algorithm
hierarchy and the component-equivalence tests) depends on it.
:return: Solution.
Source code in src/jmetal/core/problem.py
evaluate(solution)
abstractmethod
¶
Evaluate a solution. For any new problem inheriting from :class:Problem, this method should be replaced.
Note that this framework ASSUMES minimization, thus solutions must be evaluated in consequence.
:return: Evaluated solution.
Source code in src/jmetal/core/problem.py
DynamicProblem()
¶
Bases: Problem[S], Observer, ABC
Abstract base class for dynamic optimization problems.
Dynamic problems are those where the fitness landscape, constraints, or other characteristics may change over time. This class extends the base Problem interface with methods to detect and handle such changes.
This class also implements the Observer pattern to allow the problem to be notified of changes in the environment or other components.
The type parameter S represents the type of the solution this problem works with.
Source code in src/jmetal/core/problem.py
the_problem_has_changed()
abstractmethod
¶
Check if the problem has changed since the last check.
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if the problem has changed, False otherwise. |
clear_changed()
abstractmethod
¶
Clear the changed flag after handling a change event.
This method should be called after the algorithm has responded to a change in the problem to reset the change detection mechanism.
Source code in src/jmetal/core/problem.py
BinaryProblem()
¶
Bases: Problem[BinarySolution], ABC
Abstract base class for binary-encoded optimization problems.
This class is designed for problems where solutions are represented as bit strings. Each variable in the problem is encoded using a fixed number of bits, which can vary between variables.
Attributes:
| Name | Type | Description |
|---|---|---|
number_of_bits_per_variable |
List specifying the number of bits used to encode each variable. |
Initialize a binary problem with an empty list of bits per variable.
Source code in src/jmetal/core/problem.py
FloatProblem()
¶
Bases: Problem[FloatSolution], ABC
Abstract base class for continuous optimization problems with float variables.
This class is designed for problems where decision variables can take any real value within specified lower and upper bounds. It's suitable for continuous optimization problems in any number of dimensions.
Attributes:
| Name | Type | Description |
|---|---|---|
lower_bound |
List of lower bounds for each decision variable. |
|
upper_bound |
List of upper bounds for each decision variable. |
Initialize a float problem with empty bounds.
Source code in src/jmetal/core/problem.py
IntegerProblem()
¶
Bases: Problem[IntegerSolution], ABC
Abstract base class for integer-constrained optimization problems.
This class is designed for problems where decision variables must take integer values within specified lower and upper bounds. It's suitable for discrete optimization problems, combinatorial problems, and mixed-integer problems.
Attributes:
| Name | Type | Description |
|---|---|---|
lower_bound |
List of lower bounds (inclusive) for each decision variable. |
|
upper_bound |
List of upper bounds (inclusive) for each decision variable. |
Initialize an integer problem with empty bounds.
Source code in src/jmetal/core/problem.py
PermutationProblem()
¶
Bases: Problem[PermutationSolution], ABC
Abstract base class for permutation-based optimization problems.
This class is designed for problems where solutions are represented as permutations of a set of elements. Common applications include routing problems (like TSP), scheduling problems, and other combinatorial optimization problems where the order of elements is significant.
The permutation is represented as a list of integers from 0 to n-1, where n is the number of elements in the permutation.
Initialize a permutation problem.
Source code in src/jmetal/core/problem.py
OnTheFlyFloatProblem()
¶
Bases: FloatProblem
A utility class for defining float optimization problems dynamically at runtime.
This class allows users to define optimization problems programmatically by specifying the problem's variables, objectives, and constraints through method chaining. It's particularly useful for quick prototyping and testing.
Example
.. code-block:: python
# Define the problem's objective functions and constraints
def f1(x: List[float]) -> float:
return 2.0 + (x[0] - 2.0)**2 + (x[1] - 1.0)**2
def f2(x: List[float]) -> float:
return 9.0 * x[0] - (x[1] - 1.0)**2
def c1(x: List[float]) -> float:
return 1.0 - (x[0]**2 + x[1]**2) / 225.0
def c2(x: List[float]) -> float:
return (3.0 * x[1] - x[0]) / 10.0 - 1.0
# Create the problem with method chaining
problem = (OnTheFlyFloatProblem()
.set_name("Srinivas")
.add_variable(-20.0, 20.0) # x1 in [-20, 20]
.add_variable(-20.0, 20.0) # x2 in [-20, 20]
.add_function(f1) # First objective
.add_function(f2) # Second objective
.add_constraint(c1) # First constraint (g1(x) <= 0)
.add_constraint(c2)) # Second constraint (g2(x) <= 0)