docs: def nsga_replacement(random, population, parents, offspring, args):
      """Replaces population using the non-dominated sorting technique from NSGA-II.
      
      .. Arguments:
         random -- the random number generator object
         population -- the population of individuals
         parents -- the list of parent individuals
         offspring -- the list of offspring individuals
         args -- a dictionary of keyword arguments
      
      """
      survivors = []
      combined = list(population)
      combined.extend(offspring)
      
      # Perform the non-dominated sorting to determine the fronts.
      fronts = []
      pop = set(range(len(combined)))
      while len(pop) > 0:
          front = []
          for p in pop:
              dominated = False
              for q in pop:
                  if combined[p] < combined[q]:
                      dominated = True
                      break
              if not dominated:
                  front.append(p)
          fronts.append([dict(individual=combined[f], index=f) for f in front])
          pop = pop - set(front)
      
      # Go through each front and add all the elements until doing so
      # would put you above the population limit. At that point, fall
      # back to the crowding distance to determine who to put into the
      # next population. Individuals with higher crowding distances
      # (i.e., more distance between neighbors) are preferred.
      for i, front in enumerate(fronts):
          if len(survivors) + len(front) > len(population):
              # Determine the crowding distance.
              distance = [0 for _ in range(len(combined))]
              individuals = list(front)
              num_individuals = len(individuals)
              num_objectives = len(individuals[0]['individual'].fitness)
              for obj in range(num_objectives):
                  individuals.sort(key=lambda x: x['individual'].fitness[obj])
                  distance[individuals[0]['index']] = float('inf')
                  distance[individuals[-1]['index']] = float('inf')
                  for i in range(1, num_individuals-1):
                      distance[individuals[i]['index']] = (distance[individuals[i]['index']] + 
                                                           (individuals[i+1]['individual'].fitness[obj] - 
                                                            individuals[i-1]['individual'].fitness[obj]))
                  
              crowd = [dict(dist=distance[f['index']], index=f['index']) for f in front]
              crowd.sort(key=lambda x: x['dist'], reverse=True)
              last_rank = [combined[c['index']] for c in crowd]
              r = 0
              num_added = 0
              num_left_to_add = len(population) - len(survivors)
              while r < len(last_rank) and num_added < num_left_to_add:
                  if last_rank[r] not in survivors:
                      survivors.append(last_rank[r])
                      num_added += 1
                  r += 1
              # If we've filled out our survivor list, then stop.
              # Otherwise, process the next front in the list.
              if len(survivors) == len(population):
                  break
          else:
              for f in front:
                  if f['individual'] not in survivors:
                      survivors.append(f['individual'])
      return survivors