예제 #1
0
    def fit_single_line(self, x, y, zero_lev, err_continuum, fitting_parameters, bootstrap_iterations = 1000):

        #Simple fit
        if self.fit_dict['MC_iterations'] == 1:
            fit_output = lmfit_minimize(residual_gauss, fitting_parameters, args=(x, y, zero_lev, err_continuum))
            self.fit_dict['area_intg'] = simps(y, x) - simps(zero_lev, x)
            self.fit_dict['area_intg_err'] = 0.0
             
        #Bootstrap
        else:
            mini_posterior  = Minimizer(lnprob_gaussCurve, fitting_parameters, fcn_args = ([x, y, zero_lev, err_continuum]))
            fit_output      = mini_posterior.emcee(steps=200, params = fitting_parameters)
            
            #Bootstrap for the area of the lines
            area_array = empty(bootstrap_iterations) 
            len_x_array = len(x)
            for i in range(bootstrap_iterations):
                y_new =  y + np_normal_dist(0.0, err_continuum, len_x_array)
                area_array[i] = simps(y_new, x) - simps(zero_lev, x)
            self.fit_dict['area_intg'] = mean(area_array)
            self.fit_dict['area_intg_err'] = std(area_array)           
        
        #Store the fitting parameters
        output_params = fit_output.params
        for key in self.fit_dict['parameters_list']:
            self.fit_dict[key + '_norm'] = output_params[key].value
            self.fit_dict[key + '_norm_er'] = output_params[key].stderr
            
        return
예제 #2
0
    def fit_parameters(self, error_func):
        assert error_func == residuals_error, "right now we only know about lmfit miniumize, so we need to use residuals error"

        gamma1 = (1 / 10 + 1 / 5) / 2
        gamma2 = 0.02
        gamma3 = 0.02
        beta = 0.14

        num_cumulative_positiv = np.sum(self._observations[:, 0])

        print(f"num_cumulative_positiv {num_cumulative_positiv}")
        print(self._observations[:, 0])

        tau = 28 / num_cumulative_positiv

        delta = 5 / 18

        params = Parameters()
        params.add('gamma1', value=gamma1, min=1 / 10, max=1 / 5)
        params.add('gamma2', value=gamma2, min=0, max=5)
        params.add('gamma3', value=gamma3, min=0, max=5)
        params.add('beta', value=beta, min=0.01, max=0.5)
        params.add('tau', value=tau, min=tau * 0.8, max=tau * 1.2)
        params.add('delta', value=delta, min=0.6 * delta, max=1.4 * delta)

        # Attention ! We use leastsq
        # so the objective function must
        # return an array ith the residuals.

        result = lmfit_minimize(self._plumb,
                                params,
                                args=(len(self._observations), error_func),
                                method='leastsq')

        report_fit(result)

        self._fit_params = result.params
예제 #3
0
    def fit_blended_line_BS_lmfit(self, x, y, zero_lev, err_continuum, Ncomps, fitting_parameters, add_wide_component, fitting_parameters_wide, bootstrap_iterations = 500, MC_iterations = 200):
                        
        #Prepare some data in advance
        n_points        = len(x)
        n_parameters    = len(self.fit_dict['parameters_list'])
        params_list     = self.fit_dict['parameters_list']
        range_param     = range(n_parameters)
        range_boots     = range(bootstrap_iterations)
        area_array      = empty(bootstrap_iterations)
        idcs_components = map(str, range(Ncomps))
        params_matrix   = empty((n_parameters, bootstrap_iterations))
        errors_matrix   = empty((n_parameters, bootstrap_iterations))
        Ncomps_wide     = ['3']
        
        #Loop through the iterations:
        for i in range_boots:
            
            y_new           = y + np_normal_dist(0.0, err_continuum, n_points)
            area_array[i]   = simps(y_new, x) - simps(zero_lev, x)
            fit_Output      = lmfit_minimize(residual_gaussMix, fitting_parameters, args=(x, y_new, zero_lev, err_continuum, idcs_components))
            output_params   = fit_Output.params
            
            #Case with a wide component
            if add_wide_component:
                
                #Only works for Halpha
                sigma_limit = output_params['sigma1'].value
                limit_0, limit_1 = 6548.05 - self.fit_dict['x_scaler'] - sigma_limit * 1.5, 6548.05 - self.fit_dict['x_scaler'] + sigma_limit * 1.5
                limit_2, limit_3 = 0 - sigma_limit * 4,                                     0 + sigma_limit * 4 
                limit_4, limit_5 = 6583.46 - self.fit_dict['x_scaler'] - sigma_limit * 3,   6583.46 - self.fit_dict['x_scaler'] + sigma_limit * 3
                
                indeces = ((x >= limit_0) & (x <= limit_1)) + ((x >= limit_2) & (x <= limit_3)) + ((x >= limit_4) & (x <= limit_5))
                mask = invert(indeces) 
                x_wide, y_wide, zero_wide = x[mask], y[mask], zero_lev[mask]

                #Fit the wide component without narrow component
                fit_output_wide     = lmfit_minimize(residual_gaussMix, fitting_parameters_wide, args=(x_wide, y_wide, zero_wide, err_continuum, Ncomps_wide))
                output_params_wide  = fit_output_wide.params
                
                #Get wide curve
                y_wide = gaussian_mixture(output_params_wide.valuesdict(), x, zero_lev, Ncomps_wide)

                #Calculate emission line region again
                y_pure_narrow       = y - y_wide + zero_lev 
                
                #Fit narrow components again
                fit_Output          = lmfit_minimize(residual_gaussMix, fitting_parameters, args=(x, y_pure_narrow, zero_lev, err_continuum, idcs_components))
                out_params_narrow   = fit_Output.params           

                #Combine the results from both fits
                output_params       = out_params_narrow + output_params_wide
                
            #save parameters to array
            for j in range_param:
                params_matrix[j,i] = output_params[params_list[j]].value
                errors_matrix[j,i] = output_params[params_list[j]].stderr

        #Calculate mean and std deviation values
        mean_area, std_area = mean(area_array), std(area_array)
        mean_params_array, stdev_params_array = params_matrix.mean(1), params_matrix.std(1)
        errorsmean_array = errors_matrix.mean(1)
                
        #Return the data to a dictionary format
        self.fit_dict['area_intg'], self.fit_dict['area_intg_er'] = mean_area, std_area
        
        for j in range(len(self.fit_dict['parameters_list'])):
            param = self.fit_dict['parameters_list'][j]
            self.fit_dict[param+'_norm'] = mean_params_array[j]
            self.fit_dict[param+'_norm_er'] = errorsmean_array[j]

        #Increase the number of components if wide observed
        self.fit_dict.line_number = self.fit_dict.line_number + 1 if add_wide_component else self.fit_dict.line_number

        return
예제 #4
0
def AGD_double(
    vel,
    data,
    vel_em,
    data_em,
    errors,
    errors_em,
    alpha1=None,
    alpha2=None,
    alpha_em=None,
    wiggle=0.1,
    drop_width=3,
    min_dv=0,
    mode="python",
    verbose=False,
    SNR_thresh=5.0,
    BLFrac=0.1,
    SNR2_thresh=5.0,
    SNR_em=5.0,
    deblend=True,
    perform_final_fit=True,
    phase="two",
):
    """ Autonomous Gaussian Decomposition "hybrid" Method
    Allows for simultaneous decomposition of 21cm absorption and emission
    New free parameters, in addition to the alpha parameters and
    signal to noise ratios for standard one- or two-phase AGD fit, include:
        alpha_em:     regularization paramter for the fit to emission (either
                        the residuals of the absorption ,or the full emission
                        spectrum in the absence of detected absorption components)
                        Default: None
        wiggle:       factor by which parameters from the absorption fit (i.e.,
                        the Gaussian parameters of components detected in
                        absorption) are allowed to vary in the fit to emission.
                        Default: 10%
        drop_width:   if a component is found in the fit to emission and its mean
                        position is within +/- drop_width (defind in channels)
                        from the mean position of any absorption component, the
                        emission component is dropped from the fit.
                        Default: 3 channels
        min_dv:       minimum width of components fit in the emission-only fit.
                        Limits AGD from fitting unrealistically narrow components
                        in emission which, if real, shoud have been detected in
                        absorption.
                        Default: 0 (i.e., no constraint)
    """

    if type(SNR2_thresh) != type([]):
        SNR2_thresh = [SNR2_thresh, SNR2_thresh]
    if type(SNR_thresh) != type([]):
        SNR_thresh = [SNR_thresh, SNR_thresh]

    say("\n  --> AGD() \n", verbose)

    if (not alpha2) and (phase == "two"):
        print("alpha2 value required")
        return

    dv = np.abs(vel[1] - vel[0])
    v_to_i = interp1d(vel, np.arange(len(vel)))

    # --------------------------------------#
    # Find phase-one guesses               #
    # --------------------------------------#
    agd1 = initialGuess(
        vel,
        data,
        errors=None,
        alpha=alpha1,
        # plot=plot,
        mode=mode,
        verbose=verbose,
        SNR_thresh=SNR_thresh[0],
        BLFrac=BLFrac,
        SNR2_thresh=SNR2_thresh[0],
        deblend=deblend,
    )

    amps_g1, widths_g1, offsets_g1, u2 = (
        agd1["amps"],
        agd1["FWHMs"],
        agd1["means"],
        agd1["u2"],
    )
    params_g1 = np.append(np.append(amps_g1, widths_g1), offsets_g1)
    ncomps_g1 = len(params_g1) // 3
    ncomps_g2 = 0  # Default
    ncomps_f1 = 0  # Default

    # ----------------------------#
    # Find phase-two guesses #
    # ----------------------------#
    if phase == "two":
        say("Beginning phase-two AGD... ", verbose)
        ncomps_g2 = 0

        # ----------------------------------------------------------#
        # Produce the residual signal                               #
        #  -- Either the original data, or intermediate subtraction #
        # ----------------------------------------------------------#
        if ncomps_g1 == 0:
            say(
                "Phase 2 with no narrow comps -> No intermediate subtration... ",
                verbose,
            )
            residuals = data
        else:
            # "Else" Narrow components were found, and Phase == 2, so perform intermediate subtraction...

            # The "fitmask" is a collection of windows around the a list of phase-one components
            fitmask, fitmaskw = create_fitmask(len(vel), v_to_i(offsets_g1),
                                               widths_g1 / dv / 2.355 * 0.9)
            notfitmask = 1 - fitmask

            # Error function for intermediate optimization
            def objectiveD2_leastsq(paramslm):
                params = vals_vec_from_lmfit(paramslm)
                model0 = func(vel, *params)
                model2 = np.diff(np.diff(model0.ravel())) / dv / dv
                resids1 = fitmask[1:-1] * (model2 - u2[1:-1]) / errors[1:-1]
                resids2 = notfitmask * (model0 - data) / errors / 10.0
                return np.append(resids1, resids2)

            # Perform the intermediate fit using LMFIT
            t0 = time.time()
            say("Running LMFIT on initial narrow components...", verbose)
            lmfit_params = paramvec_to_lmfit(params_g1)
            result = lmfit_minimize(objectiveD2_leastsq,
                                    lmfit_params,
                                    method="leastsq")
            params_f1 = vals_vec_from_lmfit(result.params)
            ncomps_f1 = len(params_f1) // 3

            # # Make "FWHMS" positive
            # params_f1[0:ncomps_f1][params_f1[0:ncomps_f1] < 0.0] = (
            #     -1 * params_f1[0:ncomps_f1][params_f1[0:ncomps_f1] < 0.0]
            # )

            del lmfit_params
            say("LMFIT fit took {0} seconds.".format(time.time() - t0))

            if result.success:
                # Compute intermediate residuals
                # Median filter on 2x effective scale to remove poor subtractions of strong components
                intermediate_model = func(
                    vel, *params_f1).ravel()  # Explicit final (narrow) model
                median_window = 2.0 * 10**((np.log10(alpha1) + 2.187) / 3.859)
                residuals = median_filter(data - intermediate_model,
                                          np.int(median_window))
            else:
                residuals = data
            # Finished producing residual signal # ---------------------------

        # Search for phase-two guesses
        agd2 = initialGuess(
            vel,
            residuals,
            errors=None,
            alpha=alpha2,
            mode=mode,
            verbose=verbose,
            SNR_thresh=SNR_thresh[1],
            BLFrac=BLFrac,
            SNR2_thresh=SNR2_thresh[1],  # June 9 2014, change
            deblend=deblend,
            # plot=plot,
        )
        ncomps_g2 = agd2["N_components"]
        if ncomps_g2 > 0:
            params_g2 = np.concatenate(
                [agd2["amps"], agd2["FWHMs"], agd2["means"]])
        else:
            params_g2 = []
        u22 = agd2["u2"]

        # END PHASE 2 <<<

    # Check for phase two components, make final guess list
    # ------------------------------------------------------
    if phase == "two" and (ncomps_g2 > 0):
        amps_gf = np.append(params_g1[0:ncomps_g1], params_g2[0:ncomps_g2])
        widths_gf = np.append(params_g1[ncomps_g1:2 * ncomps_g1],
                              params_g2[ncomps_g2:2 * ncomps_g2])
        offsets_gf = np.append(
            params_g1[2 * ncomps_g1:3 * ncomps_g1],
            params_g2[2 * ncomps_g2:3 * ncomps_g2],
        )
        params_gf = np.concatenate([amps_gf, widths_gf, offsets_gf])
        ncomps_gf = len(params_gf) // 3
    else:
        params_gf = params_g1
        ncomps_gf = len(params_gf) // 3

    # Sort final guess list by amplitude
    # ----------------------------------
    say("N final parameter guesses: " + str(ncomps_gf))
    amps_temp = params_gf[0:ncomps_gf]
    widths_temp = params_gf[ncomps_gf:2 * ncomps_gf]
    offsets_temp = params_gf[2 * ncomps_gf:3 * ncomps_gf]
    w_sort_amp = np.argsort(amps_temp)[::-1]
    params_gf = np.concatenate([
        amps_temp[w_sort_amp], widths_temp[w_sort_amp],
        offsets_temp[w_sort_amp]
    ])

    ncomps_fit = ncomps_gf
    params_fit = params_gf

    if perform_final_fit:
        say("\n\n  --> Final Fitting... \n", verbose)

        if ncomps_gf > 0:
            # Objective functions for final fit
            def objective_leastsq(paramslm):
                params = vals_vec_from_lmfit(paramslm)
                resids = (func(vel, *params).ravel() - data.ravel()) / errors
                return resids

            # Final fit using unconstrained parameters
            t0 = time.time()
            lmfit_params = paramvec_to_lmfit(params_gf)
            result2 = lmfit_minimize(objective_leastsq,
                                     lmfit_params,
                                     method="leastsq")
            params_fit = vals_vec_from_lmfit(result2.params)
            params_errs = errs_vec_from_lmfit(result2.params)
            ncomps_fit = len(params_fit) // 3

            del lmfit_params
            say("Final fit took {0} seconds.".format(time.time() - t0),
                verbose)

            # # Make "FWHMS" positive
            # params_fit[0:ncomps_fit][params_fit[0:ncomps_fit] < 0.0] = (
            #     -1 * params_fit[0:ncomps_fit][params_fit[0:ncomps_fit] < 0.0]
            # )

            best_fit_final = func(vel, *params_fit).ravel()
            rchi2 = np.sum((data - best_fit_final)**2 / errors**2) / len(data)

            # Check if any amplitudes are identically zero, if so, remove them.
            amps_fit = np.array(params_fit[0:ncomps_fit], dtype=float)
            fwhms_fit = np.array(params_fit[ncomps_fit:2 * ncomps_fit],
                                 dtype=float)
            offsets_fit = np.array(params_fit[2 * ncomps_fit:3 * ncomps_fit],
                                   dtype=float)
            w_keep = amps_fit > 0.0
            params_fit = np.concatenate(
                [amps_fit[w_keep], fwhms_fit[w_keep], offsets_fit[w_keep]])
            ncomps_fit = len(params_fit) // 3

            amps_fit = np.array(params_fit[0:ncomps_fit], dtype=float)
            fwhms_fit = np.array(params_fit[ncomps_fit:2 * ncomps_fit],
                                 dtype=float)
            offsets_fit = np.array(params_fit[2 * ncomps_fit:3 * ncomps_fit],
                                   dtype=float)
            w_keep = offsets_fit < np.max(vel)
            params_fit = np.concatenate(
                [amps_fit[w_keep], fwhms_fit[w_keep], offsets_fit[w_keep]])
            fwhms_fit = np.abs(fwhms_fit[w_keep])
            ncomps_fit = len(params_fit) // 3

            amps_fit = np.array(params_fit[0:ncomps_fit], dtype=float)
            fwhms_fit = np.array(params_fit[ncomps_fit:2 * ncomps_fit],
                                 dtype=float)
            offsets_fit = np.array(params_fit[2 * ncomps_fit:3 * ncomps_fit],
                                   dtype=float)
            w_keep = offsets_fit > np.min(vel)
            params_fit = np.concatenate(
                [amps_fit[w_keep], fwhms_fit[w_keep], offsets_fit[w_keep]])
            fwhms_fit = np.abs(fwhms_fit[w_keep])
            ncomps_fit = len(params_fit) // 3

            amps_fit = np.array(params_fit[0:ncomps_fit], dtype=float)
            fwhms_fit = np.array(params_fit[ncomps_fit:2 * ncomps_fit],
                                 dtype=float)
            offsets_fit = np.array(params_fit[2 * ncomps_fit:3 * ncomps_fit],
                                   dtype=float)
            w_keep = fwhms_fit > dv
            params_fit = np.concatenate(
                [amps_fit[w_keep], fwhms_fit[w_keep], offsets_fit[w_keep]])
            ncomps_fit = len(params_fit) // 3
        else:
            best_fit_final = np.zeros(len(vel))
            rchi2 = -999.0
            ncomps_fit = ncomps_gf

    # Construct output dictionary (odict)
    # -----------------------------------
    odict = {}
    odict["initial_parameters"] = params_gf
    odict["N_components"] = ncomps_fit

    if (perform_final_fit) and (ncomps_fit > 0):
        odict["best_fit_parameters"] = params_fit
        odict["best_fit_errors"] = params_errs
        odict["rchi2"] = rchi2

    # ------ ADDING SUBSEQUENT FIT FOR EMISSION-ONLY COMPONENTS ---------
    # -- Find initial guesses for fit of absorption components to emission
    # -- Based on simple least-squares fit of absorption lines to emission
    # -- **Holding width and mean velocity constant for absorption lines
    # -- **Fitting only for amplitude (i.e., spin temperature)

    vel = vel_em
    data = data_em
    errors = errors_em
    dv = np.abs(vel[1] - vel[0])
    v_to_i = interp1d(vel, np.arange(len(vel)))

    params_em = []
    params_em_errs = []
    ncomps_em = 0
    if ncomps_fit > 0:

        # Objective functions for  fit
        def objective_leastsq(paramslm):
            params = vals_vec_from_lmfit(paramslm)
            resids = (func(vel, *params).ravel() - data.ravel()) / errors
            return resids

        params_full = np.concatenate(
            [params_fit,
             np.ones(ncomps_fit), params_fit[0:ncomps_fit]])

        # Final fit using constrained parameters
        t0 = time.time()
        lmfit_params = paramvec_p3_to_lmfit(params_full, wiggle, min_dv)
        result_em = lmfit_minimize(objective_leastsq,
                                   lmfit_params,
                                   method="leastsq")
        params_em = vals_vec_from_lmfit(result_em.params)
        params_em_errs = errs_vec_from_lmfit(result_em.params)
        ncomps_em = len(params_em) // 3

        # The "fitmask" is a collection of windows around the a list of two-phase absorption components
        fitmask, fitmaskw = create_fitmask(
            len(vel),
            v_to_i(params_em[2 * ncomps_em:3 * ncomps_em]),
            params_em[ncomps_em:2 * ncomps_em] / dv / 2.355 * 0.9,
        )
        notfitmask = 1 - fitmask
        # notfitmaskw = np.logical_not(fitmaskw)

        # Compute intermediate residuals
        # Median filter on 2 x effective scale to remove poor subtractions of strong components
        intermediate_model = func(
            vel, *params_em).ravel()  # Explicit final (narrow) model
        median_window = 2.0 * 10**((np.log10(alpha_em) + 2.187) / 3.859)
        residuals = median_filter(data - intermediate_model,
                                  np.int(median_window))

    else:
        residuals = data
        # Finished producing residual emission signal # ---------------------------

    # Search for phase-three guesses in residual emission spectrum
    agd3 = initialGuess(
        vel,
        residuals,
        errors,
        alpha=alpha_em,
        mode=mode,
        verbose=verbose,
        SNR_thresh=SNR_em,
        BLFrac=BLFrac,
        SNR2_thresh=SNR_thresh[0],
        deblend=deblend,
    )

    ncomps_g3 = agd3["N_components"]

    if ncomps_g3 > 0:
        params_g3 = np.concatenate(
            [agd3["amps"], agd3["FWHMs"], agd3["means"]])
    else:
        params_g3 = []
    u22 = agd3["u2"]

    # Check for phase three components, make final guess list
    # ------------------------------------------------------
    if ncomps_g3 > 0:
        abs_offsets = np.array(params_em[2 * ncomps_em:3 * ncomps_em],
                               dtype=float)
        em_widths = np.array(params_g3[ncomps_g3:2 * ncomps_g3], dtype=float)
        em_amps = np.array(params_g3[0:ncomps_g3], dtype=float)
        em_offsets = np.array(params_g3[2 * ncomps_g3:3 * ncomps_g3],
                              dtype=float)

        indices = []
        # Check if any emission components are within 3 channels of an absorption component
        if ncomps_fit > 0:
            for i, offset in enumerate(em_offsets):
                drop_comp = False
                for abs_offset in abs_offsets:
                    if np.abs(abs_offset - offset) < drop_width * dv:
                        # print(abs_offset, offset, 3.0 * dv)
                        drop_comp = True
                if not drop_comp:
                    indices.append(i)

            em_offsets = em_offsets[indices]
            em_amps = em_amps[indices]
            em_widths = em_widths[indices]
            ncomps_g3 = len(em_amps)

            # print("ncomps_em", ncomps_em)
            amps_emf = np.append(params_em[0:ncomps_em], em_amps)
            widths_emf = np.append(params_em[ncomps_em:2 * ncomps_em],
                                   em_widths)
            offsets_emf = np.append(params_em[2 * ncomps_em:3 * ncomps_em],
                                    em_offsets)
            tau_emf = np.append(params_fit[0:ncomps_em], np.zeros(ncomps_g3))
            labels_emf = np.append(np.ones(ncomps_em), np.zeros(ncomps_g3))
            # print("labels", labels_emf)
        else:
            amps_emf = em_amps
            widths_emf = em_widths
            offsets_emf = em_offsets
            tau_emf = np.zeros(ncomps_g3)
            labels_emf = np.zeros(ncomps_g3)
        params_emf = np.concatenate([amps_emf, widths_emf, offsets_emf])
        ncomps_emf = len(params_emf) // 3
    else:
        params_emf = params_em
        ncomps_emf = ncomps_em
        if ncomps_em > 0:
            tau_emf = params_fit[0:ncomps_em]
            labels_emf = np.ones(ncomps_em)
        else:
            tau_emf = []
            labels_emf = []

    params_emfit = []
    params_emfit_errs = []
    if ncomps_emf > 0:
        say("\n\n  --> Final Fitting... \n", verbose)

        # Objective functions for final fit
        def objective_leastsq(paramslm):
            params = vals_vec_from_lmfit(paramslm)
            resids = (func(vel, *params).ravel() - data.ravel()) / errors
            return resids

        # Compile parameters, labels, and original optical depths for final fit:
        params_full = np.concatenate([params_emf, labels_emf, tau_emf])

        # Final fit using constrained parameters
        t0 = time.time()
        lmfit_params = paramvec_p3_to_lmfit(params_full, wiggle, min_dv)
        result3 = lmfit_minimize(objective_leastsq,
                                 lmfit_params,
                                 method="leastsq")
        params_emfit = vals_vec_from_lmfit(result3.params)
        params_emfit_errs = errs_vec_from_lmfit(result3.params)
        ncomps_emfit = len(params_emfit) // 3

        del lmfit_params
        say("Final fit took {0} seconds.".format(time.time() - t0), verbose)

        best_fit_final = func(vel, *params_emfit).ravel()
        rchi2 = np.sum((data - best_fit_final)**2 / errors**2) / len(data)
    else:
        ncomps_emfit = ncomps_emf

    # Construct output dictionary (odict)
    # -----------------------------------
    # print(
    #     "NUMBER ABS",
    #     ncomps_fit,
    #     " NUMBER EM",
    #     len(labels_emf[labels_emf == 1]),
    #     len(params_emfit) // 3,
    #     ncomps_emfit,
    # )
    odict["N_components_em"] = ncomps_emfit
    if ncomps_emfit >= ncomps_fit:
        odict["best_fit_parameters_em"] = params_emfit
        odict["best_fit_errors_em"] = params_emfit_errs
        odict["fit_labels"] = labels_emf

    return (1, odict)
예제 #5
0
def AGD(
    vel,
    data,
    errors,
    alpha1=None,
    alpha2=None,
    # plot=False,
    mode="python",
    verbose=False,
    SNR_thresh=5.0,
    BLFrac=0.1,
    SNR2_thresh=5.0,
    deblend=True,
    perform_final_fit=True,
    phase="one",
):
    """ Autonomous Gaussian Decomposition
    """

    if type(SNR2_thresh) != type([]):
        SNR2_thresh = [SNR2_thresh, SNR2_thresh]
    if type(SNR_thresh) != type([]):
        SNR_thresh = [SNR_thresh, SNR_thresh]

    say("\n  --> AGD() \n", verbose)

    if (not alpha2) and (phase == "two"):
        print("alpha2 value required")
        return

    dv = np.abs(vel[1] - vel[0])
    v_to_i = interp1d(vel, np.arange(len(vel)))

    # --------------------------------------#
    # Find phase-one guesses               #
    # --------------------------------------#
    agd1 = initialGuess(
        vel,
        data,
        errors=None,
        alpha=alpha1,
        # plot=plot,
        mode=mode,
        verbose=verbose,
        SNR_thresh=SNR_thresh[0],
        BLFrac=BLFrac,
        SNR2_thresh=SNR2_thresh[0],
        deblend=deblend,
    )

    amps_g1, widths_g1, offsets_g1, u2 = (
        agd1["amps"],
        agd1["FWHMs"],
        agd1["means"],
        agd1["u2"],
    )
    params_g1 = np.append(np.append(amps_g1, widths_g1), offsets_g1)
    ncomps_g1 = len(params_g1) // 3
    ncomps_g2 = 0  # Default
    ncomps_f1 = 0  # Default

    # ----------------------------#
    # Find phase-two guesses #
    # ----------------------------#
    if phase == "two":
        say("Beginning phase-two AGD... ", verbose)
        ncomps_g2 = 0

        # ----------------------------------------------------------#
        # Produce the residual signal                               #
        #  -- Either the original data, or intermediate subtraction #
        # ----------------------------------------------------------#
        if ncomps_g1 == 0:
            say(
                "Phase 2 with no narrow comps -> No intermediate subtration... ",
                verbose,
            )
            residuals = data
        else:
            # "Else" Narrow components were found, and Phase == 2, so perform intermediate subtraction...

            # The "fitmask" is a collection of windows around the a list of phase-one components
            fitmask, fitmaskw = create_fitmask(len(vel), v_to_i(offsets_g1),
                                               widths_g1 / dv / 2.355 * 0.9)
            notfitmask = 1 - fitmask

            # Error function for intermediate optimization
            def objectiveD2_leastsq(paramslm):
                params = vals_vec_from_lmfit(paramslm)
                model0 = func(vel, *params)
                model2 = np.diff(np.diff(model0.ravel())) / dv / dv
                resids1 = fitmask[1:-1] * (model2 - u2[1:-1]) / errors[1:-1]
                resids2 = notfitmask * (model0 - data) / errors / 10.0
                return np.append(resids1, resids2)

            # Perform the intermediate fit using LMFIT
            t0 = time.time()
            say("Running LMFIT on initial narrow components...", verbose)
            lmfit_params = paramvec_to_lmfit(params_g1)
            result = lmfit_minimize(objectiveD2_leastsq,
                                    lmfit_params,
                                    method="leastsq")
            params_f1 = vals_vec_from_lmfit(result.params)
            ncomps_f1 = len(params_f1) // 3

            # # Make "FWHMS" positive
            # params_f1[0:ncomps_f1][params_f1[0:ncomps_f1] < 0.0] = (
            #     -1 * params_f1[0:ncomps_f1][params_f1[0:ncomps_f1] < 0.0]
            # )

            del lmfit_params
            say("LMFIT fit took {0} seconds.".format(time.time() - t0))

            if result.success:
                # Compute intermediate residuals
                # Median filter on 2x effective scale to remove poor subtractions of strong components
                intermediate_model = func(
                    vel, *params_f1).ravel()  # Explicit final (narrow) model
                median_window = 2.0 * 10**((np.log10(alpha1) + 2.187) / 3.859)
                residuals = median_filter(data - intermediate_model,
                                          np.int(median_window))
            else:
                residuals = data
            # Finished producing residual signal # ---------------------------

        # Search for phase-two guesses
        agd2 = initialGuess(
            vel,
            residuals,
            errors=None,
            alpha=alpha2,
            mode=mode,
            verbose=verbose,
            SNR_thresh=SNR_thresh[1],
            BLFrac=BLFrac,
            SNR2_thresh=SNR2_thresh[1],  # June 9 2014, change
            deblend=deblend,
            # plot=plot,
        )
        ncomps_g2 = agd2["N_components"]
        if ncomps_g2 > 0:
            params_g2 = np.concatenate(
                [agd2["amps"], agd2["FWHMs"], agd2["means"]])
        else:
            params_g2 = []
        u22 = agd2["u2"]

        # END PHASE 2 <<<

    # Check for phase two components, make final guess list
    # ------------------------------------------------------
    if phase == "two" and (ncomps_g2 > 0):
        amps_gf = np.append(params_g1[0:ncomps_g1], params_g2[0:ncomps_g2])
        widths_gf = np.append(params_g1[ncomps_g1:2 * ncomps_g1],
                              params_g2[ncomps_g2:2 * ncomps_g2])
        offsets_gf = np.append(
            params_g1[2 * ncomps_g1:3 * ncomps_g1],
            params_g2[2 * ncomps_g2:3 * ncomps_g2],
        )
        params_gf = np.concatenate([amps_gf, widths_gf, offsets_gf])
        ncomps_gf = len(params_gf) // 3
    else:
        params_gf = params_g1
        ncomps_gf = len(params_gf) // 3

    # Sort final guess list by amplitude
    # ----------------------------------
    say("N final parameter guesses: " + str(ncomps_gf))
    amps_temp = params_gf[0:ncomps_gf]
    widths_temp = params_gf[ncomps_gf:2 * ncomps_gf]
    offsets_temp = params_gf[2 * ncomps_gf:3 * ncomps_gf]
    w_sort_amp = np.argsort(amps_temp)[::-1]
    params_gf = np.concatenate([
        amps_temp[w_sort_amp], widths_temp[w_sort_amp],
        offsets_temp[w_sort_amp]
    ])

    if perform_final_fit:
        say("\n\n  --> Final Fitting... \n", verbose)

        if ncomps_gf > 0:
            # Objective functions for final fit
            def objective_leastsq(paramslm):
                params = vals_vec_from_lmfit(paramslm)
                resids = (func(vel, *params).ravel() - data.ravel()) / errors
                return resids

            # Final fit using unconstrained parameters
            t0 = time.time()
            lmfit_params = paramvec_to_lmfit(params_gf)
            result2 = lmfit_minimize(objective_leastsq,
                                     lmfit_params,
                                     method="leastsq")
            params_fit = vals_vec_from_lmfit(result2.params)
            params_errs = errs_vec_from_lmfit(result2.params)
            ncomps_fit = len(params_fit) // 3

            del lmfit_params
            say("Final fit took {0} seconds.".format(time.time() - t0),
                verbose)

            # # Make "FWHMS" positive
            # params_fit[0:ncomps_fit][params_fit[0:ncomps_fit] < 0.0] = (
            #     -1 * params_fit[0:ncomps_fit][params_fit[0:ncomps_fit] < 0.0]
            # )

            best_fit_final = func(vel, *params_fit).ravel()
            rchi2 = np.sum((data - best_fit_final)**2 / errors**2) / len(data)

            # Check if any amplitudes are identically zero, if so, remove them.
            if np.any(params_fit[0:ncomps_gf] == 0):
                amps_fit = params_fit[0:ncomps_gf]
                fwhms_fit = params_fit[ncomps_gf:2 * ncomps_gf]
                offsets_fit = params_fit[2 * ncomps_gf:3 * ncomps_gf]
                w_keep = amps_fit > 0.0
                params_fit = np.concatenate(
                    [amps_fit[w_keep], fwhms_fit[w_keep], offsets_fit[w_keep]])
                ncomps_fit = len(params_fit) // 3
        else:
            best_fit_final = np.zeros(len(vel))
            rchi2 = -999.0
            ncomps_fit = ncomps_gf

    # if plot:
    #     #                       P L O T T I N G
    #     datamax = np.max(data)
    #
    #     # Set up figure
    #     fig = plt.figure("AGD results", [12, 12])
    #     ax1 = fig.add_axes([0.1, 0.5, 0.4, 0.4])  # Initial guesses (alpha1)
    #     ax2 = fig.add_axes([0.5, 0.5, 0.4, 0.4])  # D2 fit to peaks(alpha2)
    #     ax3 = fig.add_axes([0.1, 0.1, 0.4, 0.4])  # Initial guesses (alpha2)
    #     ax4 = fig.add_axes([0.5, 0.1, 0.4, 0.4])  # Final fit
    #
    #     # Decorations
    #     plt.figtext(0.52, 0.47, "Final fit")
    #     # if perform_final_fit:
    #     #     plt.figtext(0.52, 0.45, "Reduced Chi2: {0:3.1f}".format(rchi2))
    #     #     plt.figtext(0.52, 0.43, "N components: {0}".format(ncomps_fit))
    #
    #     plt.figtext(0.12, 0.47, "Phase-two initial guess")
    #     plt.figtext(0.12, 0.45, "N components: {0}".format(ncomps_g2))
    #
    #     plt.figtext(0.12, 0.87, "Phase-one initial guess")
    #     plt.figtext(0.12, 0.85, "N components: {0}".format(ncomps_g1))
    #
    #     plt.figtext(0.52, 0.87, "Intermediate fit")
    #
    #     # Initial Guesses (Panel 1)
    #     # -------------------------
    #     ax1.xaxis.tick_top()
    #     u2_scale = 1.0 / np.max(np.abs(u2)) * datamax * 0.5
    #     ax1.plot(vel, data, "-k")
    #     ax1.plot(vel, u2 * u2_scale, "-r")
    #     ax1.plot(vel, vel / vel * agd1["thresh"], "-k")
    #     ax1.plot(vel, vel / vel * agd1["thresh2"] * u2_scale, "--r")
    #
    #     for i in range(ncomps_g1):
    #         one_component = gaussian(
    #             params_g1[i], params_g1[i + ncomps_g1], params_g1[i + 2 * ncomps_g1]
    #         )(vel)
    #         ax1.plot(vel, one_component, "-g")
    #
    #     # Plot intermediate fit components (Panel 2)
    #     # ------------------------------------------
    #     ax2.xaxis.tick_top()
    #     ax2.plot(vel, data, "-k")
    #     ax2.yaxis.tick_right()
    #     for i in range(ncomps_f1):
    #         one_component = gaussian(
    #             params_f1[i], params_f1[i + ncomps_f1], params_f1[i + 2 * ncomps_f1]
    #         )(vel)
    #         ax2.plot(vel, one_component, "-", color="blue")
    #
    #     # Residual spectrum (Panel 3)
    #     # -----------------------------
    #     if phase == "two":
    #         u22_scale = 1.0 / np.abs(u22).max() * np.max(residuals) * 0.5
    #         ax3.plot(vel, residuals, "-k")
    #         ax3.plot(vel, vel / vel * agd2["thresh"], "--k")
    #         ax3.plot(vel, vel / vel * agd2["thresh2"] * u22_scale, "--r")
    #         ax3.plot(vel, u22 * u22_scale, "-r")
    #         for i in range(ncomps_g2):
    #             one_component = gaussian(
    #                 params_g2[i], params_g2[i + ncomps_g2], params_g2[i + 2 * ncomps_g2]
    #             )(vel)
    #             ax3.plot(vel, one_component, "-g")
    #
    #     # Plot best-fit model (Panel 4)
    #     # -----------------------------
    #     if perform_final_fit:
    #         ax4.yaxis.tick_right()
    #         ax4.plot(vel, best_fit_final, label="final model", color="purple")
    #         ax4.plot(vel, data, label="data", color="black")
    #         for i in range(ncomps_fit):
    #             one_component = gaussian(
    #                 params_fit[i],
    #                 params_fit[i + ncomps_fit],
    #                 params_fit[i + 2 * ncomps_fit],
    #             )(vel)
    #             ax4.plot(vel, one_component, "-", color="purple")
    #         ax4.plot(vel, best_fit_final, "-", color="purple")
    #
    #     plt.show()
    #     plt.close()

    # Construct output dictionary (odict)
    # -----------------------------------
    odict = {}
    odict["initial_parameters"] = params_gf
    odict["N_components"] = ncomps_gf

    if (perform_final_fit) and (ncomps_gf > 0):
        odict["best_fit_parameters"] = params_fit
        odict["best_fit_errors"] = params_errs
        odict["rchi2"] = rchi2

    return (1, odict)
예제 #6
0
def AGD(vel,
        data,
        errors,
        idx=None,
        signal_ranges=None,
        noise_spike_ranges=None,
        improve_fitting_dict=None,
        alpha1=None,
        alpha2=None,
        plot=False,
        mode='conv',
        verbose=False,
        SNR_thresh=5.0,
        BLFrac=0.1,
        SNR2_thresh=5.0,
        deblend=True,
        perform_final_fit=True,
        phase='one'):
    """ Autonomous Gaussian Decomposition."""
    dct = {}
    if improve_fitting_dict is not None:
        # TODO: check if max_amp causes problems
        # dct['improve_fitting'] = improve_fitting_dict['improve_fitting']
        # dct['min_fwhm'] = improve_fitting_dict['min_fwhm']
        # dct['max_fwhm'] = improve_fitting_dict['max_fwhm']
        # dct['snr_fit'] = improve_fitting_dict['snr_fit']
        # dct['significance'] = improve_fitting_dict['significance']
        # dct['min_offset'] = improve_fitting_dict['min_offset']
        # dct['max_amp_factor'] = improve_fitting_dict['max_amp_factor']
        # dct['max_amp'] = dct['max_amp_factor']*np.max(data)
        # dct['rchi2_limit'] = improve_fitting_dict['rchi2_limit']
        # dct['snr_negative'] = improve_fitting_dict['snr_negative']
        # dct['snr'] = improve_fitting_dict['snr']
        dct = improve_fitting_dict
        dct['max_amp'] = dct['max_amp_factor'] * np.max(data)
        nChannels = len(data)
    else:
        dct['improve_fitting'] = False
        dct['max_amp'] = None
        dct['max_fwhm'] = None

    if not isinstance(SNR_thresh, list):
        SNR_thresh = [SNR_thresh, SNR_thresh]
    if not isinstance(SNR2_thresh, list):
        SNR2_thresh = [SNR2_thresh, SNR2_thresh]

    say('\n  --> AGD() \n', verbose)

    if (not alpha2) and (phase == 'two'):
        print('alpha2 value required')
        return

    dv = np.abs(vel[1] - vel[0])
    v_to_i = interp1d(vel, np.arange(len(vel)))

    # -------------------------------------- #
    # Find phase-one guesses                 #
    # -------------------------------------- #
    agd1 = initialGuess(vel,
                        data,
                        errors=errors[0],
                        alpha=alpha1,
                        plot=plot,
                        mode=mode,
                        verbose=verbose,
                        SNR_thresh=SNR_thresh[0],
                        BLFrac=BLFrac,
                        SNR2_thresh=SNR2_thresh[0],
                        deblend=deblend)

    amps_g1, widths_g1, offsets_g1, u2 = agd1['amps'], agd1['FWHMs'], agd1[
        'means'], agd1['u2']
    params_g1 = np.append(np.append(amps_g1, widths_g1), offsets_g1)
    ncomps_g1 = int(len(params_g1) / 3)
    ncomps_g2 = 0  # Default
    ncomps_f1 = 0  # Default

    # ----------------------------#
    # Find phase-two guesses #
    # ----------------------------#
    if phase == 'two':
        say('Beginning phase-two AGD... ', verbose)
        ncomps_g2 = 0

        # ----------------------------------------------------------#
        # Produce the residual signal                               #
        #  -- Either the original data, or intermediate subtraction #
        # ----------------------------------------------------------#
        if ncomps_g1 == 0:
            say(
                'Phase 2 with no narrow comps -> No intermediate subtration... ',
                verbose)
            residuals = data
        else:
            # "Else" Narrow components were found, and Phase == 2, so perform intermediate subtraction...

            # The "fitmask" is a collection of windows around the a list of phase-one components
            fitmask, fitmaskw = create_fitmask(len(vel), v_to_i(offsets_g1),
                                               widths_g1 / dv / 2.355 * 0.9)
            notfitmask = 1 - fitmask
            notfitmaskw = np.logical_not(fitmaskw)

            # Error function for intermediate optimization
            def objectiveD2_leastsq(paramslm):
                params = vals_vec_from_lmfit(paramslm)
                model0 = func(vel, *params)
                model2 = np.diff(np.diff(model0.ravel())) / dv / dv
                resids1 = fitmask[1:-1] * (model2 - u2[1:-1]) / errors[1:-1]
                resids2 = notfitmask * (model0 - data) / errors / 10.
                return np.append(resids1, resids2)

            # Perform the intermediate fit using LMFIT
            t0 = time.time()
            say('Running LMFIT on initial narrow components...', verbose)
            lmfit_params = paramvec_to_lmfit(params_g1, dct['max_amp'],
                                             dct['max_fwhm'])
            result = lmfit_minimize(objectiveD2_leastsq,
                                    lmfit_params,
                                    method='leastsq')
            params_f1 = vals_vec_from_lmfit(result.params)
            ncomps_f1 = int(len(params_f1) / 3)

            # Make "FWHMS" positive
            # params_f1[0:ncomps_f1][np.array(params_f1[0:ncomps_f1]) < 0.0] =\
            #     -1 * params_f1[0:ncomps_f1][np.array(params_f1[0:ncomps_f1]) < 0.0]

            del lmfit_params
            say('LMFIT fit took {0} seconds.'.format(time.time() - t0))

            if result.success:
                # Compute intermediate residuals
                # Median filter on 2x effective scale to remove poor subtractions of strong components
                intermediate_model = func(
                    vel, *params_f1).ravel()  # Explicit final (narrow) model
                median_window = 2. * 10**((np.log10(alpha1) + 2.187) / 3.859)
                residuals = median_filter(data - intermediate_model,
                                          np.int(median_window))
            else:
                residuals = data
            # Finished producing residual signal # ---------------------------

        # Search for phase-two guesses
        agd2 = initialGuess(
            vel,
            residuals,
            errors=errors[0],
            alpha=alpha2,
            mode=mode,
            verbose=verbose,
            SNR_thresh=SNR_thresh[1],
            BLFrac=BLFrac,
            SNR2_thresh=SNR2_thresh[1],  # June 9 2014, change
            deblend=deblend,
            plot=plot)
        ncomps_g2 = agd2['N_components']
        if ncomps_g2 > 0:
            params_g2 = np.concatenate(
                [agd2['amps'], agd2['FWHMs'], agd2['means']])
        else:
            params_g2 = []
        u22 = agd2['u2']

        # END PHASE 2 <<<

    # Check for phase two components, make final guess list
    # ------------------------------------------------------
    if phase == 'two' and (ncomps_g2 > 0):
        amps_gf = np.append(params_g1[0:ncomps_g1], params_g2[0:ncomps_g2])
        widths_gf = np.append(params_g1[ncomps_g1:2 * ncomps_g1],
                              params_g2[ncomps_g2:2 * ncomps_g2])
        offsets_gf = np.append(params_g1[2 * ncomps_g1:3 * ncomps_g1],
                               params_g2[2 * ncomps_g2:3 * ncomps_g2])
        params_gf = np.concatenate([amps_gf, widths_gf, offsets_gf])
        ncomps_gf = int(len(params_gf) / 3)
    else:
        params_gf = params_g1
        ncomps_gf = int(len(params_gf) / 3)

    # Sort final guess list by amplitude
    # ----------------------------------
    say('N final parameter guesses: ' + str(ncomps_gf))
    amps_temp = params_gf[0:ncomps_gf]
    widths_temp = params_gf[ncomps_gf:2 * ncomps_gf]
    offsets_temp = params_gf[2 * ncomps_gf:3 * ncomps_gf]
    w_sort_amp = np.argsort(amps_temp)[::-1]
    params_gf = np.concatenate([
        amps_temp[w_sort_amp], widths_temp[w_sort_amp],
        offsets_temp[w_sort_amp]
    ])

    if (perform_final_fit is True) and (ncomps_gf > 0):
        say('\n\n  --> Final Fitting... \n', verbose)

        # Objective functions for final fit
        def objective_leastsq(paramslm):
            params = vals_vec_from_lmfit(paramslm)
            resids = (func(vel, *params).ravel() - data.ravel()) / errors
            return resids

        # Final fit using unconstrained parameters
        t0 = time.time()
        lmfit_params = paramvec_to_lmfit(params_gf, dct['max_amp'], None)
        result2 = lmfit_minimize(objective_leastsq,
                                 lmfit_params,
                                 method='leastsq')
        params_fit = vals_vec_from_lmfit(result2.params)
        params_errs = errs_vec_from_lmfit(result2.params)

        del lmfit_params
        say('Final fit took {0} seconds.'.format(time.time() - t0), verbose)

        ncomps_fit = int(len(params_fit) / 3)
        # Make "FWHMS" positive
        # params_fit[0:ncomps_fit][np.array(params_fit[0:ncomps_fit]) < 0.0] =\
        #     -1 * params_fit[0:ncomps_fit][np.array(params_fit[0:ncomps_fit]) < 0.0]

        best_fit_final = func(vel, *params_fit).ravel()

    # Try to improve the fit
    # ----------------------
    if dct['improve_fitting']:
        if ncomps_gf == 0:
            ncomps_fit = 0
            params_fit = []
        #  TODO: check if ncomps_fit should be ncomps_gf
        best_fit_list, N_neg_res_peak, N_blended, log_gplus =\
            try_to_improve_fitting(
                vel, data, errors, params_fit, ncomps_fit, dct,
                signal_ranges=signal_ranges, noise_spike_ranges=noise_spike_ranges)

        params_fit, params_errs, ncomps_fit, best_fit_final, residual,\
            rchi2, aicc, new_fit, params_min, params_max, pvalue, quality_control = best_fit_list

        ncomps_gf = ncomps_fit

    if plot:
        #                       P L O T T I N G
        datamax = np.max(data)
        print(("params_fit:", params_fit))

        if ncomps_gf == 0:
            ncomps_fit = 0
            best_fit_final = data * 0

        if dct['improve_fitting']:
            rchi2 = best_fit_list[5]
        else:
            #  TODO: define mask from signal_ranges
            rchi2 = goodness_of_fit(data, best_fit_final, errors, ncomps_fit)

        # Set up figure
        fig = plt.figure('AGD results', [16, 12])
        ax1 = fig.add_axes([0.1, 0.5, 0.4, 0.4])  # Initial guesses (alpha1)
        ax2 = fig.add_axes([0.5, 0.5, 0.4, 0.4])  # D2 fit to peaks(alpha2)
        ax3 = fig.add_axes([0.1, 0.1, 0.4, 0.4])  # Initial guesses (alpha2)
        ax4 = fig.add_axes([0.5, 0.1, 0.4, 0.4])  # Final fit

        # Decorations
        if dct['improve_fitting']:
            plt.figtext(0.52, 0.47, 'Final fit (GaussPy+)')
        else:
            plt.figtext(0.52, 0.47, 'Final fit (GaussPy)')
        if perform_final_fit:
            plt.figtext(0.52, 0.45, 'Reduced Chi2: {0:3.3f}'.format(rchi2))
            plt.figtext(0.52, 0.43, 'N components: {0}'.format(ncomps_fit))

        plt.figtext(0.12, 0.47, 'Phase-two initial guess')
        plt.figtext(0.12, 0.45, 'N components: {0}'.format(ncomps_g2))

        plt.figtext(0.12, 0.87, 'Phase-one initial guess')
        plt.figtext(0.12, 0.85, 'N components: {0}'.format(ncomps_g1))

        plt.figtext(0.52, 0.87, 'Intermediate fit')

        # Initial Guesses (Panel 1)
        # -------------------------
        ax1.xaxis.tick_top()
        u2_scale = 1. / np.max(np.abs(u2)) * datamax * 0.5
        ax1.axhline(color='black', linewidth=0.5)
        ax1.plot(vel, data, '-k')
        ax1.plot(vel, u2 * u2_scale, '-r')
        ax1.plot(vel, np.ones(len(vel)) * agd1['thresh'], '--k')
        ax1.plot(vel, np.ones(len(vel)) * agd1['thresh2'] * u2_scale, '--r')

        for i in range(ncomps_g1):
            one_component = gaussian(params_g1[i], params_g1[i + ncomps_g1],
                                     params_g1[i + 2 * ncomps_g1])(vel)
            ax1.plot(vel, one_component, '-g')

        # Plot intermediate fit components (Panel 2)
        # ------------------------------------------
        ax2.xaxis.tick_top()
        ax2.axhline(color='black', linewidth=0.5)
        ax2.plot(vel, data, '-k')
        ax2.yaxis.tick_right()
        for i in range(ncomps_f1):
            one_component = gaussian(params_f1[i], params_f1[i + ncomps_f1],
                                     params_f1[i + 2 * ncomps_f1])(vel)
            ax2.plot(vel, one_component, '-', color='blue')

        # Residual spectrum (Panel 3)
        # -----------------------------
        if phase == 'two':
            u22_scale = 1. / np.abs(u22).max() * np.max(residuals) * 0.5
            ax3.axhline(color='black', linewidth=0.5)
            ax3.plot(vel, residuals, '-k')
            ax3.plot(vel, np.ones(len(vel)) * agd2['thresh'], '--k')
            ax3.plot(vel,
                     np.ones(len(vel)) * agd2['thresh2'] * u22_scale, '--r')
            ax3.plot(vel, u22 * u22_scale, '-r')
            for i in range(ncomps_g2):
                one_component = gaussian(params_g2[i],
                                         params_g2[i + ncomps_g2],
                                         params_g2[i + 2 * ncomps_g2])(vel)
                ax3.plot(vel, one_component, '-g')

        # Plot best-fit model (Panel 4)
        # -----------------------------
        if perform_final_fit:
            ax4.yaxis.tick_right()
            ax4.axhline(color='black', linewidth=0.5)
            ax4.plot(vel, data, label='data', color='black')
            for i in range(ncomps_fit):
                one_component = gaussian(params_fit[i],
                                         params_fit[i + ncomps_fit],
                                         params_fit[i + 2 * ncomps_fit])(vel)
                ax4.plot(vel, one_component, '--', color='orange')
            ax4.plot(vel, best_fit_final, '-', color='orange', linewidth=2)

        plt.show()

    # Construct output dictionary (odict)
    # -----------------------------------
    odict = {}
    odict['initial_parameters'] = params_gf

    odict['N_components'] = ncomps_gf
    odict['index'] = idx
    if dct['improve_fitting']:
        odict['best_fit_rchi2'] = rchi2
        odict['best_fit_aicc'] = aicc
        odict['pvalue'] = pvalue

        odict['N_neg_res_peak'] = N_neg_res_peak
        odict['N_blended'] = N_blended
        odict['log_gplus'] = log_gplus
        odict['quality_control'] = quality_control

    if (perform_final_fit is True) and (ncomps_gf > 0):
        odict['best_fit_parameters'] = params_fit
        odict['best_fit_errors'] = params_errs

    return (1, odict)
예제 #7
0
def AGD(vel,
        data,
        errors,
        alpha1=None,
        alpha2=None,
        plot=False,
        mode='c',
        verbose=False,
        SNR_thresh=5.0,
        BLFrac=0.1,
        SNR2_thresh=5.0,
        deblend=True,
        perform_final_fit=True,
        phase='one'):
    """ Autonomous Gaussian Decomposition
    """

    if type(SNR2_thresh) != type([]): SNR2_thresh = [SNR2_thresh, SNR2_thresh]
    if type(SNR_thresh) != type([]): SNR_thresh = [SNR_thresh, SNR_thresh]

    say('\n  --> AGD() \n', verbose)

    if (not alpha2) and (phase == 'two'):
        print 'alpha2 value required'
        return

    dv = np.abs(vel[1] - vel[0])
    v_to_i = interp1d(vel, np.arange(len(vel)))

    #--------------------------------------#
    # Find phase-one guesses               #
    #--------------------------------------#
    agd1 = initialGuess(vel,
                        data,
                        errors=None,
                        alpha=alpha1,
                        plot=plot,
                        mode=mode,
                        verbose=verbose,
                        SNR_thresh=SNR_thresh[0],
                        BLFrac=BLFrac,
                        SNR2_thresh=SNR2_thresh[0],
                        deblend=deblend)

    amps_g1, widths_g1, offsets_g1, u2 = agd1['amps'], agd1['FWHMs'], agd1[
        'means'], agd1['u2']
    params_g1 = np.append(np.append(amps_g1, widths_g1), offsets_g1)
    ncomps_g1 = len(params_g1) / 3
    ncomps_g2 = 0  # Default
    ncomps_f1 = 0  # Default

    # ----------------------------#
    # Find phase-two guesses #
    # ----------------------------#
    if phase == 'two':
        say('Beginning phase-two AGD... ', verbose)
        ncomps_g2 = 0.

        # ----------------------------------------------------------#
        # Produce the residual signal                               #
        #  -- Either the original data, or intermediate subtraction #
        # ----------------------------------------------------------#
        if ncomps_g1 == 0:
            say(
                'Phase 2 with no narrow comps -> No intermediate subtration... ',
                verbose)
            residuals = data
        else:
            # "Else" Narrow components were found, and Phase == 2, so perform intermediate subtraction...

            # The "fitmask" is a collection of windows around the a list of phase-one components
            fitmask, fitmaskw = create_fitmask(len(vel), v_to_i(offsets_g1),
                                               widths_g1 / dv / 2.355 * 0.9)
            notfitmask = 1 - fitmask
            notfitmaskw = np.logical_not(fitmaskw)

            # Error function for intermediate optimization
            def objectiveD2_leastsq(paramslm):
                params = vals_vec_from_lmfit(paramslm)
                model0 = func(vel, *params)
                model2 = np.diff(np.diff(model0.ravel())) / dv / dv
                resids1 = fitmask[1:-1] * (model2 - u2[1:-1]) / errors[1:-1]
                resids2 = notfitmask * (model0 - data) / errors / 10.
                return np.append(resids1, resids2)

            # Perform the intermediate fit using LMFIT
            t0 = time.time()
            say('Running LMFIT on initial narrow components...', verbose)
            lmfit_params = paramvec_to_lmfit(params_g1)
            result = lmfit_minimize(objectiveD2_leastsq,
                                    lmfit_params,
                                    method='leastsq')
            params_f1 = vals_vec_from_lmfit(result.params)
            ncomps_f1 = len(params_f1) / 3

            # Make "FWHMS" positive
            params_f1[0:ncomps_f1][
                params_f1[0:ncomps_f1] < 0.0] = -1 * params_f1[0:ncomps_f1][
                    params_f1[0:ncomps_f1] < 0.0]

            del lmfit_params
            say('LMFIT fit took {0} seconds.'.format(time.time() - t0))

            if result.success:
                # Compute intermediate residuals
                # Median filter on 2x effective scale to remove poor subtractions of strong components
                intermediate_model = func(
                    vel, *params_f1).ravel()  # Explicit final (narrow) model
                median_window = 2. * 10**((np.log10(alpha1) + 2.187) / 3.859)
                residuals = median_filter(data - intermediate_model,
                                          np.int(median_window))
            else:
                residuals = data
            # Finished producing residual signal # ---------------------------

        # Search for phase-two guesses
        agd2 = initialGuess(
            vel,
            residuals,
            errors=None,
            alpha=alpha2,
            mode=mode,
            verbose=verbose,
            SNR_thresh=SNR_thresh[1],
            BLFrac=BLFrac,
            SNR2_thresh=SNR2_thresh[1],  # June 9 2014, change 
            deblend=deblend,
            plot=plot)
        ncomps_g2 = agd2['N_components']
        if ncomps_g2 > 0:
            params_g2 = np.concatenate(
                [agd2['amps'], agd2['FWHMs'], agd2['means']])
        else:
            params_g2 = []
        u22 = agd2['u2']

        # END PHASE 2 <<<

    # Check for phase two components, make final guess list
    # ------------------------------------------------------
    if phase == 'two' and (ncomps_g2 > 0):
        amps_gf = np.append(params_g1[0:ncomps_g1], params_g2[0:ncomps_g2])
        widths_gf = np.append(params_g1[ncomps_g1:2 * ncomps_g1],
                              params_g2[ncomps_g2:2 * ncomps_g2])
        offsets_gf = np.append(params_g1[2 * ncomps_g1:3 * ncomps_g1],
                               params_g2[2 * ncomps_g2:3 * ncomps_g2])
        params_gf = np.concatenate([amps_gf, widths_gf, offsets_gf])
        ncomps_gf = len(params_gf) / 3
    else:
        params_gf = params_g1
        ncomps_gf = len(params_gf) / 3

    # Sort final guess list by amplitude
    # ----------------------------------
    say('N final parameter guesses: ' + str(ncomps_gf))
    amps_temp = params_gf[0:ncomps_gf]
    widths_temp = params_gf[ncomps_gf:2 * ncomps_gf]
    offsets_temp = params_gf[2 * ncomps_gf:3 * ncomps_gf]
    w_sort_amp = np.argsort(amps_temp)[::-1]
    params_gf = np.concatenate([
        amps_temp[w_sort_amp], widths_temp[w_sort_amp],
        offsets_temp[w_sort_amp]
    ])

    if (perform_final_fit == True) and (ncomps_gf > 0):
        say('\n\n  --> Final Fitting... \n', verbose)

        # Objective functions for final fit
        def objective_leastsq(paramslm):
            params = vals_vec_from_lmfit(paramslm)
            resids = (func(vel, *params).ravel() - data.ravel()) / errors
            return resids

        # Final fit using unconstrained parameters
        t0 = time.time()
        lmfit_params = paramvec_to_lmfit(params_gf)
        result2 = lmfit_minimize(objective_leastsq,
                                 lmfit_params,
                                 method='leastsq')
        params_fit = vals_vec_from_lmfit(result2.params)
        params_errs = errs_vec_from_lmfit(result2.params)
        ncomps_fit = len(params_fit) / 3

        del lmfit_params
        say('Final fit took {0} seconds.'.format(time.time() - t0), verbose)

        # Make "FWHMS" positive
        params_fit[0:ncomps_fit][
            params_fit[0:ncomps_fit] < 0.0] = -1 * params_fit[0:ncomps_fit][
                params_fit[0:ncomps_fit] < 0.0]

        best_fit_final = func(vel, *params_fit).ravel()
        rchi2 = np.sum((data - best_fit_final)**2 / errors**2) / len(data)

        # Check if any amplitudes are identically zero, if so, remove them.
        if np.any(params_fit[0:ncomps_gf] == 0.0):
            amps_fit = params_fit[0:ncomps_gf]
            fwhms_fit = params_fit[ncomps_gf:2 * ncomps_gf]
            offsets_fit = params_fit[2 * ncomps_gf:3 * ncomps_gf]
            w_keep = amps_fit > 0.
            params_fit = np.concatenate(
                [amps_fit[w_keep], fwhms_fit[w_keep], offsets_fit[w_keep]])
            ncomps_fit = len(params_fit) / 3

    if plot:
        #                       P L O T T I N G
        datamax = np.max(data)

        # Set up figure
        fig = plt.figure('AGD results', [12, 12])
        ax1 = fig.add_axes([0.1, 0.5, 0.4, 0.4])  # Initial guesses (alpha1)
        ax2 = fig.add_axes([0.5, 0.5, 0.4, 0.4])  # D2 fit to peaks(alpha2)
        ax3 = fig.add_axes([0.1, 0.1, 0.4, 0.4])  # Initial guesses (alpha2)
        ax4 = fig.add_axes([0.5, 0.1, 0.4, 0.4])  # Final fit

        # Decorations
        plt.figtext(0.52, 0.47, 'Final fit')
        if perform_final_fit:
            plt.figtext(0.52, 0.45, 'Reduced Chi2: {0:3.1f}'.format(rchi2))
            plt.figtext(0.52, 0.43, 'N components: {0}'.format(ncomps_fit))

        plt.figtext(0.12, 0.47, 'Phase-two initial guess')
        plt.figtext(0.12, 0.45, 'N components: {0}'.format(ncomps_g2))

        plt.figtext(0.12, 0.87, 'Phase-one initial guess')
        plt.figtext(0.12, 0.85, 'N components: {0}'.format(ncomps_g1))

        plt.figtext(0.52, 0.87, 'Intermediate fit')

        # Initial Guesses (Panel 1)
        # -------------------------
        ax1.xaxis.tick_top()
        u2_scale = 1. / np.max(np.abs(u2)) * datamax * 0.5
        ax1.plot(vel, data, '-k')
        ax1.plot(vel, u2 * u2_scale, '-r')
        ax1.plot(vel, vel / vel * agd1['thresh'], '-k')
        ax1.plot(vel, vel / vel * agd1['thresh2'] * u2_scale, '--r')

        for i in range(ncomps_g1):
            one_component = gaussian(params_g1[i], params_g1[i + ncomps_g1],
                                     params_g1[i + 2 * ncomps_g1])(vel)
            ax1.plot(vel, one_component, '-g')

        # Plot intermediate fit components (Panel 2)
        # ------------------------------------------
        ax2.xaxis.tick_top()
        ax2.plot(vel, data, '-k')
        ax2.yaxis.tick_right()
        for i in range(ncomps_f1):
            one_component = gaussian(params_f1[i], params_f1[i + ncomps_f1],
                                     params_f1[i + 2 * ncomps_f1])(vel)
            ax2.plot(vel, one_component, '-', color='blue')

        # Residual spectrum (Panel 3)
        # -----------------------------
        if phase == 'two':
            u22_scale = 1. / np.abs(u22).max() * np.max(residuals) * 0.5
            ax3.plot(vel, residuals, '-k')
            ax3.plot(vel, vel / vel * agd2['thresh'], '--k')
            ax3.plot(vel, vel / vel * agd2['thresh2'] * u22_scale, '--r')
            ax3.plot(vel, u22 * u22_scale, '-r')
            for i in range(ncomps_g2):
                one_component = gaussian(params_g2[i],
                                         params_g2[i + ncomps_g2],
                                         params_g2[i + 2 * ncomps_g2])(vel)
                ax3.plot(vel, one_component, '-g')

        # Plot best-fit model (Panel 4)
        # -----------------------------
        if perform_final_fit:
            ax4.yaxis.tick_right()
            ax4.plot(vel, best_fit_final, label='final model', color='purple')
            ax4.plot(vel, data, label='data', color='black')
            for i in range(ncomps_fit):
                one_component = gaussian(params_fit[i],
                                         params_fit[i + ncomps_fit],
                                         params_fit[i + 2 * ncomps_fit])(vel)
                ax4.plot(vel, one_component, '-', color='purple')
            ax4.plot(vel, best_fit_final, '-', color='purple')

        plt.show()

    # Construct output dictionary (odict)
    # -----------------------------------
    odict = {}
    odict['initial_parameters'] = params_gf
    odict['N_components'] = ncomps_gf

    if (perform_final_fit == True) and (ncomps_gf > 0):
        odict['best_fit_parameters'] = params_fit
        odict['best_fit_errors'] = params_errs
        odict['rchi2'] = rchi2

    return (1, odict)
예제 #8
0
def decompose(x,
              y,
              plot_fit=True,
              diagnostic_plots=False,
              min_peak=0.0,
              **wiener_kwargs):
    '''
    Use a Wiener filter to create a low-pass filter of `y`, find the peaks,
    and, starting with the largest peak, fit gaussian components until the
    AIC stops decreasing.
    '''

    yfilt = wiener_filter(x, y, **wiener_kwargs)

    # Catch where the PSD outputs have been passed
    y_lp = yfilt[0]
    y_hp = yfilt[1]

    if len(yfilt) > 2:
        PSD_out = yfilt[2:]

    # Estimate the noise level from the high-pass filtered data
    noise_std = astats.mad_std(y_hp)

    # Get peaks in the low-pass spectrum using the minima in the 2nd
    # derivative
    # y_spl = UnivariateSpline(x, y_lp, s=0, k=4)
    # y_spl_2der = y_spl.derivative(n=2)

    # import matplotlib.pyplot as plt
    # plt.plot(x, y_lp)
    # plt.plot(x, y_spl(x))

    peaks_idx = extrema_find(y_lp, mode='peak')
    # peaks_idx = extrema_find(y_spl_2der(x), mode='dip')
    peaks = x[peaks_idx]
    peak_vals = y_lp[peaks_idx]

    # Remove peaks below min_peak
    peaks = peaks[peak_vals >= min_peak]
    peak_vals = peak_vals[peak_vals >= min_peak]

    peaks = peaks[np.argsort(peak_vals)[::-1]]
    peak_vals = peak_vals[np.argsort(peak_vals)[::-1]]

    # NOTE: This needs to be set using the Wiener filter width! Or some other
    # estimator.
    # init_width = 0.25
    init_width = 10000
    # init_width = 100

    # u2 = np.diff(np.diff(y_lp))  # / np.diff(x[:2])[0]
    # Find points of inflection
    # inflection = np.abs(np.diff(np.sign(u2)))

    # Find Relative widths, then measure
    # peak-to-inflection distance for sharpest peak
    # widths = np.sqrt(np.abs(y_lp / y_spl_2der(x))[peaks_idx])

    # import matplotlib.pyplot as plt
    # plt.plot(x, np.sqrt(np.abs(y_lp / y_spl_2der(x))))
    # for peak in peaks:
    #     plt.axvline(peak)

    # print(widths)
    # print(argh)

    # FWHMs = widths * 2.355
    aics = []
    bics = []

    for i in range(len(peaks)):
        if i == 0:
            v0 = (peak_vals[i], init_width, peaks[i])
        else:
            v0 += (peak_vals[i], init_width, peaks[i])

        v0_lmfit = gaussian_params_lmfit(v0)

        def objective_leastsq(paramslm):
            resids = (func(x, paramslm).ravel() - y) / noise_std
            return resids

        # Final fit using unconstrained parameters
        result2 = lmfit_minimize(objective_leastsq, v0_lmfit, method='leastsq')
        ncomps_fit = i + 1
        params_fit = vals_vec_from_lmfit(result2.params)

        if diagnostic_plots:
            import matplotlib.pyplot as plt
            plt.plot(x, y, alpha=0.7)
            plt.plot(x, y_lp)
            for peak in peaks:
                plt.axvline(peak, color='g', linestyle='-.', alpha=0.5)
            plt.plot(x, func(x, result2.params))

            for j in range(1, ncomps_fit + 1):
                pars = params_fit[j]
                num_str = "{}".format(j)
                plt.plot(x,
                         gaussian(pars['amp' + num_str],
                                  pars['fwhm' + num_str],
                                  pars['cent' + num_str])(x),
                         label=num_str)
            plt.legend()
            plt.draw()
            raw_input("?")
            plt.clf()

        if i == 0:
            aics.append(result2.aic)
            bics.append(result2.bic)
            result_prev = result2
            params_fit_prev = params_fit
            ncomps_fit_prev = ncomps_fit
            continue
        elif result2.aic > aics[i - 1] or result2.bic > bics[i - 1]:
            aics.append(result2.aic)
            bics.append(result2.bic)
            break
        else:
            aics.append(result2.aic)
            bics.append(result2.bic)
            result_prev = result2
            params_fit_prev = params_fit
            ncomps_fit_prev = ncomps_fit

    print("AICS: {}".format(aics))
    print("BICS: {}".format(bics))

    if plot_fit:
        import matplotlib.pyplot as plt
        plt.plot(x, y, alpha=0.7)
        plt.plot(x, y_lp)
        plt.axhline(noise_std)
        for peak in peaks:
            plt.axvline(peak, color='g', linestyle='-.', alpha=0.5)
        for i in range(1, ncomps_fit_prev + 1):
            pars = params_fit_prev[i]
            num_str = "{}".format(i)
            plt.plot(x,
                     gaussian(pars['amp' + num_str], pars['fwhm' + num_str],
                              pars['cent' + num_str])(x),
                     label=num_str)
        plt.plot(x, func(x, result_prev.params))

        plt.legend(frameon=True)

    return result_prev, params_fit_prev
예제 #9
0
    def fit_line(self, x, y, zero_lev, err_continuum, Ncomps, fitting_parameters, fitting_parameters_wide, iterations): 
        
        #Number of x points in the spectral line
        x_Grid_Length = len(x)
                
        #Generate empty containers to store the data
        self.Fitting_dict['FluxI_N_vector'] = zeros(iterations)
        for key in self.Fitting_dict['parameters_list']:
            self.Fitting_dict[key + '_norm']    = zeros(iterations)
            self.Fitting_dict[key + '_norm_er'] = zeros(iterations)
                   
        #Loop through the iterations (Only 1 if it is not a bootstrap)
        for i in range(iterations):
            
            #Fit narrow component           
            if i == 0:
                y_new = y
            else:
                noise_array = random.normal(0.0, err_continuum, x_Grid_Length).astype(float32)
                y_new       = y + noise_array
                
            fit_Output      = lmfit_minimize(self.lmfit_gaussian_Residual, fitting_parameters, args=(x, y_new, zero_lev, Ncomps, err_continuum))
            output_params   = fit_Output.params
              
            #Case with a wide component
            if self.Fitting_dict['Add_wideComponent']:
                sigma_limit = fit_Output.params['sigma1'].value
                limit_0 = 6548.05 - self.Fitting_dict['x_scaler']  - sigma_limit * 1.5
                limit_1 = 6548.05 - self.Fitting_dict['x_scaler']  + sigma_limit * 1.5
                limit_2 = 0  - sigma_limit * 4
                limit_3 = 0  + sigma_limit * 4 
                limit_4 = 6583.46 - self.Fitting_dict['x_scaler']  - sigma_limit * 3
                limit_5 = 6583.46 - self.Fitting_dict['x_scaler']  + sigma_limit * 3
                
                indeces = ((x >= limit_0) & (x <= limit_1)) + ((x >= limit_2) & (x <= limit_3)) + ((x >= limit_4) & (x <= limit_5))    
                mask    = invert(indeces)
                self.Fitting_dict['wide mask'] = mask
                
                x_wide      = x[mask]
                y_wide      = y_new[mask]
                zero_wide   = zero_lev[mask]
                Ncomps_wide = ['3']
                
#                 y_wide[(y_wide < (mean(self.Fitting_dict['zerolev_norm']) - self.Fitting_dict['sig_zerolev_norm']))] = mean(self.Fitting_dict['zerolev_norm'])
                
                fit_Output_wide     = lmfit_minimize(self.lmfit_gaussian_Residual_wide, fitting_parameters_wide, args=(x_wide, y_wide, zero_wide, Ncomps_wide, err_continuum))

                #Substract the wide components
                y_wide              = self.gaussian_curve_SingleMixture_wide(fit_Output_wide.params.valuesdict(), x, zero_lev, Ncomps_wide)

                y_pure_emission     = y_new - y_wide + zero_lev 
                self.Fitting_dict['emis_limpio'] = y_pure_emission 
                
                fit_Output_emission = lmfit_minimize(self.lmfit_gaussian_Residual, fitting_parameters, args=(x, y_pure_emission, zero_lev, Ncomps, err_continuum))

                output_params = fit_Output_emission.params + fit_Output_wide.params
                
                print fit_report(output_params)
                print 'flux relation', output_params['FluxG2'] /  output_params['FluxG0']
                print 'A2*sigma2 / (A0*sigma0)', (output_params['A2'] * output_params['sigma2']) /  (output_params['A0'] * output_params['sigma0'])
                
            #Store the integrated flux
            self.Fitting_dict['FluxI_N_vector'][i]  = simps(y_new, x) - simps(zero_lev, x)
          
            #Store the fitting parameters
            for key in self.Fitting_dict['parameters_list']:
                self.Fitting_dict[key + '_norm'][i]     = output_params[key].value
                self.Fitting_dict[key + '_norm_er'][i]  = output_params[key].stderr
                
        #Store the output fit (Only used for single line output)        
        self.Fitting_dict['lmfit_output'] = fit_Output
        
        #Finally increase the number of components in case of a wide component
        if self.Fitting_dict['Add_wideComponent']:
            self.Fitting_dict['blended number'] = self.Fitting_dict['blended number'] + 1
        
        return
예제 #10
0
        v0 = (peak_vals[i], init_width, peaks[i])
    else:
        v0 += (peak_vals[i], init_width, peaks[i])

    v0_lmfit = paramvec_to_lmfit(v0)

    def objective_leastsq(paramslm):
        if not isinstance(paramslm, tuple):
            params = vals_vec_from_lmfit(paramslm)
        else:
            params = paramslm
        resids = (func(x, *params).ravel() - ndata) / noise_std
        return resids

    # Final fit using unconstrained parameters
    result2 = lmfit_minimize(objective_leastsq, v0_lmfit, method='leastsq')
    params_fit = vals_vec_from_lmfit(result2.params)
    params_errs = errs_vec_from_lmfit(result2.params)
    ncomps_fit = len(params_fit) / 3

    if i == 0:
        aics.append(result2.aic)
        result_old = result2
        params_fit_old = params_fit
        params_errs_old = params_errs
        ncomps_fit_old = ncomps_fit
        continue
    elif result2.aic > aics[i - 1]:
        break
    else:
        aics.append(result2.aic)