Fit the accelerated failure time model to a left-censored dataset.

        Parameters
        ----------
        df: DataFrame
            a Pandas DataFrame with necessary columns ``lower_bound_col``, ``upper_bound_col``  (see below),
            and any other covariates or weights.

        lower_bound_col: string
            the name of the column in DataFrame that contains the subjects'
            left-most observation.

        upper_bound_col: string
            the name of the column in DataFrame that contains the subjects'
            right-most observation. Values can be np.inf (and should be if the subject is right-censored).

        event_col: string, optional
            the  name of the column in DataFrame that contains the subjects' death
            observation. If left as None, will be inferred from the start and stop columns (lower_bound==upper_bound means uncensored)

        show_progress: boolean, optional (default=False)
            since the fitter is iterative, show convergence
            diagnostics. Useful if convergence is failing.

        ancillary_df: None, boolean, or DataFrame, optional (default=None)
            Choose to model the ancillary parameters.
            If None or False, explicitly do not fit the ancillary parameters using any covariates.
            If True, model the ancillary parameters with the same covariates as ``df``.
            If DataFrame, provide covariates to model the ancillary parameters. Must be the same row count as ``df``.

        timeline: array, optional
            Specify a timeline that will be used for plotting and prediction

        weights_col: string
            the column in DataFrame that specifies weights per observation.

        robust: boolean, optional (default=False)
            Compute the robust errors using the Huber sandwich estimator.

        initial_point: (d,) numpy array, optional
            initialize the starting point of the iterative
            algorithm. Default is the zero vector.

        entry_col: specify a column in the DataFrame that denotes any late-entries (left truncation) that occurred. See
            the docs on `left truncation <https://lifelines.readthedocs.io/en/latest/Survival%20analysis%20with%20lifelines.html#left-truncated-late-entry-data>`__

        Returns
        -------
        self:
            self with additional new properties: ``print_summary``, ``params_``, ``confidence_intervals_`` and more


        Examples
        --------
        >>> from lifelines import WeibullAFTFitter, LogNormalAFTFitter, LogLogisticAFTFitter
        >>>
        >>> df = pd.DataFrame({
        >>>     'start': [5, 3, 9, 8, 7, 4, 4, 3, 2, 5, 6, 7],
        >>>     'stop':  [5, 3, 9, 8, 7, 4, 8, 5, 2, 5, 6, np.inf],  # this last subject is right-censored.
        >>>     'E':     [1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0],
        >>>     'var': [0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2],
        >>>     'age': [4, 3, 9, 8, 7, 4, 4, 3, 2, 5, 6, 7],
        >>> })
        >>>
        >>> aft = WeibullAFTFitter()
        >>> aft.fit_interval_censoring(df, 'start', 'stop', 'E')
        >>> aft.print_summary()
        >>> aft.predict_median(df)
        >>>
        >>> aft = WeibullAFTFitter()
        >>> aft.fit_interval_censoring(df, 'start', 'stop', 'E', ancillary_df=df)
        >>> aft.print_summary()
        >>> aft.predict_median(df)

def fit_interval_censoring(
        self,
        df,
        lower_bound_col,
        upper_bound_col,
        event_col=None,
        ancillary_df=None,
        show_progress=False,
        timeline=None,
        weights_col=None,
        robust=False,
        initial_point=None,
        entry_col=None,
    ):
        """
        Fit the accelerated failure time model to a left-censored dataset.

        Parameters
        ----------
        df: DataFrame
            a Pandas DataFrame with necessary columns ``lower_bound_col``, ``upper_bound_col``  (see below),
            and any other covariates or weights.

        lower_bound_col: string
            the name of the column in DataFrame that contains the subjects'
            left-most observation.

        upper_bound_col: string
            the name of the column in DataFrame that contains the subjects'
            right-most observation. Values can be np.inf (and should be if the subject is right-censored).

        event_col: string, optional
            the  name of the column in DataFrame that contains the subjects' death
            observation. If left as None, will be inferred from the start and stop columns (lower_bound==upper_bound means uncensored)

        show_progress: boolean, optional (default=False)
            since the fitter is iterative, show convergence
            diagnostics. Useful if convergence is failing.

        ancillary_df: None, boolean, or DataFrame, optional (default=None)
            Choose to model the ancillary parameters.
            If None or False, explicitly do not fit the ancillary parameters using any covariates.
            If True, model the ancillary parameters with the same covariates as ``df``.
            If DataFrame, provide covariates to model the ancillary parameters. Must be the same row count as ``df``.

        timeline: array, optional
            Specify a timeline that will be used for plotting and prediction

        weights_col: string
            the column in DataFrame that specifies weights per observation.

        robust: boolean, optional (default=False)
            Compute the robust errors using the Huber sandwich estimator.

        initial_point: (d,) numpy array, optional
            initialize the starting point of the iterative
            algorithm. Default is the zero vector.

        entry_col: specify a column in the DataFrame that denotes any late-entries (left truncation) that occurred. See
            the docs on `left truncation <https://lifelines.readthedocs.io/en/latest/Survival%20analysis%20with%20lifelines.html#left-truncated-late-entry-data>`__

        Returns
        -------
        self:
            self with additional new properties: ``print_summary``, ``params_``, ``confidence_intervals_`` and more


        Examples
        --------
        >>> from lifelines import WeibullAFTFitter, LogNormalAFTFitter, LogLogisticAFTFitter
        >>>
        >>> df = pd.DataFrame({
        >>>     'start': [5, 3, 9, 8, 7, 4, 4, 3, 2, 5, 6, 7],
        >>>     'stop':  [5, 3, 9, 8, 7, 4, 8, 5, 2, 5, 6, np.inf],  # this last subject is right-censored.
        >>>     'E':     [1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0],
        >>>     'var': [0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2],
        >>>     'age': [4, 3, 9, 8, 7, 4, 4, 3, 2, 5, 6, 7],
        >>> })
        >>>
        >>> aft = WeibullAFTFitter()
        >>> aft.fit_interval_censoring(df, 'start', 'stop', 'E')
        >>> aft.print_summary()
        >>> aft.predict_median(df)
        >>>
        >>> aft = WeibullAFTFitter()
        >>> aft.fit_interval_censoring(df, 'start', 'stop', 'E', ancillary_df=df)
        >>> aft.print_summary()
        >>> aft.predict_median(df)
        """

        self.lower_bound_col = lower_bound_col
        self.upper_bound_col = upper_bound_col
        self._time_cols = [lower_bound_col, upper_bound_col]
        self._censoring_type = CensoringType.INTERVAL

        df = df.copy()

        lower_bound = pass_for_numeric_dtypes_or_raise_array(df.pop(lower_bound_col)).astype(float)
        upper_bound = pass_for_numeric_dtypes_or_raise_array(df.pop(upper_bound_col)).astype(float)

        if event_col is None:
            event_col = "E"
            df["E"] = lower_bound == upper_bound

        if ((lower_bound == upper_bound) != df[event_col]).any():
            raise ValueError(
                "For all rows, lower_bound == upper_bound if and only if event observed = 1 (uncensored). Likewise, lower_bound < upper_bound if and only if event observed = 0 (censored)"
            )
        if (lower_bound > upper_bound).any():
            raise ValueError("All upper bound measurements must be greater than or equal to lower bound measurements.")

        self.lower_bound = lower_bound
        self.upper_bound = upper_bound

        self._fit(
            self._log_likelihood_interval_censoring,
            df,
            (lower_bound.values, np.clip(upper_bound.values, 0, 1e25)),
            event_col=event_col,
            ancillary_df=ancillary_df,
            show_progress=show_progress,
            timeline=timeline,
            weights_col=weights_col,
            robust=robust,
            initial_point=initial_point,
            entry_col=entry_col,
        )

        return self