jMetal/jMetalPy

Refactor class Problem

Opened this issue · 0 comments

Class Problem in jMetalPy has the following constructor:

    def __init__(self):
        self.number_of_variables: int = 0
        self.number_of_objectives: int = 0
        self.number_of_constraints: int = 0

        self.reference_front: List[S] = []

        self.directions: List[int] = []
        self.labels: List[str] = []

Several points can be pointed out here:

  1. Setting the number of variables, objectives and constraints should be made with methods instead of state variables.
  2. The reference front is not known for real-world problems and it is used mainly for plotting and for calculating quality indicatores. It does not make sense that it has to be part of a problem.
  3. The directions is used to indicate whether the objective functions are to be minimized or maximized, which is used for generating output results. However, jMetalPy assumes that all the objectives are to be minimized, so this field can bring confusion to users.

The proposal to deal with these issues is to redefine the constructor as follows:

    def __init__(self):
        self.directions: List[int] = []
        self.labels: List[str] = []

    @abstractmethod
    def number_of_variables(self) -> int:
        pass

    @abstractmethod
    def number_of_objectives(self) -> int:
        pass

    @abstractmethod
    def number_of_constraints(self) -> int:
        pass

This way, the FloatProblem class can be implemented in this way:

    def __init__(self):
        super(FloatProblem, self).__init__()
        self.lower_bound = []
        self.upper_bound = []

    def number_of_variables(self) -> int:
        return len(self.lower_bound)

So the unconstrained benchmark problem Kursawecan be defined as:

class Kursawe(FloatProblem):
    """Class representing problem Kursawe."""

    def __init__(self, number_of_variables: int = 3):
        super(Kursawe, self).__init__()

        self.obj_directions = [self.MINIMIZE, self.MINIMIZE]
        self.obj_labels = ["f(x)", "f(y)"]

        self.lower_bound = [-5.0 for _ in range(number_of_variables)]
        self.upper_bound = [5.0 for _ in range(number_of_variables)]

        FloatSolution.lower_bound = self.lower_bound
        FloatSolution.upper_bound = self.upper_bound

    def number_of_objectives(self) -> int:
        return 2

    def number_of_constraints(self) -> int:
        return 0

In the case of the constrained problem Srinivas, it can defined in this way:

class Srinivas(FloatProblem):
    """Class representing problem Srinivas."""

    def __init__(self):
        super(Srinivas, self).__init__()
        number_of_variables = 2

        self.obj_directions = [self.MINIMIZE, self.MINIMIZE]
        self.obj_labels = ["f(x)", "f(y)"]

        self.lower_bound = [-20.0 for _ in range(number_of_variables)]
        self.upper_bound = [20.0 for _ in range(number_of_variables)]

    def number_of_objectives(self) -> int:
        return 2

    def number_of_constraints(self) -> int:
        return 2

    def evaluate(self, solution: FloatSolution) -> FloatSolution:
        x1 = solution.variables[0]
        x2 = solution.variables[1]

        solution.objectives[0] = 2.0 + (x1 - 2.0) * (x1 - 2.0) + (x2 - 1.0) * (x2 - 1.0)
        solution.objectives[1] = 9.0 * x1 - (x2 - 1.0) * (x2 - 1.0)

        self.__evaluate_constraints(solution)

        return solution

About the reference_front and directions state variables, the suggestion is to remove the first one while the second one should be properly documented.