    def __apply_noise_and_ar_model(self):
        """
        Apply noise and autoregressive (AR) models to precipitation cascades.
        This method applies the AR model to the decomposed precipitation cascades
        and adds noise perturbations if necessary.
        """
        # Make a copy of the precipitation data and replace non-finite values
        precip = self.__precip.copy()
        for i in range(self.__precip.shape[0]):
            # Replace non-finite values with the minimum finite value of the precipitation field
            precip[i, ~np.isfinite(precip[i, :])] = np.nanmin(precip[i, :])
        # Store the precipitation data back in the object
        self.__precip = precip

        # Initialize the noise generator if the noise_method is provided
        if self.__config.noise_method is not None:
            np.random.seed(
                self.__config.seed
            )  # Set the random seed for reproducibility
            init_noise, generate_noise = noise.get_method(self.__config.noise_method)
            self.__params.noise_generator = generate_noise

            self.__params.perturbation_generator = init_noise(
                self.__precip,
                fft_method=self.__params.fft,
                **self.__params.noise_kwargs,
            )

            # Handle noise standard deviation adjustments if necessary
            if self.__config.noise_stddev_adj == "auto":
                print("Computing noise adjustment coefficients... ", end="", flush=True)
                if self.__config.measure_time:
                    starttime = time.time()

                # Compute noise adjustment coefficients
                self.__params.noise_std_coefficients = (
                    noise.utils.compute_noise_stddev_adjs(
                        self.__precip[-1, :, :],
                        self.__config.precip_threshold,
                        np.min(self.__precip),
                        self.__params.bandpass_filter,
                        self.__params.decomposition_method,
                        self.__params.perturbation_generator,
                        self.__params.noise_generator,
                        20,
                        conditional=self.__config.conditional,
                        num_workers=self.__config.num_workers,
                        seed=self.__config.seed,
                    )
                )

                # Measure and print time taken
                if self.__config.measure_time:
                    __ = self.__measure_time(
                        "Noise adjustment coefficient computation", starttime
                    )
                else:
                    print("done.")

            elif self.__config.noise_stddev_adj == "fixed":
                # Set fixed noise adjustment coefficients
                func = lambda k: 1.0 / (0.75 + 0.09 * k)
                self.__params.noise_std_coefficients = [
                    func(k) for k in range(1, self.__config.n_cascade_levels + 1)
                ]

            else:
                # Default to no adjustment
                self.__params.noise_std_coefficients = np.ones(
                    self.__config.n_cascade_levels
                )

            if self.__config.noise_stddev_adj is not None:
                # Print noise std deviation coefficients if adjustments were made
                print(
                    f"noise std. dev. coeffs:   {str(self.__params.noise_std_coefficients)}"
                )

        else:
            # No noise, so set perturbation generator and noise_std_coefficients to None
            self.__params.perturbation_generator = None
            self.__params.noise_std_coefficients = np.ones(
                self.__config.n_cascade_levels
            )  # Keep default as 1.0 to avoid breaking AR model

        # Decompose the input precipitation fields
        self.__state.precip_decomposed = []
        for i in range(self.__config.ar_order + 1):
            precip_ = self.__params.decomposition_method(
                self.__precip[i, :, :],
                self.__params.bandpass_filter,
                mask=self.__state.mask_threshold,
                fft_method=self.__params.fft,
                output_domain=self.__config.domain,
                normalize=True,
                compute_stats=True,
                compact_output=True,
            )
            self.__state.precip_decomposed.append(precip_)

        # Normalize the cascades and rearrange them into a 4D array
        self.__state.precip_cascades = nowcast_utils.stack_cascades(
            self.__state.precip_decomposed, self.__config.n_cascade_levels
        )
        self.__state.precip_decomposed = self.__state.precip_decomposed[-1]
        self.__state.precip_decomposed = [
            self.__state.precip_decomposed.copy()
            for _ in range(self.__config.n_ens_members)
        ]

        # Compute temporal autocorrelation coefficients for each cascade level
        self.__params.autocorrelation_coefficients = np.empty(
            (self.__config.n_cascade_levels, self.__config.ar_order)
        )
        for i in range(self.__config.n_cascade_levels):
            self.__params.autocorrelation_coefficients[i, :] = (
                correlation.temporal_autocorrelation(
                    self.__state.precip_cascades[i], mask=self.__state.mask_threshold
                )
            )

        nowcast_utils.print_corrcoefs(self.__params.autocorrelation_coefficients)

        # Adjust the lag-2 correlation coefficient if AR(2) model is used
        if self.__config.ar_order == 2:
            for i in range(self.__config.n_cascade_levels):
                self.__params.autocorrelation_coefficients[i, 1] = (
                    autoregression.adjust_lag2_corrcoef2(
                        self.__params.autocorrelation_coefficients[i, 0],
                        self.__params.autocorrelation_coefficients[i, 1],
                    )
                )

        # Estimate the parameters of the AR model using auto-correlation coefficients
        self.__params.ar_model_coefficients = np.empty(
            (self.__config.n_cascade_levels, self.__config.ar_order + 1)
        )
        for i in range(self.__config.n_cascade_levels):
            self.__params.ar_model_coefficients[i, :] = (
                autoregression.estimate_ar_params_yw(
                    self.__params.autocorrelation_coefficients[i, :]
                )
            )

        nowcast_utils.print_ar_params(self.__params.ar_model_coefficients)

        # Discard all except the last ar_order cascades for AR model
        self.__state.precip_cascades = [
            self.__state.precip_cascades[i][-self.__config.ar_order :]
            for i in range(self.__config.n_cascade_levels)
        ]

        # Stack the cascades into a list containing all ensemble members
        self.__state.precip_cascades = [
            [
                self.__state.precip_cascades[j].copy()
                for j in range(self.__config.n_cascade_levels)
            ]
            for _ in range(self.__config.n_ens_members)
        ]

        # Initialize random generators if noise_method is provided
        if self.__config.noise_method is not None:
            self.__state.random_generator_precip = []
            self.__state.random_generator_motion = []
            seed = self.__config.seed
            for _ in range(self.__config.n_ens_members):
                # Create random state for precipitation noise generator
                rs = np.random.RandomState(seed)
                self.__state.random_generator_precip.append(rs)
                seed = rs.randint(0, high=int(1e9))
                # Create random state for motion perturbations generator
                rs = np.random.RandomState(seed)
                self.__state.random_generator_motion.append(rs)
                seed = rs.randint(0, high=int(1e9))
        else:
            self.__state.random_generator_precip = None
            self.__state.random_generator_motion = None
        print("AR model and noise applied to precipitation cascades.")
Apply noise and autoregressive (AR) models to precipitation cascades.
This method applies the AR model to the decomposed precipitation cascades
and adds noise perturbations if necessary.
pysteps/nowcasts/steps.py