docs: def cross(self,p_i,p_j, max_depth = 2):
          """subtree-like swap crossover between programs p_i and p_j."""
          # only choose crossover points for out_types available in both programs
          # pdb.set_trace()
          # determine possible outttypes
          types_p_i = [t for t in [p.out_type for p in p_i]]
          types_p_j = [t for t in [p.out_type for p in p_j]]
          types = set(types_p_i).intersection(types_p_j)
  
          # grab subtree of p_i
          p_i_sub = [i for i,n in enumerate(p_i) if n.out_type in types]
          x_i_end = self.random_state.choice(p_i_sub)
          x_i_begin = x_i_end
          arity_sum = p_i[x_i_end].arity[p_i[x_i_end].in_type]
          # print("x_i_end:",x_i_end)
          # i = 0
          while (arity_sum > 0): #and i < 1000:
              if x_i_begin == 0:
                  print("arity_sum:",arity_sum,"x_i_begin:",x_i_begin,"x_i_end:",x_i_end)
              x_i_begin -= 1
              arity_sum += p_i[x_i_begin].arity[p_i[x_i_begin].in_type]-1
          #     i += 1
          # if i == 1000:
          #     print("in variation")
          #     pdb.set_trace()
  
          # grab subtree of p_j with matching out_type to p_i[x_i_end]
          p_j_sub = [i for i,n in enumerate(p_j) if n.out_type == p_i[x_i_end].out_type]
          x_j_end = self.random_state.choice(p_j_sub)
          x_j_begin = x_j_end
          arity_sum = p_j[x_j_end].arity[p_j[x_j_end].in_type]
          # i = 0
          while (arity_sum > 0): #and i < 1000:
              if x_j_begin == 0:
                  print("arity_sum:",arity_sum,"x_j_begin:",x_j_begin,"x_j_end:",x_j_end)
                  print("p_j:",p_j)
              x_j_begin -= 1
              arity_sum += p_j[x_j_begin].arity[p_j[x_j_begin].in_type]-1
          #     i += 1
          # if i == 1000:
          #     print("in variation")
          #     pdb.set_trace()
          #swap subtrees
          tmpi = p_i[:]
          tmpj = p_j[:]
          tmpi[x_i_begin:x_i_end+1:],tmpj[x_j_begin:x_j_end+1:] = \
                          tmpj[x_j_begin:x_j_end+1:],tmpi[x_i_begin:x_i_end+1:]
  
          if not self.is_valid_program(p_i) or not self.is_valid_program(p_j):
              # pdb.set_trace()
              print("parent 1:",p_i,"x_i_begin:",x_i_begin,"x_i_end:",x_i_end)
              print("parent 2:",p_j,"x_j_begin:",x_j_begin,"x_j_end:",x_j_end)
              print("child 1:",tmpi)
              print("child 2:",tmpj)
              raise ValueError('Crossover produced an invalid program.')
  
          # size check, then assignment
          if len(tmpi) <= 2**max_depth-1:
              p_i[:] = tmpi
          if len(tmpj) <= 2**max_depth-1:
              p_j[:] = tmpj