docs: def reproduce(self, survivors, p_crossover, two_point_crossover=False, target_size=None):
          """
          Reproduces the population from a pool of surviving chromosomes
          until a target population size is met. Offspring are created
          by selecting a survivor. Survivors with higher fitness have a
          greater chance to be selected for reproduction.
          
          Genetic crossover events may occur for each offspring created.
          Crossover mates are randomly selected from the pool of survivors.
          Crossover points are randomly selected from the length of the crossed chromosomes.
          If crossover does not occur, an offspring is an exact copy of the selected survivor.
          Crossover only affects the DNA of the offspring, not the survivors/parents.
          
          survivors:  pool of parent chromosomes to reproduce from
          p_crossover:  probability in [0, 1] that a crossover event will
                        occur for each offspring
          two_point_crossover (default=False):  whether 2-point crossover is used; default is 1-point
          target_size (default=original population size): target population size
          
          return:  list of survivors plus any offspring
          """
          assert 0 <= p_crossover <= 1
          
          if not target_size:
              target_size = self.orig_pop_size
              
          num_survivors = len(survivors)
      
          # compute reproduction cumulative probabilities
          # weakest member gets p=0 but can be crossed-over with
          cdf = compute_fitness_cdf(survivors, self)
          
          offspring = []
          while num_survivors + len(offspring) < target_size:
              # pick a survivor to reproduce
              c1 = weighted_choice(survivors, cdf).copy()
              
              # crossover
              if random.random() < p_crossover:
                  # randomly pick a crossover mate from survivors
                  # same chromosome can be c1 and c2
                  c2 = random.choice(survivors).copy()
                  point1 = random.randrange(0, c1.length)
                  point2 = random.randrange(point1 + 1, c1.length + 1) if two_point_crossover else None
                  c1.crossover(c2, point1, point2)
                  
              offspring.append(c1)
              
          return survivors + offspring