def evolve_ga (G_pop, perf_fn, reproduce_fn, max_iter=1000,
              reporter=None):
    """ Evolves a population of networks using a genetic algorithm.

    Outputs the evolved population with the accociated performance values.

    Parameters
    ----------
    G_pop : list(NetworkX graph)
        Initial network population.
    
    perf_fn : function
        Performance function to evalulate each candidate network. Lower 
        performance values are better - evolution minimizes.
        
    reproduce_fn : function
        Function to generate new candidate networks from an existing
        population with performance values.

    max_iter : int (default = 1000)
        Maximum number of iterations (generations) to produce.

    reporter : function (optional default=None)
        Optional reporter called after each evolutionary step.
    """
    # Copy the population (we don't make changes to the initial one)
    cur_pop_perf = []
    for g in G_pop:
        cur_pop_perf.append([g, 0.0])
    for it in range(0, max_iter):
        # Calculate the performance
        perf_fn(cur_pop_perf)
        # Report the current performance
        if reporter != None:
            reporter(cur_pop_perf, it)
        # Mate the graphs and update the current population
        cur_pop_perf = reproduce_fn(cur_pop_perf)
    # Report the final performance
    if reporter != None:
        reporter(cur_pop_perf, max_iter)
    return cur_pop_perf