def eaGenerateUpdate(toolbox, ngen, halloffame=None, stats=None, verbose=__debug__): """This is algorithm implements the ask-tell model proposed in [Colette2010]_, where ask is called `generate` and tell is called `update`. :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. :param ngen: The number of generation. :param stats: A :class:`~deap.tools.Statistics` object that is updated inplace, optional. :param halloffame: A :class:`~deap.tools.HallOfFame` object that will contain the best individuals, optional. :param verbose: Whether or not to log the statistics. :returns: The final population :returns: A class:`~deap.tools.Logbook` with the statistics of the evolution The algorithm generates the individuals using the :func:`toolbox.generate` function and updates the generation method with the :func:`toolbox.update` function. It returns the optimized population and a :class:`~deap.tools.Logbook` with the statistics of the evolution. The logbook will contain the generation number, the number of evalutions for each generation and the statistics if a :class:`~deap.tools.Statistics` is given as argument. The pseudocode goes as follow :: for g in range(ngen): population = toolbox.generate() evaluate(population) toolbox.update(population) .. [Colette2010] Collette, Y., N. Hansen, G. Pujol, D. Salazar Aponte and R. Le Riche (2010). On Object-Oriented Programming of Optimizers - Examples in Scilab. In P. Breitkopf and R. F. Coelho, eds.: Multidisciplinary Design Optimization in Computational Mechanics, Wiley, pp. 527-565; """ logbook = tools.Logbook() logbook.header = ['gen', 'nevals'] + (stats.fields if stats else []) for gen in xrange(ngen): # Generate a new population population = toolbox.generate() # Evaluate the individuals fitnesses = toolbox.map(toolbox.evaluate, population) for ind, fit in zip(population, fitnesses): ind.fitness.values = fit if halloffame is not None: halloffame.update(population) # Update the strategy with the evaluated individuals toolbox.update(population) record = stats.compile(population) if stats is not None else {} logbook.record(gen=gen, nevals=len(population), **record) if verbose: print logbook.stream return population, logbook
def test_pickle_logbook(self): stats = tools.Statistics() logbook = tools.Logbook() stats.register("mean", numpy.mean) record = stats.compile([1,2,3,4,5,6,8,9,10]) logbook.record(**record) stats_s = pickle.dumps(logbook) logbook_r = pickle.loads(stats_s) self.assertEqual(logbook, logbook_r, "Unpickled logbook != pickled logbook")
def eaMuCommaLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, stats=None, halloffame=None, verbose=__debug__): assert lambda_ >= mu, "lambda must be greater or equal to mu." invalid_ind = [ind for ind in population if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit if halloffame is not None: halloffame.update(population) logbook = tools.Logbook() logbook.header = ['gen', 'nevals'] + (stats.fields if stats else []) record = stats.compile(population) if stats is not None else {} logbook.record(gen=0, nevals=len(invalid_ind), **record) if verbose: print logbook.stream for gen in range(1, ngen + 1): offspring = varOr(population, toolbox, lambda_, cxpb, mutpb) invalid_ind = [ind for ind in offspring if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit if halloffame is not None: halloffame.update(offspring) population[:] = toolbox.select(offspring, mu) record = stats.compile(population) if stats is not None else {} logbook.record(gen=gen, nevals=len(invalid_ind), **record) if verbose: print logbook.stream return population, logbook
def eaGenerateUpdate(toolbox, ngen, halloffame=None, stats=None, verbose=__debug__): logbook = tools.Logbook() logbook.header = ['gen', 'nevals'] + (stats.fields if stats else []) for gen in xrange(ngen): population = toolbox.generate() fitnesses = toolbox.map(toolbox.evaluate, population) for ind, fit in zip(population, fitnesses): ind.fitness.values = fit if halloffame is not None: halloffame.update(population) toolbox.update(population) record = stats.compile(population) if stats is not None else {} logbook.record(gen=gen, nevals=len(population), **record) if verbose: print logbook.stream return population, logbook
def eaSimple(population, toolbox, cxpb, mutpb, ngen, stats=None, halloffame=None, verbose=__debug__): """This algorithm reproduce the simplest evolutionary algorithm as presented in chapter 7 of [Back2000]_. :param population: A list of individuals. :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. :param cxpb: The probability of mating two individuals. :param mutpb: The probability of mutating an individual. :param ngen: The number of generation. :param stats: A :class:`~deap.tools.Statistics` object that is updated inplace, optional. :param halloffame: A :class:`~deap.tools.HallOfFame` object that will contain the best individuals, optional. :param verbose: Whether or not to log the statistics. :returns: The final population :returns: A class:`~deap.tools.Logbook` with the statistics of the evolution The algorithm takes in a population and evolves it in place using the :meth:`varAnd` method. It returns the optimized population and a :class:`~deap.tools.Logbook` with the statistics of the evolution. The logbook will contain the generation number, the number of evalutions for each generation and the statistics if a :class:`~deap.tools.Statistics` is given as argument. The *cxpb* and *mutpb* arguments are passed to the :func:`varAnd` function. The pseudocode goes as follow :: evaluate(population) for g in range(ngen): population = select(population, len(population)) offspring = varAnd(population, toolbox, cxpb, mutpb) evaluate(offspring) population = offspring As stated in the pseudocode above, the algorithm goes as follow. First, it evaluates the individuals with an invalid fitness. Second, it enters the generational loop where the selection procedure is applied to entirely replace the parental population. The 1:1 replacement ratio of this algorithm **requires** the selection procedure to be stochastic and to select multiple times the same individual, for example, :func:`~deap.tools.selTournament` and :func:`~deap.tools.selRoulette`. Third, it applies the :func:`varAnd` function to produce the next generation population. Fourth, it evaluates the new individuals and compute the statistics on this population. Finally, when *ngen* generations are done, the algorithm returns a tuple with the final population and a :class:`~deap.tools.Logbook` of the evolution. .. note:: Using a non-stochastic selection method will result in no selection as the operator selects *n* individuals from a pool of *n*. This function expects the :meth:`toolbox.mate`, :meth:`toolbox.mutate`, :meth:`toolbox.select` and :meth:`toolbox.evaluate` aliases to be registered in the toolbox. .. [Back2000] Back, Fogel and Michalewicz, "Evolutionary Computation 1 : Basic Algorithms and Operators", 2000. """ logbook = tools.Logbook() logbook.header = ['gen', 'nevals'] + (stats.fields if stats else []) # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in population if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit if halloffame is not None: halloffame.update(population) record = stats.compile(population) if stats else {} logbook.record(gen=0, nevals=len(invalid_ind), **record) if verbose: print logbook.stream # Begin the generational process for gen in range(1, ngen + 1): # Select the next generation individuals offspring = toolbox.select(population, len(population)) # Vary the pool of individuals offspring = varAnd(offspring, toolbox, cxpb, mutpb) # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit # Update the hall of fame with the generated individuals if halloffame is not None: halloffame.update(offspring) # Replace the current population by the offspring population[:] = offspring # Append the current generation statistics to the logbook record = stats.compile(population) if stats else {} logbook.record(gen=gen, nevals=len(invalid_ind), **record) if verbose: print logbook.stream return population, logbook
def eaMuCommaLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, stats=None, halloffame=None, verbose=__debug__): """This is the :math:`(\mu~,~\lambda)` evolutionary algorithm. :param population: A list of individuals. :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. :param mu: The number of individuals to select for the next generation. :param lambda\_: The number of children to produce at each generation. :param cxpb: The probability that an offspring is produced by crossover. :param mutpb: The probability that an offspring is produced by mutation. :param ngen: The number of generation. :param stats: A :class:`~deap.tools.Statistics` object that is updated inplace, optional. :param halloffame: A :class:`~deap.tools.HallOfFame` object that will contain the best individuals, optional. :param verbose: Whether or not to log the statistics. :returns: The final population :returns: A class:`~deap.tools.Logbook` with the statistics of the evolution The algorithm takes in a population and evolves it in place using the :func:`varOr` function. It returns the optimized population and a :class:`~deap.tools.Logbook` with the statistics of the evolution. The logbook will contain the generation number, the number of evalutions for each generation and the statistics if a :class:`~deap.tools.Statistics` is given as argument. The *cxpb* and *mutpb* arguments are passed to the :func:`varOr` function. The pseudocode goes as follow :: evaluate(population) for g in range(ngen): offspring = varOr(population, toolbox, lambda_, cxpb, mutpb) evaluate(offspring) population = select(offspring, mu) First, the individuals having an invalid fitness are evaluated. Second, the evolutionary loop begins by producing *lambda_* offspring from the population, the offspring are generated by the :func:`varOr` function. The offspring are then evaluated and the next generation population is selected from **only** the offspring. Finally, when *ngen* generations are done, the algorithm returns a tuple with the final population and a :class:`~deap.tools.Logbook` of the evolution. .. note:: Care must be taken when the lambda:mu ratio is 1 to 1 as a non-stochastic selection will result in no selection at all as the operator selects *lambda* individuals from a pool of *mu*. This function expects :meth:`toolbox.mate`, :meth:`toolbox.mutate`, :meth:`toolbox.select` and :meth:`toolbox.evaluate` aliases to be registered in the toolbox. This algorithm uses the :func:`varOr` variation. """ assert lambda_ >= mu, "lambda must be greater or equal to mu." # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in population if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit if halloffame is not None: halloffame.update(population) logbook = tools.Logbook() logbook.header = ['gen', 'nevals'] + (stats.fields if stats else []) record = stats.compile(population) if stats is not None else {} logbook.record(gen=0, nevals=len(invalid_ind), **record) if verbose: print logbook.stream # Begin the generational process for gen in range(1, ngen + 1): # Vary the population offspring = varOr(population, toolbox, lambda_, cxpb, mutpb) # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit # Update the hall of fame with the generated individuals if halloffame is not None: halloffame.update(offspring) # Select the next generation population population[:] = toolbox.select(offspring, mu) # Update the statistics with the new population record = stats.compile(population) if stats is not None else {} logbook.record(gen=gen, nevals=len(invalid_ind), **record) if verbose: print logbook.stream return population, logbook
def harm(population, toolbox, cxpb, mutpb, ngen, alpha, beta, gamma, rho, nbrindsmodel=-1, mincutoff=20, stats=None, halloffame=None, verbose=__debug__): """Implement bloat control on a GP evolution using HARM-GP, as defined in [Gardner2015]. It is implemented in the form of an evolution algorithm (similar to :func:`~deap.algorithms.eaSimple`). :param population: A list of individuals. :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. :param cxpb: The probability of mating two individuals. :param mutpb: The probability of mutating an individual. :param ngen: The number of generation. :param alpha: The HARM *alpha* parameter. :param beta: The HARM *beta* parameter. :param gamma: The HARM *gamma* parameter. :param rho: The HARM *rho* parameter. :param nbrindsmodel: The number of individuals to generate in order to model the natural distribution. -1 is a special value which uses the equation proposed in [Gardner2015] to set the value of this parameter : max(2000, len(population)) :param mincutoff: The absolute minimum value for the cutoff point. It is used to ensure that HARM does not shrink the population too much at the beginning of the evolution. The default value is usually fine. :param stats: A :class:`~deap.tools.Statistics` object that is updated inplace, optional. :param halloffame: A :class:`~deap.tools.HallOfFame` object that will contain the best individuals, optional. :param verbose: Whether or not to log the statistics. :returns: The final population :returns: A class:`~deap.tools.Logbook` with the statistics of the evolution This function expects the :meth:`toolbox.mate`, :meth:`toolbox.mutate`, :meth:`toolbox.select` and :meth:`toolbox.evaluate` aliases to be registered in the toolbox. .. note:: The recommended values for the HARM-GP parameters are *alpha=0.05*, *beta=10*, *gamma=0.25*, *rho=0.9*. However, these parameters can be adjusted to perform better on a specific problem (see the relevant paper for tuning information). The number of individuals used to model the natural distribution and the minimum cutoff point are less important, their default value being effective in most cases. .. [Gardner2015] M.-A. Gardner, C. Gagne, and M. Parizeau, Controlling Code Growth by Dynamically Shaping the Genotype Size Distribution, Genetic Programming and Evolvable Machines, 2015, DOI 10.1007/s10710-015-9242-8 """ def _genpop(n, pickfrom=[], acceptfunc=lambda s: True, producesizes=False): # Generate a population of n individuals, using individuals in # *pickfrom* if possible, with a *acceptfunc* acceptance function. # If *producesizes* is true, also return a list of the produced # individuals sizes. # This function is used 1) to generate the natural distribution # (in this case, pickfrom and acceptfunc should be let at their # default values) and 2) to generate the final population, in which # case pickfrom should be the natural population previously generated # and acceptfunc a function implementing the HARM-GP algorithm. producedpop = [] producedpopsizes = [] while len(producedpop) < n: if len(pickfrom) > 0: # If possible, use the already generated # individuals (more efficient) aspirant = pickfrom.pop() if acceptfunc(len(aspirant)): producedpop.append(aspirant) if producesizes: producedpopsizes.append(len(aspirant)) else: opRandom = random.random() if opRandom < cxpb: # Crossover aspirant1, aspirant2 = toolbox.mate( *map(toolbox.clone, toolbox.select(population, 2))) del aspirant1.fitness.values, aspirant2.fitness.values if acceptfunc(len(aspirant1)): producedpop.append(aspirant1) if producesizes: producedpopsizes.append(len(aspirant1)) if len(producedpop) < n and acceptfunc(len(aspirant2)): producedpop.append(aspirant2) if producesizes: producedpopsizes.append(len(aspirant2)) else: aspirant = toolbox.clone(toolbox.select(population, 1)[0]) if opRandom - cxpb < mutpb: # Mutation aspirant = toolbox.mutate(aspirant)[0] del aspirant.fitness.values if acceptfunc(len(aspirant)): producedpop.append(aspirant) if producesizes: producedpopsizes.append(len(aspirant)) if producesizes: return producedpop, producedpopsizes else: return producedpop halflifefunc = lambda x: (x * float(alpha) + beta) if nbrindsmodel == -1: nbrindsmodel = max(2000, len(population)) logbook = tools.Logbook() logbook.header = ['gen', 'nevals'] + (stats.fields if stats else []) # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in population if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit if halloffame is not None: halloffame.update(population) record = stats.compile(population) if stats else {} logbook.record(gen=0, nevals=len(invalid_ind), **record) if verbose: print logbook.stream # Begin the generational process for gen in range(1, ngen + 1): # Estimation population natural distribution of sizes naturalpop, naturalpopsizes = _genpop(nbrindsmodel, producesizes=True) naturalhist = [0] * (max(naturalpopsizes) + 3) for indsize in naturalpopsizes: # Kernel density estimation application naturalhist[indsize] += 0.4 naturalhist[indsize - 1] += 0.2 naturalhist[indsize + 1] += 0.2 naturalhist[indsize + 2] += 0.1 if indsize - 2 >= 0: naturalhist[indsize - 2] += 0.1 # Normalization naturalhist = [ val * len(population) / nbrindsmodel for val in naturalhist ] # Cutoff point selection sortednatural = sorted(naturalpop, key=lambda ind: ind.fitness) cutoffcandidates = sortednatural[int(len(population) * rho - 1):] # Select the cutoff point, with an absolute minimum applied # to avoid weird cases in the first generations cutoffsize = max(mincutoff, len(min(cutoffcandidates, key=len))) # Compute the target distribution targetfunc = lambda x: (gamma * len(population) * math.log( 2) / halflifefunc(x)) * math.exp(-math.log(2) * (x - cutoffsize) / halflifefunc(x)) targethist = [ naturalhist[binidx] if binidx <= cutoffsize else targetfunc(binidx) for binidx in range(len(naturalhist)) ] # Compute the probabilities distribution probhist = [ t / n if n > 0 else t for n, t in zip(naturalhist, targethist) ] probfunc = lambda s: probhist[s] if s < len(probhist) else targetfunc(s ) acceptfunc = lambda s: random.random() <= probfunc(s) # Generate offspring using the acceptance probabilities # previously computed offspring = _genpop(len(population), pickfrom=naturalpop, acceptfunc=acceptfunc, producesizes=False) # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit # Update the hall of fame with the generated individuals if halloffame is not None: halloffame.update(offspring) # Replace the current population by the offspring population[:] = offspring # Append the current generation statistics to the logbook record = stats.compile(population) if stats else {} logbook.record(gen=gen, nevals=len(invalid_ind), **record) if verbose: print logbook.stream return population, logbook
def setUp(self): self.logbook = tools.Logbook() print
def eaMuCommaLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen, stats=None, halloffame=None, verbose=__debug__): """This is the :math:`(\mu~,~\lambda)` evolutionary algorithm. :param population: A list of individuals. :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. :param mu: The number of individuals to select for the next generation. :param lambda\_: The number of children to produce at each generation. :param cxpb: The probability that an offspring is produced by crossover. :param mutpb: The probability that an offspring is produced by mutation. :param ngen: The number of generation. :param stats: A :class:`~deap.tools.Statistics` object that is updated inplace, optional. :param halloffame: A :class:`~deap.tools.HallOfFame` object that will contain the best individuals, optional. :param verbose: Whether or not to log the statistics. :returns: The final population. First, the individuals having an invalid fitness are evaluated. Then, the evolutionary loop begins by producing *lambda_* offspring from the population, the offspring are generated by a crossover, a mutation or a reproduction proportionally to the probabilities *cxpb*, *mutpb* and 1 - (cxpb + mutpb). The offspring are then evaluated and the next generation population is selected **only** from the offspring. Briefly, the operators are applied as following :: evaluate(population) for i in range(ngen): offspring = varOr(population, toolbox, lambda_, cxpb, mutpb) evaluate(offspring) population = select(offspring, mu) This function expects :meth:`toolbox.mate`, :meth:`toolbox.mutate`, :meth:`toolbox.select` and :meth:`toolbox.evaluate` aliases to be registered in the toolbox. This algorithm uses the :func:`varOr` variation. """ assert lambda_ >= mu, "lambda must be greater or equal to mu." # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in population if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit if halloffame is not None: halloffame.update(population) logbook = tools.Logbook() logbook.header = ['gen', 'nevals'] + (stats.fields if stats else []) record = stats.compile(population) if stats is not None else {} logbook.record(gen=0, nevals=len(invalid_ind), **record) if verbose: print logbook.stream # Begin the generational process for gen in range(1, ngen + 1): # Vary the population offspring = varOr(population, toolbox, lambda_, cxpb, mutpb) # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit # Update the hall of fame with the generated individuals if halloffame is not None: halloffame.update(offspring) # Select the next generation population population[:] = toolbox.select(offspring, mu) # Update the statistics with the new population record = stats.compile(population) if stats is not None else {} logbook.record(gen=gen, nevals=len(invalid_ind), **record) if verbose: print logbook.stream return population, logbook
def eaSimple(population, toolbox, cxpb, mutpb, ngen, stats=None, halloffame=None, verbose=__debug__): """This algorithm reproduce the simplest evolutionary algorithm as presented in chapter 7 of [Back2000]_. :param population: A list of individuals. :param toolbox: A :class:`~DEAP.base.Toolbox` that contains the evolution operators. :param cxpb: The probability of mating two individuals. :param mutpb: The probability of mutating an individual. :param ngen: The number of generation. :param stats: A :class:`~DEAP.tools.Statistics` object that is updated inplace, optional. :param halloffame: A :class:`~DEAP.tools.HallOfFame` object that will contain the best individuals, optional. :param verbose: Whether or not to log the statistics. :returns: The final population. It uses :math:`\lambda = \kappa = \mu` and goes as follow. It first initializes the population (:math:`P(0)`) by evaluating every individual presenting an invalid fitness. Then, it enters the evolution loop that begins by the selection of the :math:`P(g+1)` population. Then the crossover operator is applied on a proportion of :math:`P(g+1)` according to the *cxpb* probability, the resulting and the untouched individuals are placed in :math:`P'(g+1)`. Thereafter, a proportion of :math:`P'(g+1)`, determined by *mutpb*, is mutated and placed in :math:`P''(g+1)`, the untouched individuals are transferred :math:`P''(g+1)`. Finally, those new individuals are evaluated and the evolution loop continues until *ngen* generations are completed. Briefly, the operators are applied in the following order :: evaluate(population) for i in range(ngen): offspring = select(population) offspring = mate(offspring) offspring = mutate(offspring) evaluate(offspring) population = offspring This function expects :meth:`toolbox.mate`, :meth:`toolbox.mutate`, :meth:`toolbox.select` and :meth:`toolbox.evaluate` aliases to be registered in the toolbox. .. [Back2000] Back, Fogel and Michalewicz, "Evolutionary Computation 1 : Basic Algorithms and Operators", 2000. """ logbook = tools.Logbook() logbook.header = ['gen', 'nevals'] + (stats.fields if stats else []) # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in population if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit if halloffame is not None: halloffame.update(population) record = stats.compile(population) if stats else {} logbook.record(gen=0, nevals=len(invalid_ind), **record) if verbose: print logbook.stream # Begin the generational process for gen in range(1, ngen + 1): # Select the next generation individuals offspring = toolbox.select(population, len(population)) # Vary the pool of individuals offspring = varAnd(offspring, toolbox, cxpb, mutpb) # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if not ind.fitness.valid] fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness.values = fit # Update the hall of fame with the generated individuals if halloffame is not None: halloffame.update(offspring) # Replace the current population by the offspring population[:] = offspring # Append the current generation statistics to the logbook record = stats.compile(population) if stats else {} logbook.record(gen=gen, nevals=len(invalid_ind), **record) if verbose: print logbook.stream return population, logbook