def solve(self):
        """Solve optimal binning using added data.

        Returns
        -------
        self : OptimalBinningSketch
            Current fitted optimal binning.
        """
        time_init = time.perf_counter()

        # Check if data was added
        if not self._n_add:
            raise NotFittedError("No data was added. Add data before solving.")

        # Pre-binning
        if self.verbose:
            logger.info("Pre-binning started.")

        time_prebinning = time.perf_counter()

        splits, n_nonevent, n_event = self._prebinning_data()
        self._n_prebins = len(splits) + 1

        self._time_prebinning = time.perf_counter() - time_prebinning

        if self.verbose:
            logger.info("Pre-binning: number of prebins: {}"
                        .format(self._n_prebins))
            logger.info("Pre-binning: number of refinements: {}"
                        .format(self._n_refinements))

            logger.info("Pre-binning terminated. Time: {:.4f}s"
                        .format(self._time_prebinning))

        # Optimization
        self._fit_optimizer(splits, n_nonevent, n_event)

        # Post-processing
        if self.verbose:
            logger.info("Post-processing started.")
            logger.info("Post-processing: compute binning information.")

        time_postprocessing = time.perf_counter()

        if not len(splits):
            n_nonevent = np.array([self._t_n_nonevent])
            n_event = np.array([self._t_n_event])

        self._n_nonevent, self._n_event = bin_info(
            self._solution, n_nonevent, n_event, self._n_nonevent_missing,
            self._n_event_missing, self._n_nonevent_special,
            self._n_event_special, self._n_nonevent_cat_others,
            self._n_event_cat_others, self._cat_others)

        self._binning_table = BinningTable(
            self.name, self.dtype, self.special_codes, self._splits_optimal,
            self._n_nonevent, self._n_event, None, None, self._categories,
            self._cat_others, None)

        self._time_postprocessing = time.perf_counter() - time_postprocessing

        if self.verbose:
            logger.info("Post-processing terminated. Time: {:.4f}s"
                        .format(self._time_postprocessing))

        self._time_total = time.perf_counter() - time_init
        self._time_streaming_solve += self._time_total
        self._n_solve += 1

        if self.verbose:
            logger.info("Optimal binning terminated. Status: {}. Time: {:.4f}s"
                        .format(self._status, self._time_total))

        # Completed successfully
        self._is_solved = True
        self._update_streaming_stats()

        return self
Solve optimal binning using added data.

        Returns
        -------
        self : OptimalBinningSketch
            Current fitted optimal binning.
        
optbinning/binning/distributed/binning_sketch.py