예제 #1
0
    def __init__(self,
                 u_data=None,
                 up_data=None,
                 peak_finder=None,
                 use_hanning_filter=False):
        """
        Initialise the tune finder

        - u_data: position data, for use in Fourier Transform calculation
        - up_data: divergence data, only used for plotting (currently)
        - peak_finder: peak finder object, object used to find peaks in the tune
                diagram. If peak_finder is None, uses a WindowPeakFinder with a
                window size 10
        - use_hanning_filer: experimental hanning filter (probably doesnt work)
        """
        self._peak_finder = peak_finder
        if self._peak_finder == None:
            self._peak_finder = WindowPeakFinder(5, 0., 1)
        self.u = u_data
        self.up = up_data
        self.use_hanning_filter = use_hanning_filter
        self.fractional_tune = None
        self.peak_x_list = []
        self.peak_y_list = []
        self.k_mag_x = None
        self.k_mag_y = None
예제 #2
0
 def test_step(self):
     # peak finder with peaks at the end of the dataset
     pf = WindowPeakFinder(15, 0., 40)
     test_data = _test_data(20., 1000, 1e9)
     peaks = pf.find_peaks(test_data)
     peak_delta = [test-peaks[i] for i, test in enumerate(peaks[1:])]
     self.assertEqual(peak_delta, [40]*24)
예제 #3
0
 def test_find_end(self):
     # peak finder with peaks at the end of the dataset
     pf = WindowPeakFinder(10, 0., 1)
     test_data = _test_data(200., 440)[50:]
     peaks = pf.find_peaks(test_data)
     self.assertTrue(0 in peaks, msg = "Didn't get 0 in "+str(peaks))
     self.assertTrue(389 in peaks, msg = "Didn't get 389 in "+str(peaks))
예제 #4
0
 def test_find_peaks_large_window(self):
     # peak finder with window size > space between peaks
     # ignores subpeaks close to a main peak
     pf = WindowPeakFinder(100, 0., 1)
     test_data = _test_data(50., 500)
     self.assertEqual(pf.find_peaks(test_data),
                      [11, 61, 111, 161, 211, 261, 311, 361, 411])
예제 #5
0
 def test_threshold(self):
     # peak finder with peaks at the end of the dataset
     test_data = [random.gauss(10., 1.) for i in range(10000)]+\
                 [random.gauss(10., 20.) for i in range(10000)]
     test_data[50] = 25.
     # test_data[50] > 10 sigma from mean
     # test_data[i] < 10 sigma from mean (we expect)
     pf = WindowPeakFinder(500, 10.0, 1)
     peaks = sorted(pf.find_peaks(test_data))
     self.assertTrue(50 in peaks)
     self.assertTrue(peaks[1] > 100)
예제 #6
0
 def test_get_tune_peak_finder(self):
     # check test initialises properly
     co, tracking = matrix(math.pi / 4., 1000)
     fft = FFTTuneFinder(peak_finder=WindowPeakFinder(5, 0., 1.))
     fft.run_tracking('x', 1.0, co, tracking)
     tune = fft.get_tune()
     self.assertTrue(abs(tune * 2. * math.pi - math.pi / 4.) < 1e-2)
def find_peaks(data, window_size):
    print "  Peaks...",
    sys.stdout.flush()
    print 'smoothing...',
    sys.stdout.flush()
    smoothed_data = GaussianSmoothing(window_size/2., window_size, True).smooth(data)
    print 'finding peaks...',
    sys.stdout.flush()
    peak_finder = WindowPeakFinder(window_size, 0., window_size/2)
    peak_list = peak_finder.find_peaks(smoothed_data)
    print 'getting errors for', len(peak_list), 'peaks...',
    sys.stdout.flush()
    error_summary = []
    peak_refiner = RefinePeakFinder(peak_list, 50, 3000, False)
    peak_error_list = peak_refiner.find_peak_errors(data)
    print "found", len(peak_error_list), "errors... Done"
    return peak_error_list
예제 #8
0
 def test_find_peaks_large_window(self):
     # peak finder with window size > signal length
     # ignores subpeaks close to a main peak
     pf = WindowPeakFinder(500, 0., 1)
     test_data = _test_data(50., 500)
     self.assertEqual(pf.find_peaks(test_data),
                      [11])
     pf = WindowPeakFinder(1000, 0., 1)
     test_data = _test_data(50., 500)
     self.assertEqual(pf.find_peaks(test_data),
                      [11])
예제 #9
0
 def test_find_peaks_small_window(self):
     # peak finder with window size < space between peaks
     pf = WindowPeakFinder(100, 0., 1)
     test_data = _test_data(200., 500)
     self.assertEqual(pf.find_peaks(test_data), [45, 245, 445])
예제 #10
0
 def test_init(self):
     # initialises properly
     pf = WindowPeakFinder(10, 0., 1)
     self.assertEqual(pf.window_size, 10)
예제 #11
0
class FFTTuneFinder(object):
    """
    Find the tune using the fast fourier transform technique

    Apply a small displacement to the reference trajectory and track through a
    lattice; use the displaced trajectory 
    """
    def __init__(self,
                 u_data=None,
                 up_data=None,
                 peak_finder=None,
                 use_hanning_filter=False):
        """
        Initialise the tune finder

        - u_data: position data, for use in Fourier Transform calculation
        - up_data: divergence data, only used for plotting (currently)
        - peak_finder: peak finder object, object used to find peaks in the tune
                diagram. If peak_finder is None, uses a WindowPeakFinder with a
                window size 10
        - use_hanning_filer: experimental hanning filter (probably doesnt work)
        """
        self._peak_finder = peak_finder
        if self._peak_finder == None:
            self._peak_finder = WindowPeakFinder(5, 0., 1)
        self.u = u_data
        self.up = up_data
        self.use_hanning_filter = use_hanning_filter
        self.fractional_tune = None
        self.peak_x_list = []
        self.peak_y_list = []
        self.k_mag_x = None
        self.k_mag_y = None

    def get_tune(self, tune_tolerance=None):
        """
        Find the fractional tune for a given tracking object

        - tune_tolerance: tolerance with which the tune finder will attempt to
            calculate the tune. If set to None, FTTuneFinder will use
            1/len(u)/100

        Displaces the reference hit by an amount delta, tracks this hit using
        the tracking object and runs a one dimensional FFT against the axis
        variable. Number of samples used in the FFT is determined by the 
        tracking object (and hence accuracy of the tune calculation).

        Note that u_data must be of odd length for this algorithm; FFTTuneFinder
        will discard the last element if this is not the case.

        Returns the principle Fourier frequency that is not 0.
        """
        if self.u == None:
            raise ValueError("No data set for Fourier Transform")
        if len(self.u) % 2 == 0:
            if self.up != None:
                del self.up[-1]
            del self.u[-1]
        if tune_tolerance == None:
            tune_tolerance = 1. / len(self.u) / 100.
        self._fft_find_peaks()
        self._get_max_peak()
        if len(self.peak_x_list) == 0:
            self._sft_find_peaks(tune_tolerance)
        self._get_max_peak()
        if tune_tolerance < 1. / len(self.k_mag_x):
            for i, k_x in enumerate(self.peak_x_list):
                try:
                    new_peak_x = self._recursive_refine_peak(
                        k_x, tune_tolerance)
                    self.peak_x_list[i] = new_peak_x
                    index = self.k_mag_x.index(new_peak_x)
                    self.peak_y_list[i] = self.k_mag_y[index]
                except ValueError:
                    sys.excepthook(*sys.exc_info())
            self._get_max_peak()
        return self.fractional_tune

    def run_tracking(self,
                     axis,
                     delta,
                     reference_hit,
                     tracking,
                     use_hits=None):
        """
        Set position data from tracking output

        - reference_hit: Hit on the closed orbit (for a ring).
        - axis: string, variable from Hit.get_variables() over which the tune
                will be found.
        - delta: float, displacement from the reference_hit.
        - tracking: xboa.tracking.TrackingBase, tracking object to propagate
                particles through the lattice.
        - use_hits: list of integers, only consider hits returned by tracking 
                with an index in use_hits. If set to None, consider all hits.
        """
        hit = reference_hit.deepcopy()
        hit[axis] += delta
        hits_out = tracking.track_one(hit)
        if use_hits != None:
            hits_out = [hit for i, hit in enumerate(hits_out) if i in use_hits]
        self.u = [hit[axis] for hit in hits_out]
        self.up = [hit["p" + axis] for hit in hits_out]

    def plot_signal(self, title):
        """
        Plot the FFT frequency spectrum
        - title, string used as a title in FFT plots
        Returns tuple of TCanvas, TH2, TGraph
        """
        canvas = common.make_root_canvas(title)
        x_list = range(len(self.u))
        hist, graph = common.make_root_graph(title, x_list, 'count', self.u,
                                             'signal')
        hist.SetTitle(title)
        hist.Draw()
        graph.Draw('l')
        canvas.Update()
        return canvas, hist, graph

    def plot_phase_space(self, title):
        """
        Plot the phase space for the fourier transform
        - title, string used as a title in FFT plots
        Returns tuple of TCanvas, TH2, TGraph
        """
        # NEEDS TEST
        canvas = common.make_root_canvas(title)
        print(len(self.u), len(self.up))
        hist, graph = common.make_root_graph(title, self.u, 'displacement',
                                             self.up, 'divergence')
        hist.SetTitle(title)
        hist.Draw()
        graph.SetMarkerStyle(26)
        graph.Draw('p')
        canvas.Update()
        return canvas, hist, graph

    def plot_fft(self, title, ymin=None, ymax=None):
        """
        Plot the FFT frequency spectrum
        - title, string used as a title in FFT plots
        Returns tuple of TCanvas, TH2, TGraph
        """
        if self.k_mag_x == None or self.k_mag_y == None:
            self.get_tune()
        canvas = common.make_root_canvas(title)
        if ymin == None:
            ymin = max([1e-5, min(self.k_mag_y)])
        hist, graph = common.make_root_graph(title,
                                             self.k_mag_x,
                                             'frequency',
                                             self.k_mag_y,
                                             'magnitude',
                                             ymin=ymin,
                                             ymax=ymax)
        hist.SetTitle(title)
        hist.Draw()
        graph.Draw('l')
        canvas.SetLogy()
        canvas.Update()
        return canvas, hist, graph

    def plot_fft_fit(self,
                     title,
                     peak_k_index,
                     fit_lower_index,
                     fit_upper_index,
                     fit=None):
        """
        Draw the FFT spectrum and fit within a window
        - title, string title for the plot
        - peak_k_index, integer corresponding to element from self.k_mag which
          makes the seed for the position of the hit
        - fit_lower_bound, integer corresponding to the element from self.k_mag
          which makes the lower edge of the fit window
        - fit_lower_bound, integer corresponding to the element from self.k_mag
          which makes the upper edge of the fit window
        - fit, ROOT.TF1 used for fitting. If set to None, a Gaussian will be
          used with reasonable parameters. Note that user has responsibility to
          set the fit window in fit

        Uses the ROOT library to do the fit; this means drawing the data onto a
        ROOT Canvas using self.plot_fft. Fits with a Gaussian; in the presence
        of noisy/low statistics data, this can improve the estimate of tune. 

        Returns a tuple of TCanvas, TH2, TGraph, TF1. Hint:- to get the 
        fractional tune, use TF1::GetParameter(0); to get the estimated error,
        use TF1::GetParError(0).
        """
        canvas, hist, graph = self.plot_fft(title)
        fit_height = self.k_mag_y[peak_k_index]
        fit_centre = self.k_mag_x[peak_k_index]
        fit_low = self.k_mag_x[fit_lower_index]
        fit_hi = self.k_mag_x[fit_upper_index]
        if fit == None:
            fit_function = "[1]*exp(-((x-[0])*[2])**2)"
            fit = ROOT.TF1(title + " fit", fit_function, fit_low, fit_hi)
            fit.SetParameter(0, fit_centre)
            fit.SetParameter(1, fit_height)
            fit.SetParameter(2, fit_height)
        graph.Fit(fit)
        canvas.cd()
        fit.Draw("SAME")
        print("Got peak at", fit.GetParameter(0), "with error",
              fit.GetParError(0))
        canvas.Update()
        return canvas, hist, graph, fit

    def _get_max_peak(self):
        """
        Find the maximum peak in self.peak_index_list
        """
        peak_index_list = [self.k_mag_x.index(k_x) for k_x in self.peak_x_list]
        peak_values = [self.k_mag_y[i] for i in peak_index_list]
        if len(peak_values) == 0:
            raise ValueError("Can't get max peak - found no peaks at all")
        peak_max = max(peak_values)
        peak_index = self.k_mag_y.index(peak_max)
        self.fractional_tune = self.k_mag_x[peak_index]
        return peak_index

    def _sft_find_peaks(self, interval):
        """
        Perform a slow Fourier Transform and find any peaks
        """
        n_items = int(1. / interval / 2.)
        self.k_mag_x = [i * interval for i in range(n_items)]
        self.k_mag_y = [self._sft(k_x) for k_x in self.k_mag_x]
        self._find_peaks()

    def _fft_find_peaks(self):
        """
        Perform the Fast Fourier Transform and find any peaks
        """
        fft = numpy.fft.rfft(numpy.array(self.u))
        k_list = [[float(numpy.real(z)), float(numpy.imag(z))] for z in fft]
        self.k_mag_y = [(z[0]**2 + z[1]**2)**0.5 for z in k_list]
        self.k_mag_x = [
            i / 2. / float(len(k_list)) for i, z in enumerate(k_list)
        ]
        self._find_peaks()

    def _find_peaks(self):
        """
        Run the peak finder
        """
        peak_index_list = sorted(self._peak_finder.find_peaks(self.k_mag_y))
        if len(peak_index_list) == 0:
            return
        if peak_index_list[0] == 0:
            peak_index_list.pop(0)
        self.peak_x_list = [self.k_mag_x[i] for i in peak_index_list]
        self.peak_y_list = [self.k_mag_y[i] for i in peak_index_list]

    def _recursive_refine_peak(self, k_x, x_tolerance):
        """
        Use slow fourier transforms in the region of a peak to recursively
        improve the peak estimate to some tolerance.

        On each iteration, the new k_mag values are appended to k_mag_x/y.

        Stop recursing if the new value is < the neighbouring value; we assume
        this is numerical precision noise and the recursion has converged
        (implicit that the seed peak was closer than any troughs).

        Returns the new peak_index
        """
        peak_index = self.k_mag_x.index(k_x)
        if peak_index + 1 == len(self.k_mag_y):
            peak_index -= 1
        y_values = [
            self.k_mag_y[peak_index - 1], self.k_mag_y[peak_index],
            self.k_mag_y[peak_index + 1]
        ]
        if y_values[2] > y_values[0]:
            del y_values[0]
            x_values = [self.k_mag_x[peak_index], self.k_mag_x[peak_index + 1]]
            index_right = peak_index + 1
        else:
            del y_values[2]
            x_values = [self.k_mag_x[peak_index - 1], self.k_mag_x[peak_index]]
            index_right = peak_index
        new_x = (x_values[0] + x_values[1]) / 2.
        new_y = self._sft(new_x)
        if abs(x_values[1] - x_values[0]) < x_tolerance:
            return self.k_mag_x[peak_index]
        self.k_mag_x.insert(index_right, new_x)
        self.k_mag_y.insert(index_right, new_y)
        # new index for the peak value;
        # if new_y is highest, peak index is the new_y index
        if new_y > y_values[0] and new_y > y_values[1]:
            new_peak_index = index_right
        # if new_y is lowest, the peak has split and we split the recursion
        # (this is a bit dodgy)
        elif y_values[0] > new_y and y_values[1] > new_y:
            new_peak_index = self.k_mag_y.index(y_values[0])
            self._recursive_refine_peak(self.k_mag_x[new_peak_index],
                                        x_tolerance)
            new_peak_index = self.k_mag_y.index(y_values[1])
        # if y_values[0] is highest, peak index is unchanged
        elif y_values[0] > y_values[1]:
            new_peak_index = peak_index
        # else index of y_values[1], which is now 1 higher thanks to insert
        else:
            new_peak_index = peak_index + 1
        return self._recursive_refine_peak(self.k_mag_x[new_peak_index],
                                           x_tolerance)

    def _sft(self, k_x):
        """
        Calculate "Slow" Fourier Transform at k_x

        - k_x, float, position at which the sft is found
        - hanning filter, bool, set to True to apply a hanning filter

        "Slow" Fourier transform means using Sum(A_i cos(...) + A_i sin(...)) to
        get the FT at a given k value
        """
        y_point, z_point = 0., 0.
        n = float(len(self.u))
        hanning = 1.
        for m, a_m in enumerate(self.u):
            if self.use_hanning_filter:
                hanning = 2. * math.sin(math.pi * m / n)**2.
            f = 2. * math.pi * m * k_x * (n + 1) / n
            dy = hanning * a_m * math.cos(f)
            dz = hanning * a_m * math.sin(f)
            y_point += dy
            z_point += dz
        k_y = (y_point**2 + z_point**2)**0.5
        return k_y