//  [python]
def _compute_diffraction_points(self, targets, sources, wedges_indices):
        r"""
        Compute the interaction points on the wedges that minimizes the path
        length, and masks the wedges for which the interaction points is
        not on the finite wedge.

        Note: This calculation is done in double-precision (64bit).

        Input
        ------
        targets : [num_targets, 3], tf.float
            Coordinates of the targets.

        sources : [num_sources, 3], tf.float
            Coordinates of the sources.

        wedges_indices : [num_targets, num_sources, max_num_paths], tf.int
            Indices of the wedges that interacted with the diffracted paths

        Output
        -------
        wedges_indices : [num_targets, num_sources, max_num_paths], tf.int
            Indices of the wedges that interacted with the diffracted paths

        vertices : [num_targets, num_sources, max_num_paths, 3], tf.float
            Coordinates of the interaction points on the intersected wedges
        """

        sources = tf.cast(sources, tf.float64)
        targets = tf.cast(targets, tf.float64)

        # On CPU, indexing with -1 does not work. Hence we replace -1 by 0.
        # This makes no difference on the resulting paths as such paths
        # are not flagged as active.
        # [max_num_paths]
        valid_wedges_idx = tf.where(wedges_indices == -1, 0, wedges_indices)

        # [max_num_paths, 3]
        origins = tf.gather(self._wedges_origin, valid_wedges_idx)
        origins = tf.cast(origins, tf.float64)
        # [1, 1, max_num_paths, 3]
        origins = expand_to_rank(origins, 4, 0)
        # [max_num_paths, 3]
        e_hat = tf.gather(self._wedges_e_hat, valid_wedges_idx)
        e_hat = tf.cast(e_hat, tf.float64)
        # [1, 1, max_num_paths, 3]
        e_hat = expand_to_rank(e_hat, 4, 0)
        # [max_num_paths]
        wedges_length = tf.gather(self._wedges_length, valid_wedges_idx)
        wedges_length = tf.cast(wedges_length, tf.float64)
        # [1, 1, max_num_paths]
        wedges_length = expand_to_rank(wedges_length, 3, 0)

        # Expand to broadcast with paths and sources/targets
        # [1, num_sources, 1, 3]
        sources = tf.expand_dims(tf.expand_dims(sources, axis=1), axis=0)
        # [num_targets, 1, 1, 3]
        targets = insert_dims(targets, 2, 1)
        # Sources/Targets vectors
        # [1, num_sources, max_num_paths, 3]
        u_t = origins - sources
        # [num_targets, 1, max_num_paths, 3]
        u_r = origins - targets

        # Quantites required for the computation of the interaction points
        # [1, num_sources, max_num_paths]
        a = dot(u_t,e_hat)
        # [num_targets, 1, max_num_paths]
        b = dot(u_r, e_hat)
        # [1, num_sources, max_num_paths]
        c = dot(u_t, u_t)
        # [num_targets, 1, max_num_paths]
        d = dot(u_r, u_r)

        # Quantites required for the computation of the interaction points
        # [num_targets, num_sources, max_num_paths]
        alpha = -tf.square(a) + tf.square(b) + c - d
        beta = 2.*(a*tf.square(b) - b*tf.square(a) + b*c - a*d)
        gamma = tf.square(b)*c - tf.square(a)*d

        # Normalized quantites to improve numerical preicion, only valid if
        # alpha != 0
        # [num_targets, num_sources, max_num_paths]
        beta_norm = tf.math.divide_no_nan(beta, alpha)
        gamma_norm = tf.math.divide_no_nan(gamma, alpha)
        delta = tf.square(beta_norm) - 4.*gamma_norm

        # Because of numerical imprecision, delta could be slighlty smaller than
        # 0
        # [num_targets, num_sources, max_num_paths]
        delta = tf.where(tf.less(delta, tf.zeros_like(delta)),
                         tf.zeros_like(delta), delta)


        # Four possible outcomes depending on the value of the previous
        # quantities.
        # Values of t that minimizes the path length for each outcome.
        # [num_targets, num_sources, max_num_paths]
        t_min_1 = -a
        t_min_2 = -tf.math.divide_no_nan(gamma, beta)
        t_min_3 = (-beta_norm + tf.sqrt(delta))*0.5
        t_min_4 = (-beta_norm - tf.sqrt(delta))*0.5
        # Condition for each outcome to be selected
        # If a == b and c == d, then set to t_min_1
        # [num_targets, num_sources, max_num_paths]
        cond_1 = tf.logical_and(tf.experimental.numpy.isclose(a, b),
                                tf.experimental.numpy.isclose(c, d))
        # If cond_1 does not hold and alpha == 0, then set to t_min_2
        # [num_targets, num_sources, max_num_paths]
        cond_2 = tf.logical_and(tf.logical_not(cond_1),
                                tf.experimental.numpy.isclose(alpha,
                                tf.zeros_like(alpha)))
        # If neither cond_1 nor cond_2 holds, then set to t_min_3 or t_min_4
        # depending on the signs of t+a and t+b
        # [num_targets, num_sources, max_num_paths]
        not_cond_12 = tf.logical_and(tf.logical_not(cond_1),
                                     tf.logical_not(cond_2))
        # [num_targets, num_sources, max_num_paths]
        t_min_3a = t_min_3 + a
        t_min_3b = t_min_3 + b
        # [num_targets, num_sources, max_num_paths]
        cond_3 = tf.logical_and(not_cond_12,
                tf.less_equal(tf.sign(t_min_3a)*tf.sign(t_min_3b), 0.0))
        # If none of conditions 1, 2, or 3 are satisfied, then all is left is
        # t_min_4
        # [num_targets, num_sources, max_num_paths]
        cond_4 = tf.logical_and(not_cond_12, tf.logical_not(cond_3))
        # Assign t_min according to the previously computed conditions
        # [num_targets, num_sources, max_num_paths]
        t_min = tf.zeros_like(cond_1, tf.float64)
        t_min = tf.where(cond_1, t_min_1, t_min)
        t_min = tf.where(cond_2, t_min_2, t_min)
        t_min = tf.where(cond_3, t_min_3, t_min)
        t_min = tf.where(cond_4, t_min_4, t_min)

        # Mask paths for which the interaction point is not on the finite
        # wedge
        # [num_targets, num_sources, max_num_paths]
        mask_ = tf.logical_and(
            tf.greater_equal(t_min, tf.zeros_like(t_min)),
            tf.less_equal(t_min, wedges_length))
        # [num_targets, num_sources, max_num_paths]
        wedges_indices = tf.where(mask_, wedges_indices, -1)

        # Interaction points
        # Expand to broadcast with coordinates
        # [num_targets, num_sources, max_num_paths, 1]
        t_min = tf.expand_dims(t_min, axis=3)
        # [num_targets, num_sources, max_num_paths, 3]
        inter_point = origins + t_min*e_hat

        # Discard wedges with no valid paths
        # [max_num_paths]
        used_wedges = tf.where(tf.reduce_any(tf.not_equal(wedges_indices, -1),
                                             axis=(0,1)))[:,0]
        # [num_targets, num_sources, max_num_paths, 3]
        inter_point = tf.gather(inter_point, used_wedges, axis=2)
        # [num_targets, num_sources, max_num_paths]
        wedges_indices = tf.gather(wedges_indices, used_wedges, axis=2)

        # Back to the required precision
        inter_point = tf.cast(inter_point, self._rdtype)

        return wedges_indices, inter_point