Project-Platypus / Platypus

A Free and Open Source Python Library for Multiobjective Optimization
GNU General Public License v3.0
563 stars 153 forks source link

CompoundOperator with multiparent variators and Subsets #140

Closed mronda closed 4 years ago

mronda commented 4 years ago

Hi,

So I have a problem setup with multiple types, Real and Subset. I was wondering if I can use the Multiparent Variator for the Reals ? I tried the line below :

operators = CompoundOperator(SSX(probability=0.1), SPX(nparents=4,noffspring=2), Replace(probability=0.01), PM(probability=0.01))

And got an error like unexpected number of offspring, expected 2, received 3.

Is it even possible to combine these operators or I would have to stick to SBX() for Reals?

Thanks!

dhadka commented 4 years ago

@mronda Hi! CompoundOperator isn't very smart. It wants the number of offspring from one operator to match the number of parents required by the next operator. For example, SSX produces 2 offspring, but SPX requires 4 parents. It wouldn't allow that. I believe you can work around this by putting SPX first:

operators = CompoundOperator(SPX(nparents=4,noffspring=2), SSX(probability=0.1), Replace(probability=0.01), PM(probability=0.01))
mronda commented 4 years ago

Hi @dhadka

Tried that and getting:

    508         # compute center of mass
--> 509         G = [sum([x[i][j] for i in range(n)]) / n for j in range(m)]
    510 
    511         # compute expanded simplex vertices

TypeError: unsupported operand type(s) for +: 'int' and 'list' 

SBX() has a check for type before entering, should this operator also be doing that?

mronda commented 4 years ago

hi @dhadka any ideas on this? Thanks!

dhadka commented 4 years ago

Hmm...yeah, it looks like SPX doesn't filter by the decision variable type, so it can currently only work on real-valued decision variables. Have you tried SBX instead (simulated binary crossover)? That should work.

mronda commented 4 years ago

Hi @dhadka yes, I have. But for the constrained problem I am working on, I saw huge benefits of multi-parent crossover as SBX wasn't exploring the space as well. Would filtering the variables work? something like this :

k = len(parents)
n = parents[0].problem.nvars
x = []

problem = parents[0].problem
for i in range(k):
    variables = []
    for v in range(n):
        if isinstance(problem.types[v], Real):
            variables.append(parents[i].variables[v])

    x.append(variables)

And then SSX() would handle the Subsets...

dhadka commented 4 years ago

Yes, filtering the variables is another option :)

mronda commented 4 years ago

Just thought I'd update this. I re wrote a bit of that operator to work when using the CompoundOperator() and mix types. This is the code I am using for SPX() . Thanks!


class _SPX(Variator):
    def __init__(self, nparents=10, noffspring=2, expansion=None):
        super(_SPX, self).__init__(nparents)
        self.nparents = nparents
        self.noffspring = noffspring

        if expansion is None:
            self.expansion = math.sqrt(nparents + 1)
        else:
            self.expansion = expansion

    def evolve(self, parents):
        n = len(parents)
        nvars = parents[0].problem.nvars
        problem = parents[0].problem
        x = []

        # get continous indices
        con_ix = []
        for i in range(nvars):
            if isinstance(problem.types[i], Real):
                con_ix.append(i)

        m = len(con_ix)

        # append only continous variables
        for i in range(n):
            variables = []
            for v in con_ix:
                if isinstance(problem.types[v], Real):
                    variables.append(parents[i].variables[v])

            x.append(variables)

        # compute center of mass
        G = [sum([x[i][j] for i in range(n)]) / n for j in range(m)]

        # compute expanded simplex vertices
        for i in range(n):
            x[i] = add(G, multiply(self.expansion, subtract(x[i], G)))

        # generate offspring
        result = []

        for _ in range(self.noffspring):
            child = copy.deepcopy(parents[n - 1])
            r = [
                math.pow(random.uniform(0.0, 1.0), 1.0 / (i + 1.0))
                for i in range(n - 1)
            ]
            C = zeros(n, m)

            for i in range(n):
                for j in range(m):
                    if i == 0:
                        C[i][j] = 0.0
                    else:
                        C[i][j] = r[i - 1] * (x[i - 1][j] - x[i][j] + C[i - 1][j])

            for j in range(0, len(con_ix)):
                type = child.problem.types[con_ix[j]]
                child.variables[con_ix[j]] = clip(
                    x[n - 1][j] + C[n - 1][j], type.min_value, type.max_value
                )

            child.evaluated = False
            result.append(child)

        return result