def test_fitness(self):
        """
        Fitness must always be the same length as individuals
        """
        pop = Population(individuals=[1, 2, 3, 4, 5])

        # should raise error when trying to set fitness with length not
        # equal to the length of indivuals
        with self.assertRaises(PopulationFittnessDimensionError):
            pop.fitness = [1, 2, 3]

        # should allow setting fitness to an iterable with the same length
        # as individuals
        self.fitness = np.zeros(pop.npop)
        self.assertEqual(len(self.fitness), pop.npop)

        # setting fitness should force a sort of individuals and fitness
        ind0 = np.random.rand(100, 5)
        fit0 = np.arange(100)
        pop = Population(individuals=ind0, fitness=fit0)
        for v0, v1 in zip(ind0[0], pop.individuals[-1]):
            self.assertEqual(v0, v1)

        self.assertEqual(fit0.max(), pop.fitness[0])


        # ... but should not change the shape of individuals
        self.assertEqual(ind0.shape, pop.individuals.shape)
        self.assertEqual(pop.fitness.shape, (100,))

        # should not allow fitness to be greater than 1d
        with self.assertRaises(PopulationFittnessDimensionError):
            pop.fitness = np.zeros((pop.npop, 10))
    def test_individuals(self):
        """
        Updating individuals should force an update of fitness
        """
        pop = Population(individuals=[1, 2, 3], fitness=[0, 0, 0])

        self.assertEqual(pop.npop, 3)

        # resetting individuals should force an update of fit
        pop.individuals = [0, 1, 2]
        with self.assertRaises(PopulationFittnessNotSetError):
            f = pop.fitness

        # npop should always be the length of individuals
        ind0 = np.random.rand(100, 5)
        pop = Population(individuals=ind0)
        self.assertEqual(ind0.shape[0], pop.npop)
        self.assertEqual(ind0.shape[0], len(pop.individuals))
        self.assertEqual(len(ind0), len(pop.individuals))
    def test_mutate(self):
        """
        Should perform mutation on a subset of the population
        """
        ind0 = np.asarray([1., 2., 3., 4.])
        pop = Population(individuals=ind0, fitness=ind0)

        # should change some values
        pop.mutate(p=1.)
        count = 0
        for f0, f1 in zip(ind0, pop.individuals):
            if f0 != f1:
                count += 1
        self.assertGreater(count, 0)

        # should set fitness for changed individuals to nan
        self.assertGreater(len(pop.inew), 0)

        # should handle > 1d
        ind0 = np.random.rand(50, 10)
        pop = Population(individuals=ind0, fitness=range(50))

        pop.mutate(p=1.)
        self.assertGreater(len(pop.inew), 0)

        # should not change shape of individuals
        self.assertEqual(pop.individuals.shape, ind0.shape)
    def test_crossover(self):
        """
        Should perform binary crossover on a subset of the population
        """
        ind0 = np.random.rand(4) 
        fit0 = np.arange(len(ind0))
        pop = Population(individuals=ind0, fitness=fit0)

        # should change some values
        pop.crossover(p=1.)
        count = 0
        for f0, f1 in zip(ind0, pop.individuals):
            if f0 != f1:
                count += 1

        self.assertGreater(count, 0)
        
        # should set fitness for changed individuals to nan
        self.assertGreater(len(pop.inew), 0)
        
        # should handle > 1d
        ind0 = np.random.rand(50, 10)
        pop = Population(individuals=ind0, fitness=range(50))

        pop.crossover(p=1.)
        self.assertGreater(len(pop.inew), 0)

        # should not change shape of individuals
        self.assertEqual(pop.individuals.shape, ind0.shape)

        # FIXME?? fails on np.log10(0) in power calc. in float2bin()
        ## should not produce NaNs
        ind0 = np.round(np.random.rand(100))
        pop = Population(individuals=ind0)
        for p in [0.1, 0.5, 1.]:
            pop.crossover(p=p)
            self.assertEqual(len(np.nonzero(np.isnan(pop.individuals))[0]), 0)
    def test_clone(self):
        """
        Should create a new generation by copying the fittest indivuals in
        the current generation
        """
        fit0 = [1, 2, 3, 4]
        pop = Population(individuals=fit0, fitness=fit0)

        sumfit = []
        for i in range(100):
            pop.clone()
            sumfit.append(np.sum(pop.fitness))

        self.assertGreater(np.max(sumfit), np.sum(fit0))

        # should not change shape of 1d array
        ind0 = np.random.rand(50)
        pop = Population(individuals=ind0, fitness=range(50))
        pop.clone()
        self.assertEqual(pop.individuals.shape, ind0.shape) 

        # should not change shape of multi-d array
        ind0 = np.random.rand(50, 5)
        pop = Population(individuals=ind0, fitness=range(50))
        pop.clone()
        self.assertEqual(pop.individuals.shape, ind0.shape)

        # should not produce NaNs
        ind0 = np.round(np.random.rand(100))
        pop = Population(individuals=ind0)
        for p in range(100):
            pop.clone()
            self.assertEqual(len(np.nonzero(np.isnan(pop.individuals))[0]), 0)