docs: def deterministic_crowding(self,parents,offspring,X_parents,X_offspring):
          """deterministic crowding implementation (for non-steady state).
          offspring compete against the parent they are most similar to, here defined as
          the parent they are most correlated with.
          the offspring only replace their parent if they are more fit.
          """
          # get children locations produced from crossover
          cross_children = [i for i,o in enumerate(offspring) if len(o.parentid) > 1]
          # order offspring so that they are lined up with their most similar parent
          for c1,c2 in zip(cross_children[::2], cross_children[1::2]):
              # get parent locations
              p_loc = [j for j,p in enumerate(parents) if p.id in offspring[c1].parentid]
              if len(p_loc) != 2:
                  continue
              # if child is more correlated with its non-root parent
              if r2_score(X_parents[p_loc[0]],X_offspring[c1]) + r2_score(X_parents[p_loc[1]],X_offspring[c2]) < r2_score(X_parents[p_loc[0]],X_offspring[c2]) + r2_score(X_parents[p_loc[1]],X_offspring[c1]):
                  # swap offspring
                  offspring[c1],offspring[c2] = offspring[c2],offspring[c1]
  
          survivors = []
          survivor_index = []
  
          for i,(p,o) in enumerate(zip(parents,offspring)):
              if p.fitness >= o.fitness:
                  survivors.append(copy.deepcopy(p))
                  survivor_index.append(i)
              else:
                  survivors.append(copy.deepcopy(o))
                  survivor_index.append(i+len(parents))
  
          # return survivors along with their indices
          return survivors, survivor_index