Exemplo n.º 1
0
def toa_score(template,
              pcs,
              coeffs,
              profile,
              ts=None,
              tol=sqrt(np.finfo(np.float64).eps)):
    '''
    Calculate a maximum-likelihood TOA given a template and a PCA model of pulse shape variations.
    Uses the dot-product-based method of Osłowski (2011).

    `pcs`:    The principal components (unit vectors), as rows of an array.
    `coeffs`: Coefficients of principal component dot products to use in correcter.
    `ts`:     Evenly-spaced array of phase values corresponding to the profile.
              Sets the units of the TOA. If this is `None`, the TOA is reported in bins.
    `tol`:    Relative tolerance for optimization (in bins).
    '''
    n = len(profile)
    if ts is None:
        ts = np.arange(n)
    dt = float(ts[1] - ts[0])
    k = len(pcs)

    result = toa_fourier(template, profile, ts=ts, tol=tol)
    initial_toa = result.toa
    ampl = result.ampl

    template_shifted = fft_roll(template, initial_toa / dt)
    pcs_shifted = fft_roll(pcs, initial_toa / dt)
    scores = np.dot(pcs_shifted, profile)
    correcter = np.dot(coeffs, scores)
    toa = initial_toa - correcter

    return ToaScoreResult(toa=toa, ampl=ampl, scores=scores)
Exemplo n.º 2
0
def test_toa_recovery(func,
                      template,
                      n,
                      rms_toa,
                      SNR=np.inf,
                      ts=None,
                      tol=sqrt(eps)):
    '''
    Test function for `toa_ws()` and `toa_fourier()`.
    Attempts to recover `n` TOAs at a given SNR and returns the RMS error.
    
    `func`:     Function to test (`toa_ws` or `toa_fourier`).
    `template`: Template to use. Test profiles will be generated by
                shifting it.
    `n`:        Number of test profiles to generate.
    `rms_toa`:  RMS TOA for test profiles.
    `tol`:      Relative tolerance for optimization.
    '''
    if ts is None:
        ts = np.arange(len(template))
    dt = ts[1] - ts[0]
    dtoas = []
    toa_errs = []
    for i in range(n):
        true_toa = rms_toa * randn()
        profile = fft_roll(template, true_toa / dt)
        if np.isfinite(SNR):
            profile += randn(len(profile)) / SNR
        result = func(template, profile, ts=ts, tol=tol)
        toa_estimate = result.toa
        dtoas.append(toa_estimate - true_toa)
        toa_errs.append(result.error)
    dtoas = np.array(dtoas)
    toa_errs = np.array(toa_errs)
    return np.sqrt(np.mean(dtoas**2)), np.sqrt(np.mean(toa_errs**2))
Exemplo n.º 3
0
 def chisq_fn(shift):
     shifted_profile = fft_roll(profile, -shift)
     pc_ampls = pcs @ shifted_profile
     chisq = shifted_profile @ shifted_profile
     chisq -= (template @ shifted_profile)**2 / (template @ template)
     chisq -= np.sum(pc_ampls**2)
     chisq /= noise_level**2
     chisq += np.sum(pc_ampls**2 / (sgvals**2 + noise_level**2))
     return chisq
Exemplo n.º 4
0
def toa_fourier(template, profile, ts=None, noise_level=None, tol=sqrt(eps)):
    '''
    Calculate a TOA by maximizing the CCF of the template and the profile
    in the frequency domain. Searches within the interval between the sample
    below and the sample above the argmax of the circular CCF.
    
    `ts`:  Evenly-spaced array of phase values corresponding to the profile.
           Sets the units of the TOA. If this is `None`, the TOA is reported 
           in bins.
    `tol`: Relative tolerance for optimization (in bins).
    `noise_level`: Off-pulse noise, in the same units as the profile.
           Used in calculating error. If not supplied, noise level will be
           estimated as the standard deviation of the profile residual.
    '''
    n = len(profile)
    if ts is None:
        ts = np.arange(n)
    dt = float(ts[1] - ts[0])

    template_fft = fft(template)
    profile_fft = fft(profile)
    phase_per_bin = -2j * pi * fftfreq(n)

    circular_ccf = irfft(rfft(profile) * np.conj(rfft(template)), n)
    ccf_argmax = np.argmax(circular_ccf)
    if ccf_argmax > n / 2:
        ccf_argmax -= n
    ccf_max = ccf_argmax * dt

    def ccf_fourier(tau):
        phase = phase_per_bin * tau / dt
        ccf = np.inner(profile_fft, exp(-phase) * np.conj(template_fft)) / n
        return ccf.real

    brack = (ccf_max - dt, ccf_max, ccf_max + dt)
    toa = minimize_scalar(lambda tau: -ccf_fourier(tau),
                          method='Brent',
                          bracket=brack,
                          tol=tol * dt).x

    assert brack[0] < toa < brack[-1]

    template_shifted = fft_roll(template, toa / dt)
    b = np.dot(template_shifted, profile) / np.dot(template, template)
    residual = profile - b * template_shifted
    ampl = b * np.max(template_shifted)
    if noise_level is None:
        noise_level = offpulse_rms(profile, profile.size // 4)
    snr = ampl / noise_level

    w_eff = np.sqrt(n * dt / np.trapz(np.gradient(template, ts)**2, ts))
    error = w_eff / (snr * sqrt(n))

    return ToaResult(toa=toa, error=error, ampl=ampl)
Exemplo n.º 5
0
    def chisq_fn(shift):
        shifted_profile = fft_roll(profile, -shift)
        t_ampl = template @ shifted_profile / (template @ template)
        td_ampl = template_deriv @ shifted_profile / (
            template_deriv @ template_deriv)
        pc_ampls = pcs @ shifted_profile

        pred_td_ampl = coeffs @ pc_ampls
        chisq = (td_ampl - pred_td_ampl)**2 / (t_ampl * scatter)**2
        chisq += np.sum(pc_ampls**2 / (t_ampl * sgvals)**2)
        chisq += (t_ampl - template_ampl)**2 / template_scatter**2

        return chisq
Exemplo n.º 6
0
    def chisq_fn(shift):
        shifted_profile = fft_roll(profile, -shift)
        t_ampl = template @ shifted_profile / np.sqrt(template @ template)
        td_ampl = template_deriv @ shifted_profile / np.sqrt(
            template_deriv @ template_deriv)
        pc_ampls = pcs @ shifted_profile

        pred_td_ampl = coeffs @ pc_ampls * np.sqrt(
            template_deriv @ template_deriv)
        chisq = (td_ampl -
                 pred_td_ampl)**2 / (noise_level**2 + scatter**2 *
                                     (template_deriv @ template_deriv))
        chisq += np.sum(pc_ampls**2 / (sgvals**2 + noise_level**2))

        return chisq
Exemplo n.º 7
0
def gen_data(spec, n_profiles, npprof, n_bins, SNR, drift_bins):
    '''
    Generated simulated data based on a pulse specification.
    '''
    phase = np.linspace(-1 / 2, 1 / 2, n_bins, endpoint=False)
    profiles = gen_profiles(phase,
                            spec=spec,
                            n_profiles=n_profiles,
                            npprof=npprof,
                            SNR=SNR)

    shifts = drift_bins / n_profiles * np.arange(n_profiles)
    shifts -= np.mean(shifts)
    for i, profile in enumerate(profiles):
        profiles[i] = fft_roll(profile, shifts[i])

    return phase, profiles
Exemplo n.º 8
0
def toa_ws(template, profile, ts=None, noise_level=None, tol=sqrt(eps)):
    '''
    Calculate a TOA by maximizing the Whittaker-Shannon interpolant of the 
    CCF between `template` and `profile`. Searches within the interval
    between the sample below and the sample above the argmax of the CCF.
    
    `ts`:  Evenly-spaced array of phase values corresponding to the profile.
           Sets the units of the TOA. If this is `None`, the TOA is reported
           in bins.
    `tol`: Relative tolerance for optimization.
    `noise_level`: Off-pulse noise, in the same units as the profile.
           Used in calculating error. If not supplied, noise level will be
           estimated as the standard deviation of the profile residual.
    '''
    n = len(profile)
    if ts is None:
        ts = np.arange(n)
    dt = ts[1] - ts[0]
    lags = np.arange(-len(ts) + 1, len(ts)) * dt

    ccf = np.correlate(profile, template, mode='full')
    ccf_max = lags[np.argmax(ccf)]

    interpolant = interp_ws(ccf, lags)
    brack = (ccf_max - dt, ccf_max, ccf_max + dt)
    toa = minimize_scalar(lambda t: -interpolant(t),
                          method='Brent',
                          bracket=brack,
                          tol=tol).x

    assert brack[0] < toa < brack[-1]

    template_shifted = fft_roll(template, toa / dt)
    b = np.dot(template_shifted, profile) / np.dot(template, template)
    residual = profile - b * template_shifted
    ampl = b * np.max(template_shifted)
    if noise_level is None:
        noise_level = offpulse_rms(profile, profile.size // 4)
    snr = ampl / noise_level

    w_eff = np.sqrt(n * dt / np.trapz(np.gradient(template, ts)**2, ts))
    error = w_eff / (snr * sqrt(n))

    return ToaResult(toa=toa, error=error, ampl=ampl)
Exemplo n.º 9
0
def get_template(profiles, n_iter=1):
    '''
    Create a template by iteratively aligning and averaging profiles.
    Starts by averaging the middle 10% of profiles and iterates `n_iter` times,
    each time aligning the pulses using the previous template and averaging
    to create a new template.
    '''
    n = profiles.shape[0]
    n_5_percent = n // 20
    sl = slice(n // 2 - n_5_percent, n // 2 + n_5_percent)
    template = np.mean(profiles[sl], axis=0)
    toas = np.empty(profiles.shape[0])

    for i in range(n_iter):
        for i, profile in enumerate(profiles):
            result = toa_fourier(template, profile)
            toas[i] = result.toa
        profiles_aligned = np.empty_like(profiles)
        for j, profile in enumerate(profiles):
            profiles_aligned[j] = fft_roll(profile, -toas[j])

        template = np.mean(profiles_aligned, axis=0)

    return template
Exemplo n.º 10
0
def toa_pca_prior(template,
                  pcs,
                  weights,
                  profile,
                  ts=None,
                  tol=sqrt(np.finfo(np.float64).eps),
                  plot=False):
    '''
    Calculate a maximum-likelihood TOA given a template and a PCA model of pulse shape variations.
    
    `pcs`: The principal components (unit vectors), as rows of an array.
    `ts`:  Evenly-spaced array of phase values corresponding to the profile.
           Sets the units of the TOA. If this is `None`, the TOA is reported in bins.
    `tol`: Relative tolerance for optimization (in bins).
    '''
    n = len(profile)
    if ts is None:
        ts = np.arange(n)
    dt = float(ts[1] - ts[0])
    k = len(pcs)

    template_fft = fft(template)
    profile_fft = fft(profile)
    pcs_fft = fft(pcs)
    phase_per_bin = -2j * pi * fftfreq(n)

    circular_ccf = irfft(rfft(profile) * np.conj(rfft(template)), n) * dt
    sq_ccf = circular_ccf**2 / (np.sum(template**2) * dt)
    for i in range(k):
        pcfft = irfft(rfft(profile) * np.conj(rfft(pcs[i]))) * sqrt(dt)
        sq_ccf += pcfft**2 / (1 + weights[i])

    ccf_argmax = np.argmax(sq_ccf)
    ccf_max_val = sq_ccf[ccf_argmax]
    ccf_max = ccf_argmax * dt
    if ccf_argmax > n / 2:
        ccf_max -= n * dt

    def modified_squared_ccf(tau):
        phase = phase_per_bin * tau / dt
        ccf = np.inner(profile_fft,
                       exp(-phase) * np.conj(template_fft)) * dt / n
        sq_ccf = ccf.real**2 / (np.sum(template**2) * dt)

        for i in range(k):
            pc_fft = pcs_fft[i]
            weight = weights[i]
            pccf = np.inner(profile_fft,
                            exp(-phase) * np.conj(pc_fft)) * sqrt(dt) / n
            sq_ccf += pccf.real**2 / (1 + weight)

        return sq_ccf

    brack = (ccf_max - dt, ccf_max, ccf_max + dt)
    toa = minimize_scalar(lambda tau: -modified_squared_ccf(tau),
                          method='Brent',
                          bracket=brack,
                          tol=tol * dt).x

    assert brack[0] < toa < brack[-1]

    template_shifted = fft_roll(template, toa / dt)
    b = np.dot(template_shifted, profile) / np.dot(template, template)
    ampl = b * np.max(template_shifted)

    pcs_shifted = fft_roll(pcs, toa / dt)
    scores = np.dot(pcs_shifted, profile)

    return ToaPcaResult(toa=toa, ampl=ampl, scores=scores)
Exemplo n.º 11
0
def extract_pcs(profiles,
                n_pcs,
                initial_template=None,
                return_all=True,
                use_trend=True):
    '''
    Extract a template and principal components from a set of profiles.
    An initial template can be supplied; if not, the default strategy is to average
    the middle 10 percent of profiles to get an initial template.
    
    Inputs
    ------
    profiles:   The profiles, as rows of a 2-D array.
    n_pcs:      The number of principal components to use in the model.
    n_iter:     The number of iterations to perform.
    initial_template: The initial template (see above).
    return_all: Return all principal components (instead of the first `n_pcs`).
    use_trend:  If `False`, align profiles using their individual TOAs,
                ignoring n_iter. If `True`, align using a linear trend (default).
    
    Outputs
    -------
    template: The final template
    pcs:      The final principal components
    sgvals:   The singular values (characteristic amplitudes) corresponding to the
              principal components.
    scores:   The PC scores of the training data.
    dtoas:    The differences between the TOAs and the linear trend.
    '''
    n_profiles = profiles.shape[0]
    profile_number = np.arange(n_profiles)
    if initial_template is None:
        initial_template = get_template(profiles, n_iter=0)

    toas = np.zeros(n_profiles)
    for i, profile in enumerate(profiles):
        result = toa_fourier(initial_template, profile)
        toas[i] = result.toa

    resids = np.empty_like(profiles)
    if use_trend:
        trend_coeffs = np.polyfit(profile_number, toas, 1)
        trend = np.polyval(trend_coeffs, profile_number)

        profiles_aligned = np.empty_like(profiles)
        for j, profile in enumerate(profiles):
            profiles_aligned[j] = fft_roll(profile, -trend[j])

        template = np.mean(profiles_aligned, axis=0)
        for j, profile in enumerate(profiles_aligned):
            ampl = np.dot(profile, template) / np.dot(template, template)
            resids[j] = profile - ampl * template
        u, s, pcs = svd(resids, full_matrices=return_all)
        sgvals = s / np.sqrt(resids.shape[0])

        scores = np.dot(pcs, profiles_aligned.T)
        dtoas = toas - trend
    else:
        profiles_aligned = np.empty_like(profiles)
        for j, profile in enumerate(profiles):
            profiles_aligned[j] = fft_roll(profile, -toas[j])

        template = np.mean(profiles_aligned, axis=0)
        for j, profile in enumerate(profiles_aligned):
            ampl = np.dot(profile, template) / np.dot(template, template)
            resids[j] = profile - ampl * template
        u, s, pcs = svd(resids, full_matrices=return_all)
        sgvals = s / np.sqrt(resids.shape[0])

        # Trend used only for computing ΔTOAs
        trend_coeffs = np.polyfit(profile_number, toas, 1)
        trend = np.polyval(trend_coeffs, profile_number)
        scores = np.dot(pcs, profiles_aligned.T)
        dtoas = toas - trend

    return template, pcs, sgvals, scores, dtoas