Project: 039_A_Theoretically-Principled_Sparse_Connected_and_Ri | Function: project_sphere (iclr)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Projects 3D points onto unit sphere centered at center of points, implementing Step 1 of SCHull algorithm

**KEY TERMS:** {
  "data/np.linalg.norm(data, axis=1, keepdims=True)": "px(x) = (x−x̄)/||x−x̄||",
  "temp": "projected points on unit sphere",
  "distances": "||x−x̄||"
}

**CONTEXT CODE:**                             #     print(projection[i-1])
                    ### modified by hyh: save two angles ###
                else:
                    angle += [(0, 0)]


            # lexicographical shift
            ### modified by hyh ###
            # lst = [(custom_round(a,self.tol), custom_round(d, self.tol)) for a,d in zip(angle, d_ij)]
            lst = {}
            ct = 0
            for angles, d in zip(angle, d_ij):
                # lst.append(
                #         (   
                #             d,
                #             (
                #                 custom_round(angles[0], self.tol), 
                #                 custom_round(angles[1], self.tol)
                #             ),
                #             (point, adj_list[point][ct]) 
                #         )
                #     )
                lst[adj_list[point][ct]] = (
                                            d,
                                            (
                                                custom_round(angles[0], self.tol), 
                                                custom_round(angles[1], self.tol)
                                            )
                                            )
                ct += 1
            s_feature[point] = lst

            # lst = tuple(list_rotate(lst))
            # if lst not in g_hash:
            #     g_hash[lst] = id(lst)
            # encoding[point] = g_hash[lst]   
            g_hash = None
            encoding = None

        return g_hash, encoding, s_feature


    def check_type(self, data, *args, **kwargs):
        if isinstance(data, torch.Tensor):
            return data.detach().cpu().numpy()
        elif isinstance(data, np.ndarray):
            return data
        else:
            raise TypeError(f"Data type not supported {type(data)}")

    def project_sphere(self, data, cat_data=None, *args, **kwargs):

        raise NotImplementedError("Function implementation has been masked for testing")
    
    def get_recover_adj(self,
                        adj_list,
                        shell_data_proj_id_rcrd):
        # step one
        recover_adj_list_1 = {}
        for key in adj_list:
            recover_key = shell_data_proj_id_rcrd[key]
            for k in range(len(recover_key)):
                recover_adj_list_1[recover_key[k]] = adj_list[key]
        
        recover_adj_list_2 = {}
        for key in recover_adj_list_1:
            lst = recover_adj_list_1[key]
            temp = []
            for k in range(len(lst)):
                temp += shell_data_proj_id_rcrd[lst[k]]
            temp.sort()
            recover_adj_list_2[key] = temp
        
        recover_adj_list_2 = dict(sorted(recover_adj_list_2.items()))
        # for key in recover_adj_list_2:
        #     recover_adj_list_2[key].sort()
        return recover_adj_list_2
    
    ### modified by hyh ###
    def get_merged_edge_index(self,
                              adj_list,
                              shell_data_proj_id_rcrd,
                              data_edge_index):
        # step one
        recover_adj_list_1 = {}
        for key in adj_list:
            recover_key = shell_data_proj_id_rcrd[key]
            for k in range(len(recover_key)):
                recover_adj_list_1[recover_key[k]] = adj_list[key]
        
        recover_adj_list_2 = {}
        for key in recover_adj_list_1:
            lst = recover_adj_list_1[key]
            temp = []
            for k in range(len(lst)):
                temp += shell_data_proj_id_rcrd[lst[k]]
            recover_adj_list_2[key] = temp

        edge_node = np.unique(data_edge_index[0])
        data_edge_index_list = {}
        for k in range(len(edge_node)):
            data_edge_index_list[edge_node[k]] = []
        for k in range(len(data_edge_index[0])):
            data_edge_index_list[int(data_edge_index[0][k])].append(int(data_edge_index[1][k]))

        for key in recover_adj_list_2:
            lst = data_edge_index_list[key]
            for ik in range(len(lst)):
                if lst[ik] not in recover_adj_list_2[key]:
                    recover_adj_list_2[key].append(lst[ik])
        
        return recover_adj_list_2
    
    def merge_coord_info(self, 
                         data, s_feature, 
                         shell_data_proj_id_rcrd):
        new_coord_fea = {}
        for key in shell_data_proj_id_rcrd:
            for k in range(len(shell_data_proj_id_rcrd[key])):
                key_ = shell_data_proj_id_rcrd[key][k]
                new_coord_fea[key_] = {'R': np.linalg.norm(data[key_])}

        return new_coord_fea

    def get_radial_arr(self, data):
        radial_arr = []
        for i in range(len(data)):
            radial_arr.append(np.linalg.norm(data[i]))
        return radial_arr

    def adj_arr(self, adj_list):
        arr = [[], []]
        for key in adj_list:
            temp = adj_list[key].copy()
            for k in range(len(temp)):
                arr[0].append(int(key))
                arr[1].append(int(temp[k]))
        return arr
    
    def edge_attr_arr(self, s_feature, 
                      proj_id_rcrd_rvrs,
                      edge_index_hull):

        attr_arr = []
        for i in range(len(edge_index_hull[0])):
            key1 = proj_id_rcrd_rvrs[edge_index_hull[0][i]]
            key2 = proj_id_rcrd_rvrs[edge_index_hull[1][i]]
            temp = s_feature[key1][key2]
            # attr_arr.append(
            #         [temp[0], 


Complete the function implementation replacing "raise NotImplementedError".