//  [python]
def _scat_prefixes_2_paths(self, paths, paths_tmp):
        """
        Extracts valid prefixes as invidual paths.

        Input
        ------
        paths : :class:`~sionna.rt.Paths`
            Structure storing the scattered paths.

        paths_tmp : :class:`~sionna.rt.PathsTmpData`
            Addtional quantities required for paths computation

        Output
        -------
        paths : :class:`~sionna.rt.Paths`
            Updates paths.

        paths_tmp : :class:`~sionna.rt.PathsTmpData`
            Updated addtional quantities required for paths computation
        """

        # [max_depth, num_targets, num_sources, max_num_paths]
        prefix_mask = paths_tmp.scat_prefix_mask
        prefix_mask_int = t.cast(prefix_mask, t.int32)
        # [max_depth, num_targets, num_sources, max_num_paths, 3]
        prefix_vertices = paths.vertices
        # [max_depth, num_targets, num_sources, max_num_paths]
        prefix_objects = paths.objects
        # [num_targets, num_sources, max_num_paths]
        prefix_theta_t = paths.theta_t
        # [num_targets, num_sources, max_num_paths]
        prefix_phi_t = paths.phi_t
        # [max_depth, num_targets, num_sources, num_paths, 3]
        prefix_normals = paths_tmp.normals
        # [max_depth + 1, num_targets, num_sources, num_paths, 3]
        prefix_k_i = paths_tmp.k_i
        # [max_depth, num_targets, num_sources, num_paths, 3]
        prefix_k_r = paths_tmp.k_r
        # [max_depth, num_targets, num_sources, num_paths]
        prefix_distances = paths_tmp.total_distance
        # [num_targets, num_sources, max_num_paths, 3]
        prefix_ktx = paths_tmp.k_tx

        max_depth = tf.shape(prefix_mask)[0]
        max_depth64 = tf.cast(max_depth, tf.int64)
        num_targets = tf.shape(prefix_mask)[1]
        num_sources = tf.shape(prefix_mask)[2]

        # Number of paths for each link and depth
        # [max_depth, num_targets, num_sources]
        paths_count = tf.reduce_sum(prefix_mask_int, axis=3)
        # Maximum number of paths for each depth over all the links
        # [max_depth]
        path_count_depth = tf.reduce_max(paths_count, axis=(1,2))
        # Upper bound on the total number of paths
        # ()
        max_num_paths = tf.reduce_sum(path_count_depth)

        # [num_valid_paths, 4]
        gather_indices = tf.where(prefix_mask)
        # To build the indices of the paths in the tensor with optimized size,
        # the path dimension is indexed by counting the valid path in the order
        # in which they appear
        # [max_depth, num_targets, num_sources, num_paths]
        path_indices = tf.cumsum(prefix_mask_int, axis=-1)
        # [num_valid_paths, 4]
        path_indices = tf.gather_nd(path_indices, gather_indices) - 1
        scatter_indices = tf.transpose(gather_indices, [1,0])
        if not tf.size(scatter_indices) == 0:
            scatter_indices = tf.tensor_scatter_nd_update(scatter_indices,
                                [[3]], [path_indices])
        # [num_valid_paths, 3]
        scatter_indices = tf.transpose(scatter_indices, [1,0])

        # Create the final tensors to update
        # [num_targets, num_sources, max_num_paths]
        mask = tf.fill([num_targets, num_sources, max_num_paths], False)
        # This tensor is created transposed as paths
        # are added with all the objects hit along the paths.
        # [num_targets, num_sources, max_num_paths, max_depth, 3]
        vertices = tf.zeros([num_targets, num_sources, max_num_paths, max_depth,
                             3], self._rdtype)
        # Last vertices that were hit
        # [num_targets, num_sources, max_num_paths, 3]
        last_vertices = tf.zeros([num_targets, num_sources, max_num_paths, 3],
                                 self._rdtype)
        # Objects that were hit. This tensor is created transposed as paths
        # are added with all the objects hit along the paths.
        # [num_targets, num_sources, max_num_paths, max_depth]
        objects = tf.fill([num_targets, num_sources, max_num_paths, max_depth],
                          -1)
        # Last objects that were hit
        # [num_targets, num_sources, max_num_paths]
        last_objects = tf.fill([num_targets, num_sources, max_num_paths], -1)
        # Angles of departure
        # [num_targets, num_sources, max_num_paths]
        theta_t = tf.zeros([num_targets, num_sources, max_num_paths],
                           self._rdtype)
        # [num_targets, num_sources, max_num_paths]
        phi_t = tf.zeros([num_targets, num_sources, max_num_paths],
                         self._rdtype)
        # Normal to the last intersected objects
        # [num_targets, num_sources, max_num_paths, 3]
        last_normals = tf.zeros([num_targets, num_sources, max_num_paths, 3],
                                self._rdtype)
        # Direction of incidence at the last interaction point
        # [num_targets, num_sources, max_num_paths, 3]
        last_k_i = tf.zeros([num_targets, num_sources, max_num_paths, 3],
                                self._rdtype)
        # Distance from the sources to the last interaction point
        # [num_targets, num_sources, max_num_paths]
        last_distance = tf.zeros([num_targets, num_sources, max_num_paths],
                                 self._rdtype)
        # [num_targets, num_sources, max_num_paths, 3]
        k_tx = tf.zeros([num_targets, num_sources, max_num_paths, 3],
                        self._rdtype)
        # Normals at the intersection points
        # [num_targets, num_sources, max_num_paths, max_depth, 3]
        normals = tf.zeros([num_targets, num_sources, max_num_paths,
                            max_depth, 3], self._rdtype)
        # Direction of reflection at intersection points
        # [num_targets, num_sources, max_num_paths, max_depth, 3]
        k_r = tf.zeros([num_targets, num_sources, max_num_paths, max_depth, 3],
                       self._rdtype)
        # Direction of incidence at intersection points
        # [num_targets, num_sources, max_num_paths, max_depth+1, 3]
        k_i = tf.zeros([num_targets, num_sources, max_num_paths, max_depth+1,3],
                       self._rdtype)

        # Need to transpose these tensors in order to gather paths from them
        # with all the interactions, i.e., extract the entire "max_depth"
        # dimension.
        # [max_depth, num_targets, num_sources, max_num_paths]
        prefix_objects_tp = tf.transpose(prefix_objects, [1,2,3,0])
        # [num_targets, num_sources, max_num_paths, max_depth, 3]
        prefix_vertices_tp = tf.transpose(prefix_vertices, [1,2,3,0,4])
        # [num_targets, num_sources, max_num_paths, max_depth, 3]
        normals_tp = tf.transpose(prefix_normals, [1,2,3,0,4])
        # [num_targets, num_sources, max_num_paths, max_depth, 3]
        k_i_tp = tf.transpose(prefix_k_i, [1,2,3,0,4])
        # [num_targets, num_sources, max_num_paths, max_depth, 3]
        k_r_tp = tf.transpose(prefix_k_r, [1,2,3,0,4])

        # We sequentially add the prefixes for each depth value.
        # To avoid overwriting the paths scattered at the previous
        # iterations, we incremdent the path index by the maximum number
        # of paths over all links, cumulated over the iterations.
        path_ind_incr = 0
        for depth in tf.range(max_depth, dtype=tf.int64):
            # Indices of valid paths with depth d
            # [num_valid_paths with depth=depth, 4]
            gather_indices_ = tf.gather(gather_indices,
                                tf.where(gather_indices[:,0] == depth)[:,0],
                                axis=0)
            # Depth is not needed for some tensors
            # [num_valid_paths with depth=depth, 3]
            gather_indices_nd_ = gather_indices_[:,1:]

            # Indices for scattering the results in the target tensor
            # [num_valid_paths with depth=depth, 4]
            scatter_indices_ = tf.gather(scatter_indices,
                                tf.where(scatter_indices[:,0] == depth)[:,0],
                                axis=0)


            # [1, 4]
            path_ind_incr_ = tf.cast([0, 0, 0, path_ind_incr], tf.int64)
            # [num_valid_paths with depth=depth, 4]
            scatter_indices_ = scatter_indices_ + path_ind_incr_
            # Depth is not needed for some tensors
            # [num_valid_paths with depth=depth, 3]
            scatter_indices_nd_ = scatter_indices_[:,1:]
            # Prepare for next iteration
            path_ind_incr = path_ind_incr + path_count_depth[depth]

            # Update the tensors

            # Mask
            # [num_valid_paths with depth=depth]
            prefix_mask_ = tf.fill([tf.shape(scatter_indices_nd_)[0]], True)
            # [num_targets, num_sources, max_num_paths]
            mask = tf.tensor_scatter_nd_update(mask, scatter_indices_nd_,
                                               prefix_mask_)

            # Vertices
            prefix_vertices_ = tf.gather_nd(prefix_vertices_tp,
                                            gather_indices_nd_)
            # [num_targets, num_sources, max_num_paths, max_depth, 3]
            vertices = tf.tensor_scatter_nd_update(vertices,
                                                   scatter_indices_nd_,
                                                   prefix_vertices_)

            # Last vertex
            prefix_vertex = tf.gather_nd(prefix_vertices, gather_indices_)
            # [num_targets, num_sources, max_num_paths, 3]
            last_vertices = tf.tensor_scatter_nd_update(last_vertices,
                                                        scatter_indices_nd_,
                                                        prefix_vertex)

            # Objects
            # [num_paths, max_depth]
            objects_ = tf.gather_nd(prefix_objects_tp, gather_indices_nd_)
            # Only keep the prefix of length depth
            # [num_paths, depth]
            objects_ = objects_[:,:depth+1]
            # [num_paths, max_depth]
            objects_ = tf.pad(objects_, [[0,0],[0,max_depth64-depth-1]],
                              constant_values=-1)
            # [num_targets, num_sources, max_num_paths, max_depth]
            objects = tf.tensor_scatter_nd_update(objects, scatter_indices_nd_,
                                                  objects_)

            # Normals at intersection points
            normals_ = tf.gather_nd(normals_tp, gather_indices_nd_)
            # [num_targets, num_sources, max_num_paths, max_depth, 3]
            normals = tf.tensor_scatter_nd_update(normals, scatter_indices_nd_,
                                                  normals_)

            # Direction of incidence at intersection points
            k_i_ = tf.gather_nd(k_i_tp, gather_indices_nd_)
            # [num_targets, num_sources, max_num_paths, max_depth+1, 3]
            k_i = tf.tensor_scatter_nd_update(k_i, scatter_indices_nd_, k_i_)

            # Direction of reflection at intersection points
            k_r_ = tf.gather_nd(k_r_tp, gather_indices_nd_)
            # [num_targets, num_sources, max_num_paths, max_depth, 3]
            k_r = tf.tensor_scatter_nd_update(k_r, scatter_indices_nd_, k_r_)

            # Last hit objects
            objects_ = tf.gather_nd(prefix_objects, gather_indices_)
            # [num_targets, num_sources, max_num_paths]
            last_objects = tf.tensor_scatter_nd_update(last_objects,
                                                       scatter_indices_nd_,
                                                       objects_)

            # Azimuth of departure
            phi_t_ = tf.gather_nd(prefix_phi_t, gather_indices_nd_)
            # [num_targets, num_sources, max_num_paths]
            phi_t = tf.tensor_scatter_nd_update(phi_t, scatter_indices_nd_,
                                                phi_t_)

            # Elevation of departure
            theta_t_ = tf.gather_nd(prefix_theta_t, gather_indices_nd_)
            # [num_targets, num_sources, max_num_paths]
            theta_t = tf.tensor_scatter_nd_update(theta_t, scatter_indices_nd_,
                                                  theta_t_)

            # Normals at the last intersected object
            normals_ = tf.gather_nd(prefix_normals, gather_indices_)
            # [num_targets, num_sources, max_num_paths, 3]
            last_normals = tf.tensor_scatter_nd_update(last_normals,
                                                scatter_indices_nd_, normals_)

            # Direction of incidence at the last interaction point
            k_i_ = tf.gather_nd(prefix_k_i, gather_indices_)
            # [num_targets, num_sources, max_num_paths, 3]
            last_k_i = tf.tensor_scatter_nd_update(last_k_i,
                                                   scatter_indices_nd_,
                                                   k_i_)

            # Distance from the sources to the last interaction point
            last_dist_ = tf.gather_nd(prefix_distances, gather_indices_)
            # [num_targets, num_sources, max_num_paths]
            last_distance = tf.tensor_scatter_nd_update(last_distance,
                                                        scatter_indices_nd_,
                                                        last_dist_)

            # Direction of tx
            k_tx_ = tf.gather_nd(prefix_ktx, gather_indices_nd_)
            # [num_targets, num_sources, max_num_paths, 3]
            k_tx = tf.tensor_scatter_nd_update(k_tx, scatter_indices_nd_, k_tx_)

        # Computes the angles of arrivals, direction of the scattered field,
        # and distance from the scattering point to the targets
        # [num_targets, 3]
        targets = paths.targets
        # [num_targets, 1, 1, 3]
        targets = insert_dims(targets, 2, 1)
        # k_s : [num_targets, num_sources, max_num_paths, 3]
        # scat_2_target_dist : [num_targets, num_sources, max_num_paths]
        k_s,scat_2_target_dist = normalize(targets - last_vertices)
        # Angles of arrivales
        # theta_r, phi_r : [num_targets, num_sources, max_num_paths]
        theta_r, phi_r = theta_phi_from_unit_vec(-k_s)
        # Compute the delays
        # [num_targets, num_sources, max_num_paths]
        tau = (last_distance + scat_2_target_dist)/SPEED_OF_LIGHT
        # [num_targets, num_sources, max_num_paths]
        tau = tf.where(mask, tau, -tf.ones_like(tau))
        # [max_depth, num_targets, num_sources, max_num_paths]
        objects = tf.transpose(objects, [3, 0, 1, 2])
        vertices = tf.transpose(vertices, [3, 0, 1, 2, 4])
        normals = tf.transpose(normals, [3, 0, 1, 2, 4])
        k_i = tf.transpose(k_i, [3, 0, 1, 2, 4])
        k_r = tf.transpose(k_r, [3, 0, 1, 2, 4])

        paths.mask = mask
        paths.vertices = vertices
        paths.objects = objects
        paths.tau = tau
        paths.phi_t = phi_t
        paths.theta_t = theta_t
        paths.phi_r = phi_r
        paths.theta_r = theta_r

        paths_tmp.scat_last_objects = last_objects
        paths_tmp.scat_last_normals = last_normals
        paths_tmp.scat_last_k_i = last_k_i
        paths_tmp.scat_last_vertices = last_vertices
        paths_tmp.scat_src_2_last_int_dist = last_distance
        paths_tmp.scat_k_s = k_s
        paths_tmp.scat_2_target_dist = scat_2_target_dist
        paths_tmp.k_tx = k_tx
        paths_tmp.k_rx = -k_s
        paths_tmp.normals = normals
        paths_tmp.k_i = k_i
        paths_tmp.k_r = k_r
        paths_tmp.total_distance = scat_2_target_dist + last_distance

        return paths, paths_tmp