anyoptimization / pymoo

NSGA2, NSGA3, R-NSGA3, MOEAD, Genetic Algorithms (GA), Differential Evolution (DE), CMAES, PSO
https://pymoo.org
Apache License 2.0
2.28k stars 392 forks source link

Multi-objective discrete parameter sampling #13

Closed rtn92 closed 5 years ago

rtn92 commented 5 years ago

Hi Julian,

thank you for your work and for providing such a great framework to the community.

I've been trying in vain to get NSGA2 working with discrete parameter sampling. Does the framework generally provide the possibility of discrete parameter sampling for multi-objective problems in the current state?

rtn92 commented 5 years ago

I dug a little deeper into your code. Apparently some NumPy updates have happened in the meantime, which might cause my trouble.

I wanted to implement your 'Getting Started'-example for discrete variables (See https://www.egr.msu.edu/coinlab/blankjul/pymoo/getting_started.html#Define-a-Problem).

So all I did was changing the lower and upper bounds for the variables to something more appropriate for discrete parameter optimization.

def __init__(self):
    super().__init__(n_var=2, n_obj=2, n_constr=1,
                     xl=np.array([-20, -20]), xu=np.array([20, 20]))

I also used your convenience functions for defining an integer-based algorithm.

from pymoo.factory import get_algorithm, get_crossover, get_mutation, get_sampling

method = get_algorithm("nsga2",
                       pop_size=20,
                       sampling=get_sampling("int_random"),
                       crossover=get_crossover("int_sbx"),
                       mutation=get_mutation("int_pm", eta=3.0),
                       elimate_duplicates=False)

These steps resulted in the following error:

File "C:\[...]\pymoo\operators\mutation\polynomial_mutation.py", line 34, in _do
    xl -= 0.5
numpy.core._exceptions.UFuncTypeError: Cannot cast ufunc 'subtract' output from dtype('float64') to dtype('int32') with casting rule 'same_kind'

I solved this by changing the lines

if self.var_type == np.int:
    xl -= 0.5
    xu += (0.5 - 1e-16)

to:

if self.var_type == np.int:
    xl = np.subtract(xl, 0.5)
    xu = np.add(xu, 0.5 - 1e-16)

But yet another error stated:

File "C:\[...]\pymoo\algorithms\nsga2.py", line 265, in calc_crowding_distance_vectorized
    norm[norm == 0] = np.nan
ValueError: cannot convert float NaN to integer

Changing

norm[norm == 0] = np.nan

to

norm = np.column_stack([norm, np.zeros(norm.shape)])
norm = np.apply_along_axis(lambda vector: np.nan if vector[0] == 0 else vector[0], 1, norm)

solved the problem for me. The result fits the solution given in your 'Getting Started'-example. If that last bit of code is a bit messy, it's because I'm not an experienced Python programmer. Best regards

blankjul commented 5 years ago

Hey rtn92,

sorry for my late answer. I was traveling the last week and did not have access to wifi. It is possible to solve discrete problems as well. I wrote a guide (I need to put it somewhere where it is more obvious): https://www.egr.msu.edu/coinlab/blankjul/pymoo/tutorial/discrete_problem.html

You can initialize the sampling, mutation and crossover for integer variables. The int prefix does the job here. Does this work for you? If no, what NumPy version are you using?

So far, mixed variable problems (float and int together) can not be solved out of the box. I planning to include that in one of the next versions.

rtn92 commented 5 years ago

Thank you for your answer.

Your guide on discrete problems helped a lot indeed. So far my problems are gone.

The initialization of the sampling, mutation and crossover with int prefixes resulted in the error messages stated in my second comment. I think, the errors I had faced, were related to NumPy's behavior on implicit type conversion. I am using NumPy version 1.16.4.

Mixed variable problems would be a very fancy feature, as that's exactly what I could use at the moment. I'm hoping that I'm able to handle the mixed variable problem by using discrete methods for both the real parameters and the discrete ones. I think appropriately scaling up the discrete parameter space for the real parameters and projecting it onto the desired real parameter space will do the job.

blankjul commented 5 years ago

In fact, I am using the same NumPy version. What operating system are you working on? Can you post a small code snippet which let me reproduce this issue?

It might be easier to do it the other way. Everything is a float and then you can apply nearest integer rounding after the mutation. Or you apply the conversion during the evaluation function (but then duplicates might not be handled implicitly by the algorithm).

In general, it would make sense to have another class the compose the treatment of different types. Because everything is based on vectors in pymoo, for each variable type a vector would need to be extracted and then the same recombination as usual can be applied.

rtn92 commented 5 years ago

I'm working on Windows 10. Here's the snippet, which gave me the mentioned errors:

import numpy as np
from pymop.problem import Problem
from pymoo.factory import get_algorithm, get_crossover, get_mutation, get_sampling

class MyProblem(Problem):

    def __init__(self):
        super().__init__(n_var=2, n_obj=2, n_constr=1,
                         xl=np.array([-20, -20]), xu=np.array([20, 20]))

    def _evaluate(self, x, out, *args, **kwargs):
        f1 = x[:,0]**2 + x[:,1]**2
        f2 = (x[:,0]-1)**2 + x[:,1]**2
        out["F"] = np.column_stack([f1, f2])
        out["G"] = - (x[:,0]**2 - x[:,0] + 3/16)

problem = MyProblem()
method = get_algorithm("nsga2",
                       pop_size=20,
                       sampling=get_sampling("int_random"),
                       crossover=get_crossover("int_sbx"),
                       mutation=get_mutation("int_pm", eta=3.0),
                       elimate_duplicates=False)

from pymoo.optimize import minimize
import matplotlib.pyplot as plt
from pymoo.util import plotting

res = minimize(problem,
               method,
               termination=('n_gen', 20),
               seed=1,
               save_history=True,
               disp=False)

print("\nDesign Space")
plotting.plot(res.X, show=False, no_fill=True)
plt.ylim(-2, 2)
plt.xlim(-2, 2)
plt.show()

print("\nObjective Space")
plotting.plot(res.F, no_fill=True)
blankjul commented 5 years ago

Thanks for the code snippet. I was able to reproduce it and you are right the casting of variables has changed in the NumPy versions.

To make the casting correct it needs: xl = xl - 0.5 xu = xu + (0.5 - 1e-16)

Also, I realized that for non-dominated sorting and crowding distance the objective space values are not always floats anymore which is necessary. A simple astype(np.float) applied on a vector does the job. I will fix that in the next version.

JiaGe-qub commented 2 years ago

Hi Julian,

The link you have proposed as instructions for handling discrete problems has expired. Would really appreciate it if you can pose it agian. Many thanks.

Regards, Cactus

blankjul commented 2 years ago

please have a look here: https://pymoo.org/customization/discrete.html