jMetal / jMetalPy

A framework for single/multi-objective optimization with metaheuristics
https://jmetal.github.io/jMetalPy/index.html
MIT License
497 stars 150 forks source link

How to continue searching with two files[VAR and FUN] as the initial value? #130

Closed CryoARIUM closed 1 year ago

CryoARIUM commented 2 years ago

Thank you for providing a great application. I was able to execute the prepared example.

I have one question. Some Examples generate two files after execution. If I want to continue searching with that values as the initial value, how should I read the file?

For example, "gnsgaii_solving_zdt2_with_reference_point.py". (1) Execute it with [max_evaluations=5000]. (2) The evaluation results of 5000 are saved in "VAR.NSGAII.ZDT2" and "FUN.NSGAII.ZDT2". But they have not completely converged yet. (3) I want to execute the subsequent evolutionary computation based on the saved VAR and FUN files.

ajnebro commented 2 years ago

Hi. Certainly, 5000 evaluations is a very low number to get a front with good convergence on problem ZDT2. A number in the range of 15000 - 20000 is usually adopted.

The feature you request could be implemented by overriding the create_initial_solutions() method of class Algorithm.

CryoARIUM commented 2 years ago

Thank you for your support !

However, I didn't know how to override the create_initial_solutions () method. I'll study Python a little more and then try to implement it.

CryoARIUM commented 2 years ago

I tried to override the create_initial_solutions() method as below. But it can't work well. I think that the return of `create_initial_solutions' was wrong on my description .

from typing import TypeVar
S= TypeVar('S')
R= TypeVar('R')

class NSGAIIWithInitialSolution(NSGAII[S,R]):
    def create_initial_solutions(self):
        initial_solutions_VAR=[]

        with open(file = 'VAR.NSGAII.ZDT2') as file:
            for line in file:
                vector = [float(x) for x in line.split()]
                initial_solutions_VAR.append(vector)

        return initial_solutions_VAR

Could you please give a simple example about 'create_initial_solutions' ?

When solving a real-world problem, I would like to explore the surrounding area starting from one or some points selected by technical view.

CryoARIUM commented 2 years ago

I tried override create_initial_solution as below.

I reduced the number to make it easier to check the operation.

import random
random.seed(1) 

if __name__ == '__main__':
    problem = ZDT2()
    problem.reference_front = read_solutions(filename='resources/reference_front/ZDT2.pf')
    reference_point = [0.2, 0.5]

    max_evaluations = 2                            # change from 50000 to 2

    algorithm = NSGAIIWithInitialSolution(
        problem=problem,
        population_size=1,                            # change from 100 to 1
        offspring_population_size=1,           # change from 100 to 1
        mutation=PolynomialMutation(probability=1.0 / problem.number_of_variables, distribution_index=20),
        crossover=SBXCrossover(probability=1.0, distribution_index=20),
        dominance_comparator=GDominanceComparator(reference_point),
        termination_criterion=StoppingByEvaluations(max_evaluations=max_evaluations)
    )

I also tried overriding create_initial_solution by writing the floatsolution list directly.

class NSGAIIWithInitialSolution(GeneticAlgorithm[S,R]):
    def create_initial_solution(self) -> List[FloatSolution]:
        solutions = FloatSolution(lower_bound=[], upper_bound=[], number_of_objectives=2, number_of_constraints=0 )
        solutions.number_of_variables=30
        solutions.variables = [1,1,1,1,1, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0] 
        return solutions

The script was able to run, but algorithms.solutions.variables could not be changed.

CryoARIUM commented 2 years ago

I was able to read initial_solutions from 'VAR.NSGAII.ZDT1' file. The create_initial_solutions method were overriden and initial_solutions was evaluated. However, after evaluate initial_solutions, "IndexError: list index out of range" was occurred.

from jmetal.algorithm.multiobjective.nsgaii import NSGAII
from jmetal.lab.visualization import Plot, InteractivePlot
from jmetal.operator import SBXCrossover, PolynomialMutation
from jmetal.problem import ZDT2
from jmetal.util.comparator import GDominanceComparator
from jmetal.util.observer import ProgressBarObserver, VisualizerObserver
from jmetal.util.solution import print_function_values_to_file, print_variables_to_file, read_solutions
from jmetal.util.termination_criterion import StoppingByEvaluations

"""  
Program to configure and run G-NSGA-II (NSGA-II with G-Dominance) to solve problem ZDT2 with 
reference point = [0.2, 0.5].
"""

from typing import TypeVar
S= TypeVar('S')
R= TypeVar('R')

import logging
LOGGER=logging.getLogger('jmetal')
from pathlib import Path
from jmetal.core.solution import FloatSolution 

####################################
def read_initial_solutions_VAR(filename:str):
    """
    read initial_solutions from the 'VAR.*.*' file. 
    """
    initial_solution_VAR=[]

    if Path(filename).is_file():
        with open(filename) as file:
            for line in file:
                vector = [float(x) for x in line.split()]
                solution=FloatSolution(
                    lower_bound=[],
                    upper_bound=[],
                    number_of_constraints=0,
                    number_of_objectives=2)
                solution.number_of_variables=30
                solution.variables = vector

                initial_solution_VAR.append(solution)
    else:
        LOGGER.warning(' .VAR file was not found at {}'.format(filename))
    return initial_solution_VAR
################################################

from typing import TypeVar, List, Generator
from jmetal.config import store
from jmetal.core.operator import Mutation, Crossover, Selection
from jmetal.core.problem import Problem
from jmetal.operator import BinaryTournamentSelection
from jmetal.util.density_estimator import CrowdingDistance
from jmetal.util.evaluator import Evaluator
from jmetal.util.ranking import FastNonDominatedRanking
from jmetal.util.replacement import RankingAndDensityEstimatorReplacement, RemovalPolicyType
from jmetal.util.comparator import Comparator, MultiComparator
from jmetal.util.termination_criterion import TerminationCriterion

class NSGAIIWithInitialSolution(NSGAII[S,R]):
    def __init__(self,
                 problem: Problem,
                 population_size: int,
                 offspring_population_size: int,
                 mutation: Mutation,
                 crossover: Crossover,
                 selection: Selection = BinaryTournamentSelection(
                     MultiComparator([FastNonDominatedRanking.get_comparator(),
                                      CrowdingDistance.get_comparator()])),
                 termination_criterion: TerminationCriterion = store.default_termination_criteria,
                 population_generator: Generator = store.default_generator,
                 population_evaluator: Evaluator = store.default_evaluator,
                 dominance_comparator: Comparator = store.default_comparator):
        super(NSGAIIWithInitialSolution, self).__init__(
            problem=problem,
            population_size=population_size,
            offspring_population_size=offspring_population_size,
            mutation=mutation,
            crossover=crossover,
            selection=selection,
            termination_criterion=termination_criterion,
            population_evaluator=population_evaluator,
            population_generator=population_generator
        )
        self.dominance_comparator = dominance_comparator

    def replacement(self, population: List[S], offspring_population: List[S]) -> List[List[S]]:
        """ This method joins the current and offspring populations to produce the population of the next generation
        by applying the ranking and crowding distance selection.

        :param population: Parent population.
        :param offspring_population: Offspring population.
        :return: New population after ranking and crowding distance selection is applied.
        """
        ranking = FastNonDominatedRanking(self.dominance_comparator)
        density_estimator = CrowdingDistance()

        r = RankingAndDensityEstimatorReplacement(ranking, density_estimator, RemovalPolicyType.ONE_SHOT)
        solutions = r.replace(population, offspring_population)

        return solutions

    def get_result(self) -> R:
        return self.solutions

    def get_name(self) -> str:
        return 'NSGAII'

    ### override create_initial_solutions()method ########################
    def create_initial_solutions(self):
        initial_solutions = read_initial_solutions_VAR(filename = 'VAR.NSGAII.ZDT2')
        return initial_solutions
    ##########################################################

if __name__ == '__main__':
    problem = ZDT2()
    problem.reference_front = read_solutions(filename='resources/reference_front/ZDT2.pf')

    reference_point = [0.2, 0.5]

    max_evaluations = 1000
    algorithm = NSGAIIWithInitialSolution(
        problem=problem,
        population_size=100,
        offspring_population_size=100,
        mutation=PolynomialMutation(probability=1.0 / problem.number_of_variables, distribution_index=10),
        crossover=SBXCrossover(probability=1.0, distribution_index=10),
        dominance_comparator=GDominanceComparator(reference_point),
        termination_criterion=StoppingByEvaluations(max_evaluations=max_evaluations)
    )

    algorithm.observable.register(observer=ProgressBarObserver(max=max_evaluations))
    algorithm.observable.register(
        observer=VisualizerObserver(reference_front=problem.reference_front, reference_point=reference_point))

    algorithm.run()
    front = algorithm.get_result()

    # Plot front
    plot_front = Plot(title='Pareto front approximation. Problem: ' + problem.get_name(),
                      reference_front=problem.reference_front, axis_labels=problem.obj_labels)
    plot_front.plot(front, label=algorithm.label, filename=algorithm.get_name())

    # Plot interactive front
    plot_front = InteractivePlot(title='Pareto front approximation. Problem: ' + problem.get_name(),
                                 reference_front=problem.reference_front, axis_labels=problem.obj_labels)
    plot_front.plot(front, label=algorithm.label, filename=algorithm.get_name())

    # Save results to file
    print_function_values_to_file(front, 'FUN.' + algorithm.label)
    print_variables_to_file(front, 'VAR.' + algorithm.label)

    print('Algorithm (continuous problem): ' + algorithm.get_name())
    print('Problem: ' + problem.get_name())
    print('Computing time: ' + str(algorithm.total_computing_time))
CryoARIUM commented 2 years ago

It worked for the time being. I found that lower_bound and upper_bound are also needed.

############
def read_initial_solutions_VAR(filename:str):
    """
    read initial_solutions from the 'VAR.*.*' file. 
    """
    initial_solution_VAR=[]

    if Path(filename).is_file():
        solution=FloatSolution(
                    lower_bound=[0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0],
                    upper_bound=[1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1],
                    number_of_constraints=0,
                    number_of_objectives=2)
        solution.number_of_variables=30
        with open(filename) as file:
            for line in file:
                vector = [float(x) for x in line.split()]
                solution.variables = vector
                initial_solution_VAR.append(solution)
    else:
        LOGGER.warning(' .VAR file was not found at {}'.format(filename))
    return initial_solution_VAR
############