Exemplo n.º 1
0
def precursor_heuristics(q_I):
    """Guess radius of gyration and Guinier prefactor of scatterers.

    Parameters
    ----------
    q_I : array
        n-by-2 array of q (scattering vector magnitude) 
        and I (intensity at q)

    Returns
    -------
    rg_pre : float
        estimated radius of gyration 
    G_pre : float
        estimated Guinier factor
    """
    n_q = len(q_I[:,0])
    ## use the higher-q regions 
    highq_I = q_I[int(n_q*1./2):,:] 
    #highq_I = q_I[int(n_q*3./4):,:] 
    fit_obj = lambda x: fit_guinier_porod(highq_I,x[0],4,x[1])
    idx_nz = highq_I[:,1]>0
    #fit_obj = lambda x: -1*compute_pearson(
    #    np.log(guinier_porod(highq_I[idx_nz,0],x[0],4,x[1])),
    #    np.log( highq_I[idx_nz,1]) )
    ##res = scipimin(fit_ojb,[0.1],bounds=[(0,0.3)]) 
    res = scipimin(fit_obj,[1,1],bounds=[(1E-3,10),(1E-3,None)])
    rg_opt, G_opt = res.x
    I_pre = guinier_porod(q_I[:,0],rg_opt,4,G_opt)
    return rg_opt, G_opt
Exemplo n.º 2
0
 def calibrate(self):
     if self.flowrate_table is not None:
         flow_setpts = self.flowrate_table[:, 0]
         flow_meas = self.flowrate_table[:, 1]
         fit_obj = lambda a: np.sum([(fset**a - fmeas)**2
                                     for fset, fmeas in zip(
                                         flow_setpts, flow_meas)])
         res = scipimin(fit_obj, 1.)
         self.flow_conversion_power = res.x[0]
         if self.verbose:
             msg = 'finished calibrating- power law flowrate conversion: {}'.format(
                 self.flow_conversion_power)
             self.message_callback(msg)
Exemplo n.º 3
0
def spherical_normal_heuristics(q_I,I_at_0):
    """Guess mean and std of radii for spherical scatterers.

    This algorithm was developed and 
    originally contributed by Amanda Fournier.    

    Performs some heuristic measurements on the input spectrum,
    in order to make educated guesses 
    for the parameters of a size distribution
    (mean and standard deviation of radius)
    for a population of spherical scatterers.

    TODO: Document algorithm here.
    """
    m = saxs_Iq4_metrics(q_I)

    width_metric = m['pI_qwidth']/m['q_at_Iqqqq_min1']
    intensity_metric = m['I_at_Iqqqq_min1']/I_at_0
    #######
    #
    # The function spherical_normal_heuristics_setup()
    # (in this same module) should be used to regenerate these polynomials
    # if any part of saxs_Iq4_metrics() is changed.
    # polynomial coefs for qr0 focus: 
    p_f = [16.86239254,8.85709143,-11.10439599,-0.26735688,4.49884714]
    # polynomial coefs for width metric: 
    p_w = [12.42148677,-16.85723287,7.43401497,-0.38234993,0.06203096]
    # polynomial coefs for intensity metric: 
    p_I = [1.19822603,-1.20386273,2.88652860e-01,1.78677430e-02,-2.67888841e-04]
    #
    #######
    # Find the sigma_r/r0 value that gets the extracted metrics
    # as close as possible to p_I and p_w.
    width_error = lambda x: (np.polyval(p_w,x)-width_metric)**2
    intensity_error = lambda x: (np.polyval(p_I,x)-intensity_metric)**2
    # TODO: make the objective function weight all errors equally
    heuristics_error = lambda x: width_error(x) + intensity_error(x)
    res = scipimin(heuristics_error,[0.1],bounds=[(0,0.45)]) 
    sigma_over_r = res.x[0]
    qr0_focus = np.polyval(p_f,sigma_over_r)
    # qr0_focus = x1  ==>  r0 = x1 / q1
    r0 = qr0_focus/m['q_at_Iqqqq_min1']
    #print('r0: {}, sigma/r: {}'.format(r0,sigma_over_r))
    return r0,sigma_over_r
Exemplo n.º 4
0
    def fit(self, params=None, fixed_params=None, objective='chi2log'):
        """Fit the SAXS spectrum, optionally holding some parameters fixed.
    
        Parameters
        ----------
        params : dict
            Dict of scattering equation parameters (initial guess).
            See saxs_math module documentation.
            If not provided, some defaults are chosen.
        fixed_params : dict, optional
            Dict of floats giving values in `params`
            that should be held constant during fitting.
            The structure of this dict should constitute
            a subset of the structure of the `params` dict.
            Entries in `fixed_params` take precedence 
            over the corresponding entries in `params`, so that the 
            initial condition does not violate the constraint.
            Entries in `fixed_params` that are outside 
            the structure of the `params` dict will be ignored.
        objective : string
            Choice of objective function 
            (currently the only option is 'chi2log').

        Returns
        -------
        p_opt : dict
            Dict of optimized SAXS equation parameters,
            with the same shape as the input `params`.
        rpt : dict
            Dict reporting quantities of interest
            pertaining to the fit result.
        """

        if bool(self.populations['unidentified']):
            return OrderedDict(), OrderedDict()

        if params is None:
            params = self.default_params()

        x_init, x_keys, param_idx = self.unpack_params(params)
        x_bounds = []
        for k in x_keys:
            x_bounds.append(param_limits[k])

        # --- constraints ---
        c = []
        if fixed_params is not None:
            for pk, pvals in fixed_params.items():
                if pk in params.keys():
                    for idx, val in enumerate(pvals):
                        if idx < len(params[pk]):
                            params[pk][idx] = val
                            fix_idx = param_idx[pk][idx]
                            cfun = lambda x: x[fix_idx] - x_init[fix_idx]
                            c.append({'type': 'eq', 'fun': cfun})
        # TODO: inequality constraint on I0_floor, G_gp, and I0_sphere,
        # to prevent amplitudes from going to zero
        #if objective in ['chi2log_fixI0']:
        #    if len(I_idx) > 0:
        #        # Set up a constraint to keep I(q=0) fixed
        #        I0_init = np.sum([x_init[i] for i in I_idx.values()])
        #        cfun = lambda x: np.sum([x[I_idx[k]] for k in I_idx.keys()]) - I0_init
        #        c.append({'type':'eq','fun':cfun})
        # --- end constraints ---

        fit_obj = self.fit_objfun(params)
        #fit_obj = saxs_chi2log(flags,params,q_I)
        res = scipimin(fit_obj,
                       x_init,
                       bounds=x_bounds,
                       options={'ftol': 1E-3},
                       constraints=c)
        p_opt = self.pack_params(res.x, x_keys)
        rpt = self.fit_report(p_opt)
        return p_opt, rpt
Exemplo n.º 5
0
def fit_spectrum(q_I,flags,params,fixed_params,objective='chi2log'):
    """Fit a SAXS spectrum, given population flags and parameter guesses.

    Parameters
    ----------
    q_I : array
        n-by-2 array of scattering vector q (1/Angstrom) and intensity.
    flags : dict
        dictionary of flags indicating various scatterer populations.
    params : dict
        scattering equation parameters to use as intial condition for fitting.
    fixed_params : list
        list of keys (strings) indicating which entries in `params` 
        should be held constant during the optimization. 
    objective : str
        choice of objective function for the fitting. supported objectives:
        - 'chi2log': sum of difference of logarithm, squared, across entire q range. 
        - 'chi2log_fixI0': like chi2log, but with I(q=0) constrained. 

    Returns
    -------
    params_opt : dict
        Dict of scattering equation parameters 
        optimized to fit `q_I` under `objective`.
    """

    f_bd = flags['bad_data']
    f_pre = flags['precursor_scattering']
    f_form = flags['form_factor_scattering']
    f_pks = flags['diffraction_peaks']
    q = q_I[:,0]
    n_q = len(q)
    I = q_I[:,1]

    # trim non-flagged populations out of fit_params:
    # fit as few of the requested fit_params as possible
    #if not f_pre:
    #    if 'rg_precursor' in fit_params:
    #        fit_params.pop(fit_params.index('rg_precursor')) 
    #    if 'G_precursor' in fit_params:
    #        fit_params.pop(fit_params.index('G_precursor')) 
    #if not f_form:
    #    if 'I0_sphere' in fit_params:
    #        fit_params.pop(fit_params.index('I0_sphere')) 
    #    if 'r0_sphere' in fit_params:
    #        fit_params.pop(fit_params.index('r0_sphere')) 
    #    if 'sigma_sphere' in fit_params:
    #        fit_params.pop(fit_params.index('sigma_sphere')) 

    # index the params into an array 
    x_init = np.zeros(len(params))
    x_bounds = [] 
    I_keys = ['I0_floor','G_precursor','I0_sphere']
    p_idx = {} 
    I_idx = {} 
    for i,k in zip(range(len(params)),params.keys()):
        p_idx[k] = i 
        x_init[i] = params[k]
        if k in I_keys:
            I_idx[k] = i
        if k in ['rg_precursor','r0_sphere']:
            x_bounds.append((1E-3,None))
        elif k in ['G_precursor','I0_sphere','I0_floor']:
            x_bounds.append((0.0,None))
        elif k in ['sigma_sphere']:
            x_bounds.append((0.0,0.45))
        else:
            x_bounds.append((None,None))
    
    c = []
    if objective in ['chi2log_fixI0']:
        if len(I_idx) > 0:
            # Set up a constraint to keep I(q=0) fixed
            I0_init = np.sum([x_init[i] for i in I_idx])
            cfun = lambda x: np.sum([x[i] for i in I_idx] - I0_init)
            c.append({'type':'eq','fun':cfun})

    for fixk in fixed_params:
        cfun = lambda x: x[p_idx[fixk]] - params[fixk]
        c.append({'type':'eq','fun':cfun})

    p_opt = copy.deepcopy(params) 
    # Only proceed if there is work to do.
    if not f_pks and not f_bd:
        saxs_fun = lambda q,x,p: compute_saxs_with_substitutions(q,flags,p,params.keys(),x)
        if objective in ['chi2log','chi2log_fixI0']:
            #idx_fit = ((I>0)&(q>0.1))
            if f_pre and not f_form and not f_pks:
                # if we are fitting only precursors,
                # intensities should be relatively low,
                # so low-q region can be problematic. 
                # TODO: consider whether this special case is justified,
                # or come up with a better way to filter high-error low-q points
                idx_fit = ((I>0)&(np.arange(len(q))>n_q*1./4))
            else:
                idx_fit = (I>0)
            q_fit = q[idx_fit]
            I_fit = I[idx_fit]
            Imean_fit = np.mean(I_fit)
            Istd_fit = np.std(I_fit)
            Is_fit = (I_fit-Imean_fit)/Istd_fit
            fit_obj = lambda x: compute_chi2(
            (np.log(saxs_fun(q_fit,x,params))-Imean_fit)/Istd_fit , Is_fit)
        p_opt['objective_before'] = fit_obj(x_init)
        res = scipimin(fit_obj,x_init,
            method='SLSQP',bounds=x_bounds,
            options={'ftol':1E-3},constraints=c)
        p_opt['objective_after'] = fit_obj(res.x)
        for k,xk in zip(params.keys(),res.x):
            p_opt[k] = xk
        p_opt['fixed_params'] = fixed_params
        p_opt['objective'] = objective 

    #print('precursors: {}, spheres: {}, peaks: {}'.format(f_pre,f_form,f_pks))
    #print('params: {}'.format(d_opt))

    #I_guess = compute_saxs(q,flags,params) 
    #I_opt = compute_saxs(q,flags,d_opt) 
    
    #from matplotlib import pyplot as plt
    #plt.figure(2)
    #plt.plot(q,I)
    #plt.plot(q,I_guess,'r')
    #plt.plot(q,I_opt,'g')
    #plt.figure(12)
    #plt.semilogy(q,I)
    #plt.semilogy(q,I_guess,'r')
    #plt.semilogy(q,I_opt,'g')
    #plt.show()

    return p_opt    
Exemplo n.º 6
0
def parameterize_spectrum(q_I,flags):
    """Determine scattering equation parameters for a given spectrum.

    Parameters
    ----------
    q_I : array
        n-by-2 array of q (scattering vector in 1/A) and I (intensity)
    flags : dict
        dictionary of population flags, like SaxsClassifier.classify() output

    Returns
    -------
    params : dict
        dict of scattering equation parameters
        corresponding to the flagged populations,
        similar to the input params for compute_saxs().
    """
    #print('parameterizing for: {}'.format(flags))
    d = OrderedDict()
    #if flags['bad_data']:
    #    return d 
    q = q_I[:,0]
    I = q_I[:,1]
    n_q = len(q)
    # Get a number for I(q=0)
    # TODO: add units to this package
    # Disregard lowest-q values if they are far from the mean, 
    # as these points are likely dominated by experimental error,
    # such as interference from beam stops, etc.
    idx_lowq = (q<0.06)
    Imean_lowq = np.mean(I[idx_lowq])
    Istd_lowq = np.std(I[idx_lowq])
    idx_good = ((I[idx_lowq] < Imean_lowq+Istd_lowq) & (I[idx_lowq] > Imean_lowq-Istd_lowq))
    I_at_0 = fit_I0(q[idx_lowq][idx_good],I[idx_lowq][idx_good],4)
    # safety check in case of low-q errors (e.g. beam stop interference, beam drift)...
    if not I_at_0 > 0 or I_at_0 < 0.5*Imean_lowq: 
        #print('TRYING TO GET BETTER I0')
        idx_fit = ((q>0.06)&(q<0.1))
        I_at_0 = fit_I0(q[idx_fit],I[idx_fit],2)
    #elif flags['precursor_scattering']:
    #    I_at_0 = fit_I0(q,I,3)
    d['I_at_0'] = I_at_0

    #TODO: add parameters for diffraction peaks
    #if flags['diffraction_peaks']:
    #    .... 

    if flags['form_factor_scattering']:
        # TODO: insert cases for non-spherical form factors
        r0_sphere, sigma_sphere = spherical_normal_heuristics(
            np.array(zip(q,I)),I_at_0)
        #I_nz = np.invert((I<=0))
        #I_error = lambda x: np.sum( 
        #    (x*spherical_normal_saxs(q,r0_sphere,sigma_sphere) - I)**2 )
        #res = scipimin(I_error,[1E-6],bounds=[(0,None)])
        #I0_sphere = res.x[0]
        #I_sphere = I0_sphere*spherical_normal_saxs(q,r0_sphere,sigma_sphere)
        d['r0_sphere'] = r0_sphere
        d['sigma_sphere'] = sigma_sphere

    if flags['precursor_scattering']: 
        rg_pre, G_pre = precursor_heuristics(
            np.array(zip(q,I)))
        #idx_highq = (q > q[int(n_q*3./4)]) 
        #I_error = lambda x: np.sum( 
        #    (x*guinier_porod(q,rg_pre,4,G_pre)[idx_highq] - I[idx_highq])**2 )
        #res = scipimin(I_error,[1E-6],bounds=[(0,None)])
        #I0_pre = res.x[0]
        #I_pre = I0_pre*guinier_porod(q,rg_pre,4,G_pre)
        I_pre = guinier_porod(q,rg_pre,4,G_pre)
        d['rg_precursor'] = rg_pre 
        d['G_precursor'] = G_pre 

    #from matplotlib import pyplot as plt
    #plt.figure(1)
    #plt.plot(q,I)
    #plt.plot(q,I_sphere,'g')
    #plt.show()

    #I_floor = np.ones(n_q)
    #I_pre = np.zeros(n_q)
    #I_form = np.zeros(n_q)
    #if flags['precursor_scattering']: 
    #    I_pre = guinier_porod(q,rg_pre,4,G_pre)
    #if flags['form_factor_scattering']:
    #    I_form = spherical_normal_saxs(q,r0_sphere,sigma_sphere)
    #if flags['diffraction_peaks']:
    #   I_peaks = .....

    # Run a quick least-squares to get initial guesses for intensity prefactors
    #if flags['precursor_scattering'] and flags['form_factor_scattering']:
    #    x_init = [0.0,I_at_0]
    #    x_bounds = [(0.0,None),(1E-9,None)]
    #    Ifunc = lambda x: x[0]*I_floor + I_pre + x[1]*I_form 
    #elif flags['precursor_scattering']:
    #    x_init = [0.0]
    #    x_bounds = [(0.0,None)]
    #    Ifunc = lambda x: x[0]*I_floor + I_pre 
    f_pre = flags['precursor_scattering']
    f_form = flags['form_factor_scattering']
    f_pks = flags['diffraction_peaks']
    n_q = len(q)
    I_nz = np.invert((I<=0))
    if f_pre and f_form: 
        Ifunc = lambda x: x[0]*np.ones(n_q) \
            + guinier_porod(q,rg_pre,4,x[1]) \
            + x[2]*spherical_normal_saxs(q,r0_sphere,sigma_sphere) 
        I_error = lambda x: np.sum( (np.log(Ifunc(x)[I_nz]) - np.log(I[I_nz]))**2 )
        res = scipimin(I_error,[0.0,G_pre,I_at_0],
            bounds=[(0.0,None),(1E-6,None),(1E-6,None)],
            constraints=[{'type':'eq','fun':lambda x:np.sum(x)-I_at_0}])
        d['I0_floor'] = res.x[0]
        d['G_precursor'] = res.x[1]
        d['I0_sphere'] = res.x[2]
    elif f_pre:
        d['I0_floor'] = 0
        #Ifunc = lambda x: x[0]*np.ones(n_q) \
        #    + guinier_porod(q,rg_pre,4,x[1]) 
        #I_error = lambda x: np.sum( (np.log(Ifunc(x)[I_nz]) - np.log(I[I_nz]))**2 )
        #res = scipimin(I_error,[0.0,G_pre],bounds=[(0.0,None),(0.0,None)])
        #d['I0_floor'] = res.x[0]
        #d['G_precursor'] = res.x[1]
    elif f_form:
        I0_floor = np.mean(I[int(n_q*0.9):])
        d['I0_floor'] = I0_floor 
        d['I0_sphere'] = I_at_0 - I0_floor
        #Ifunc = lambda x: x[0]*np.ones(n_q) \
        #    + x[1]*spherical_normal_saxs(q,r0_sphere,sigma_sphere) 
        #I_error = lambda x: np.sum( (np.log(Ifunc(x)[I_nz]) - np.log(I[I_nz]))**2 )
        #res = scipimin(I_error,[0.0,I_at_0],bounds=[(0.0,None),(0.0,None)])
        #d['I0_floor'] = res.x[0]
        #d['I0_sphere'] = res.x[1]

        #if flags['precursor_scattering'] and flags['form_factor_scattering']:
        #    #I0_pre = res.x[1] 
        #    I0_sphere = res.x[2]
        #    #d['I0_precursor'] = I0_pre 
        #    d['I0_sphere'] = I0_sphere 
        #elif flags['precursor_scattering']:
        #    I0_pre = res.x[1]
        #    d['I0_precursor'] = I0_pre 
        #elif flags['form_factor_scattering']:
        #    I0_form = res.x[1]
        #    d['I0_sphere'] = I0_form 
        #d['I0_floor'] = I0_floor 
    I_guess = compute_saxs(q,flags,d)
    q_I_guess = np.array([q,I_guess]).T
    nz = ((I>0)&(I_guess>0))
    logI_nz = np.log(I[nz])
    logIguess_nz = np.log(I_guess[nz])
    Imean = np.mean(logI_nz)
    Istd = np.std(logI_nz)
    logI_nz_s = (logI_nz - Imean) / Istd
    logIguess_nz_s = (logIguess_nz - Imean) / Istd
    #d['R2log_guess'] = compute_Rsquared(logI_nz,logIguess_nz)
    #d['chi2log_guess'] = compute_chi2(logI_nz_s,logIguess_nz_s)
    return d