class Real(Type):
    """Represents a floating-point value with min and max bounds.
    
    Attributes
    ----------
    min_value : int
        The minimum value (inclusive)
    max_value: int
        The maximum value (inclusive)
    """
    
    def __init__(self, min_value, max_value):
        super(Real, self).__init__()
        self.min_value = float(min_value)
        self.max_value = float(max_value)
        
    def rand(self):
        return random.uniform(self.min_value, self.max_value)
        
    def __str__(self):
        return "Real(%f, %f)" % (self.min_value, self.max_value) class Binary(Type):
    """Represents a binary string.
    
    Binary strings are useful for problems dealing with subsets, where from a
    set of N items a subset of 0 to N elements are selected.  For example, see
    the Knapsack problem.
    
    Internally, in Python, the binary string is stored as a list of boolean
    values, where False represents the 0 (off) bit and and True represents the
    1 (on) bit.
    
    Attributes
    ----------
    nbits : int
        The number of bits.
    """
    
    def __init__(self, nbits):
        super(Binary, self).__init__()
        self.nbits = nbits
        
    def rand(self):
        return [random.choice([False, True]) for _ in range(self.nbits)]
        
    def __str__(self):
        return "Binary(%d)" % self.nbits class Permutation(Type):
    """Represents a permutation.
    
    Permutations are stored as a list of elements in a specific order.  All
    elements will appear in the list exactly once.  For example, this is used
    to represent the traversal through a graph, such as for the Traveling
    Salesman Problem.
    
    Examples
    --------
        # A permutation of integers 0 through 9.
        Permutation(range(10))
        
        # A permutation of tuples.
        Permutation([(a1, a2), (b1, b2), (c1, c2), (d1, d2)])
    
    Attributes
    ----------
    elements : list of objects
        The list of elements that appear in the permutation.
    """
    
    def __init__(self, elements):
        super(Permutation, self).__init__()
        self.elements = list(elements)
        
    def rand(self):
        elements = copy.deepcopy(self.elements)
        random.shuffle(elements)
        return elements
        
    def __str__(self):
        return "Permutation(%d)" % len(self.elements) class Subset(Type):
    """Represents a fixed-size subset.
    
    Use a subset when you must select K elements from a collection of N items.
    Use a binary string when you can select any number of elements (0
    through N) from a collection of N items.
    
    Examples
    --------
        # Pick any two numbers between 0 and 9, without repeats.
        Subset(range(10), 2)
    
    Attributes
    ----------
    elements : list of objects
        The set of elements.
    size : int
        The size of the subset.
    """
    
    def __init__(self, elements, size):
        super(Subset, self).__init__()
        self.elements = list(elements)
        self.size = size
        
    def rand(self):
        indices = list(range(len(self.elements)))
        random.shuffle(indices)
        return [self.elements[i] for i in indices[:self.size]]
    
    def __str__(self):
        return "Subset(%d, %d)" % (len(self.elements), self.size) class GAOperator(Variator):
    
    def __init__(self, variation, mutation):
        super(GAOperator, self).__init__(variation.arity)
        self.variation = variation
        self.mutation = mutation
        
    def evolve(self, parents):
        return list(map(self.mutation.evolve, self.variation.evolve(parents))) class CompoundOperator(Variator):
    
    def __init__(self, *variators):
        super(CompoundOperator, self).__init__(variators[0].arity)
        self.variators = variators
        
    def evolve(self, parents):
        offspring = parents
        
        for variator in self.variators:
            if variator.arity == len(offspring):
                offspring = variator.evolve(offspring)
            elif variator.arity == 1 and len(offspring) >= 1:
                offspring = list(map(variator.evolve, offspring))
            else:
                raise PlatypusError("unexpected number of offspring, expected %d, received %d" % (variator.arity, len(offspring)))
            
        return offspring class CompoundMutation(Mutation):
    
    def __init__(self, *mutators):
        super(CompoundMutation, self).__init__()
        self.mutators = mutators
        
    def mutate(self, parent):
        result = parent
        
        for mutator in self.mutators:
            result = mutator.mutate(result)
            
        return result class SBX(Variator):
     
    def __init__(self, probability = 1.0, distribution_index = 15.0):
        super(SBX, self).__init__(2)
        self.probability = probability
        self.distribution_index = distribution_index
         
    def evolve(self, parents):
        child1 = copy.deepcopy(parents[0])
        child2 = copy.deepcopy(parents[1])
        
        if random.uniform(0.0, 1.0) <= self.probability:
            problem = child1.problem
            nvars = problem.nvars
            
            for i in range(nvars):
                if isinstance(problem.types[i], Real):
                    if random.uniform(0.0, 1.0) <= 0.5:
                        x1 = float(child1.variables[i])
                        x2 = float(child2.variables[i])
                        lb = problem.types[i].min_value
                        ub = problem.types[i].max_value
                        
                        x1, x2 = self.sbx_crossover(x1, x2, lb, ub)
                        
                        child1.variables[i] = x1
                        child2.variables[i] = x2
                        child1.evaluated = False
                        child2.evaluated = False
                    
        return [child1, child2]
                    
    def sbx_crossover(self, x1, x2, lb, ub):
        dx = x2 - x1
        
        if dx > EPSILON:
            if x2 > x1:
                y2 = x2
                y1 = x1
            else:
                y2 = x1
                y1 = x2

            beta = 1.0 / (1.0 + (2.0 * (y1 - lb) / (y2 - y1)))
            alpha = 2.0 - pow(beta, self.distribution_index + 1.0)
            rand = random.uniform(0.0, 1.0)

            if rand <= 1.0 / alpha:
                alpha = alpha * rand
                betaq = pow(alpha, 1.0 / (self.distribution_index + 1.0))
            else:
                alpha = alpha * rand;
                alpha = 1.0 / (2.0 - alpha)
                betaq = pow(alpha, 1.0 / (self.distribution_index + 1.0))
            
            x1 = 0.5 * ((y1 + y2) - betaq * (y2 - y1))
            beta = 1.0 / (1.0 + (2.0 * (ub - y2) / (y2 - y1)));
            alpha = 2.0 - pow(beta, self.distribution_index + 1.0);
            
            if rand <= 1.0 / alpha:
                alpha = alpha * rand;
                betaq = pow(alpha, 1.0 / (self.distribution_index + 1.0));
            else:
                alpha = alpha * rand;
                alpha = 1.0 / (2.0 - alpha);
                betaq = pow(alpha, 1.0 / (self.distribution_index + 1.0));
            
            x2 = 0.5 * ((y1 + y2) + betaq * (y2 - y1));
            
            # randomly swap the values
            if bool(random.getrandbits(1)):
                x1, x2 = x2, x1
            
            x1 = clip(x1, lb, ub)
            x2 = clip(x2, lb, ub)
            
        return x1, x2 class PM(Mutation):
    
    def __init__(self, probability = 1, distribution_index = 20.0):
        super(PM, self).__init__()
        self.probability = probability
        self.distribution_index = distribution_index
        
    def mutate(self, parent):
        child = copy.deepcopy(parent)
        problem = child.problem
        probability = self.probability
        
        if isinstance(probability, int):
            probability /= float(len([t for t in problem.types if isinstance(t, Real)]))
            
        for i in range(len(child.variables)):
            if isinstance(problem.types[i], Real):
                if random.uniform(0.0, 1.0) <= probability:
                    child.variables[i] = self.pm_mutation(float(child.variables[i]),
                                                          problem.types[i].min_value,
                                                          problem.types[i].max_value)

                    child.evaluated = False
        
        return child
    
    def pm_mutation(self, x, lb, ub):
        u = random.uniform(0, 1)
        dx = ub - lb
        
        if u < 0.5:
            bl = (x - lb) / dx
            b = 2.0*u + (1.0 - 2.0*u)*pow(1.0 - bl, self.distribution_index + 1.0)
            delta = pow(b, 1.0 / (self.distribution_index + 1.0)) - 1.0
        else:
            bu = (ub - x) / dx
            b = 2.0*(1.0 - u) + 2.0*(u - 0.5)*pow(1.0 - bu, self.distribution_index + 1.0)
            delta = 1.0 - pow(b, 1.0 / (self.distribution_index + 1.0))
            
        x = x + delta*dx
        x = clip(x, lb, ub)
            
        return x class HUX(Variator):
    
    def __init__(self, probability = 1.0):
        super(HUX, self).__init__(2)
        self.probability = probability
        
    def evolve(self, parents):
        result1 = copy.deepcopy(parents[0])
        result2 = copy.deepcopy(parents[1])
        problem = result1.problem
        
        if random.uniform(0.0, 1.0) <= self.probability:
            for i in range(problem.nvars):
                if isinstance(problem.types[i], Binary):
                    for j in range(problem.types[i].nbits):
                        if result1.variables[i][j] != result2.variables[i][j]:
                            if bool(random.getrandbits(1)):
                                result1.variables[i][j] = not result1.variables[i][j]
                                result2.variables[i][j] = not result2.variables[i][j]
                                result1.evaluated = False
                                result2.evaluated = False
                                
        return [result1, result2] class BitFlip(Mutation):
    
    def __init__(self, probability=1):
        """Bit Flip Mutation for Binary Strings.
        
        Parameters
        ----------
        probability : int or float
            The probability of flipping an individual bit.  If the value is
            an int, then the probability is divided by the number of bits.
        """
        super(BitFlip, self).__init__()
        self.probability = probability
        
    def mutate(self, parent):
        result = copy.deepcopy(parent)
        problem = result.problem
        probability = self.probability
        
        if isinstance(probability, int):
            probability /= sum([t.nbits for t in problem.types if isinstance(t, Binary)])
        
        for i in range(problem.nvars):
            type = problem.types[i]
            
            if isinstance(type, Binary):
                for j in range(type.nbits):
                    if random.uniform(0.0, 1.0) <= probability:
                        result.variables[i][j] = not result.variables[i][j]
                        result.evaluated = False
                        
        return result class PMX(Variator):
    
    def __init__(self, probability = 1.0):
        super(PMX, self).__init__(2)
        self.probability = probability
        
    def evolve(self, parents):
        result1 = copy.deepcopy(parents[0])
        result2 = copy.deepcopy(parents[1])
        problem = result1.problem
        
        for index in range(problem.nvars):
            if isinstance(problem.types[index], Permutation) and random.uniform(0.0, 1.0) <= self.probability:
                p1 = result1.variables[index]
                p2 = result2.variables[index]
                n = len(p1)
                o1 = [None]*n
                o2 = [None]*n
                
                # select cutting points
                cp1 = random.randrange(n)
                cp2 = random.randrange(n)
                
                if n > 1:
                    while cp1 == cp2:
                        cp2 = random.randrange(n)
                
                if cp1 > cp2:
                    cp1, cp2 = cp2, cp1
                    
                # exchange between the cutting points, setting up replacement arrays
                replacement1 = {}
                replacement2 = {}
                
                for i in range(cp1, cp2+1):
                    o1[i] = p2[i]
                    o2[i] = p1[i]
                    replacement1[p2[i]] = p1[i]
                    replacement2[p1[i]] = p2[i]
                    
                # fill in remaining slots with replacements
                for i in range(n):
                    if i < cp1 or i > cp2:
                        n1 = p1[i]
                        n2 = p2[i]
                        
                        while n1 in replacement1:
                            n1 = replacement1[n1]
                            
                        while n2 in replacement2:
                            n2 = replacement2[n2]
                            
                        o1[i] = n1
                        o2[i] = n2
                        
                result1.variables[index] = o1
                result2.variables[index] = o2
                result1.evaluated = False
                result2.evaluated = False       
                
        return [result1, result2] class Insertion(Mutation):
    
    def __init__(self, probability = 0.3):
        super(Insertion, self).__init__()
        self.probability = probability
        
    def mutate(self, parent):
        result = copy.deepcopy(parent)
        problem = result.problem
        
        for index in range(problem.nvars):
            if isinstance(problem.types[index], Permutation) and random.uniform(0.0, 1.0) <= self.probability:
                permutation = result.variables[index]
                i = random.randrange(len(permutation))
                j = random.randrange(len(permutation))
                
                if len(permutation) > 1:
                    while i == j:
                        j = random.randrange(len(permutation))
                    
                # remove the i-th element and insert at j-th position
                temp = permutation[i]
                
                if i < j:
                    for k in range(i+1, j+1):
                        permutation[k-1] = permutation[k]
                elif i > j:
                    for k in range(i-1, j-1, -1):
                        permutation[k+1] = permutation[k]
                        
                permutation[j] = temp
                result.evaluated = False
                
        return result class Swap(Mutation):
    
    def __init__(self, probability = 0.3):
        super(Swap, self).__init__()
        self.probability = probability
        
    def mutate(self, parent):
        result = copy.deepcopy(parent)
        problem = result.problem
        
        for index in range(problem.nvars):
            if isinstance(problem.types[index], Permutation) and random.uniform(0.0, 1.0) <= self.probability:
                permutation = result.variables[index]
                i = random.randrange(len(permutation))
                j = random.randrange(len(permutation))
                
                if len(permutation) > 1:
                    while i == j:
                        j = random.randrange(len(permutation))
                
                permutation[i], permutation[j] = permutation[j], permutation[i]
                result.evaluated = False
                
        return result class SSX(Variator):
    
    def __init__(self, probability = 1.0):
        super(SSX, self).__init__(2)
        self.probability = probability
        
    def evolve(self, parents):
        result1 = copy.deepcopy(parents[0])
        result2 = copy.deepcopy(parents[1])
        problem = result1.problem
        
        for i in range(problem.nvars):
            if isinstance(problem.types[i], Subset) and random.uniform(0.0, 1.0) <= self.probability:
                s1 = set(result1.variables[i])
                s2 = set(result2.variables[i])
                
                for j in range(problem.types[i].size):
                    if result2.variables[i][j] not in s1 and result1.variables[i][j] not in s2 and random.uniform(0.0, 1.0) < 0.5:
                        temp = result1.variables[i][j]
                        result1.variables[i][j] = result2.variables[i][j]
                        result2.variables[i][j] = temp

                result1.evaluated = False
                result2.evaluated = False       
                
        return [result1, result2] class Replace(Mutation):
    
    def __init__(self, probability = 0.3):
        super(Replace, self).__init__()
        self.probability = probability
        
    def mutate(self, parent):
        result = copy.deepcopy(parent)
        problem = result.problem
        
        for index in range(problem.nvars):
            if isinstance(problem.types[index], Subset) and random.uniform(0.0, 1.0) <= self.probability:
                subset = result.variables[index]
                
                if len(subset) < len(problem.types[index].elements):
                    i = random.randrange(len(subset))

                    nonmembers = list(set(problem.types[index].elements) - set(subset))
                    j = random.randrange(len(nonmembers))
                    subset[i] = nonmembers[j]
                    result.evaluated = False
                
        return result class PlatypusError(Exception):
    pass class MapEvaluator(Evaluator):
    
    def __init__(self, map_func=map):
        super(MapEvaluator, self).__init__()
        self.map_func = map_func
    
    def evaluate_all(self, jobs, **kwargs):
        log_frequency = kwargs.get("log_frequency", None)
        
        if log_frequency is None:
            return list(self.map_func(run_job, jobs))
        else:
            result = []
            job_name = kwargs.get("job_name", "Batch Jobs")
            start_time = time.time()
            
            for chunk in _chunks(jobs, log_frequency):
                result.extend(self.map_func(run_job, chunk))
                LOGGER.log(logging.INFO,
                           "%s running; Jobs Complete: %d, Elapsed Time: %s",
                           job_name,
                           len(result),
                           datetime.timedelta(seconds=time.time()-start_time))
                
            return result
import six
from .types import Real, Binary, Permutation, Subset
from .operators import GAOperator, CompoundOperator, CompoundMutation, SBX, PM, HUX, BitFlip, PMX, Insertion, Swap, SSX, Replace
from .core import PlatypusError
from .evaluator import MapEvaluator
# Copyright 2015-2018 David Hadka
#
# This file is part of Platypus, a Python module for designing and using
# evolutionary algorithms (EAs) and multiobjective evolutionary algorithms
# (MOEAs).
#
# Platypus is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Platypus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Platypus.  If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function


class _PlatypusConfig(object):
    
    def __init__(self):
        super(_PlatypusConfig, self).__init__()
    
        self.default_variator = {Real : GAOperator(SBX(), PM()),
                                 Binary : GAOperator(HUX(), BitFlip()),
                                 Permutation : CompoundOperator(PMX(), Insertion(), Swap()),
                                 Subset : GAOperator(SSX(), Replace())}
        
        self.default_mutator = {Real : PM(),
                                Binary : BitFlip(),

                                Permutation : CompoundMutation(Insertion(), Swap()),