def _critical_point_search(
        self,
        delta_0: float,
        fraction_true: float,
        rel_diff_slope: float,
        rel_diff_value: float,
    ) -> None:
        """
        Search for critical points.

        :param delta_0: Initial step size of binary search.
        :param fraction_true: Fraction of output predictions that have to fulfill criteria for critical point.
        :param rel_diff_slope: Relative slope difference at critical points.
        :param rel_diff_value: Relative value difference at critical points.
        """
        logger.info("Searching for critical points.")

        if self.num_neurons is None:
            raise ValueError("The value of `num_neurons` is required for critical point search.")
        h_square = self.num_neurons * self.num_neurons

        t_current = float(-h_square)
        while t_current < h_square:
            delta = delta_0
            found_critical_point = False

            while not found_critical_point:
                epsilon = delta / 10

                t_1 = t_current
                t_2 = t_current + delta

                x_1 = self._get_x(t_1)
                x_1_p = self._get_x(t_1 + epsilon)
                x_2 = self._get_x(t_2)
                x_2_m = self._get_x(t_2 - epsilon)

                m_1 = (self._o_l(x_1_p) - self._o_l(x_1)) / epsilon
                m_2 = (self._o_l(x_2) - self._o_l(x_2_m)) / epsilon

                y_1 = self._o_l(x_1)
                y_2 = self._o_l(x_2)

                if np.sum(np.abs((m_1 - m_2) / m_1) < rel_diff_slope) > fraction_true * self.num_classes:
                    t_current = t_2
                    break

                t_hat = t_1 + np.divide(y_2 - y_1 - (t_2 - t_1) * m_2, m_1 - m_2)
                y_hat = y_1 + m_1 * np.divide(y_2 - y_1 - (t_2 - t_1) * m_2, m_1 - m_2)

                t_mean = np.mean(t_hat[t_hat != -np.inf])

                x_mean = self._get_x(t_mean)
                x_mean_p = self._get_x(t_mean + epsilon)
                x_mean_m = self._get_x(t_mean - epsilon)

                y = self._o_l(x_mean)

                m_x_1 = (self._o_l(x_mean_p) - self._o_l(x_mean)) / epsilon
                m_x_2 = (self._o_l(x_mean) - self._o_l(x_mean_m)) / epsilon

                if (
                    np.sum(np.abs((y_hat - y) / y) < rel_diff_value) > fraction_true * self.num_classes
                    and t_1 < t_mean < t_2
                    and np.sum(np.abs((m_x_1 - m_x_2) / m_x_1) > rel_diff_slope) > fraction_true * self.num_classes
                ):
                    found_critical_point = True
                    self.critical_points.append(x_mean)
                    t_current = t_2
                else:
                    delta = delta / 2

        if len(self.critical_points) != self.num_neurons:
            raise AssertionError(
                f"The number of critical points found ({len(self.critical_points)}) does not equal the number of "
                f"expected neurons in the first layer ({self.num_neurons})."
            )

        Search for critical points.

        :param delta_0: Initial step size of binary search.
        :param fraction_true: Fraction of output predictions that have to fulfill criteria for critical point.
        :param rel_diff_slope: Relative slope difference at critical points.
        :param rel_diff_value: Relative value difference at critical points.
        
art/attacks/extraction/functionally_equivalent_extraction.py