diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ecc8ae --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2017 The GNOME Collaboration + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f9ed050 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# GNOME Data Analysis Software + +[![License MIT](https://img.shields.io/badge/License-MIT-blue.svg)](http://opensource.org/licenses/MIT) +[![PyPI version](https://badge.fury.io/py/gdas.svg)](https://badge.fury.io/py/gdas) + +## Installation & Documentation + +The program is available through [pip](https://pypi.python.org/pypi/pip). +A full description of the software is provided at the following link: + + https://gnome-physics.github.io/gdas + +## License + +(The MIT License) + +Copyright (c) 2016-2017 The GNOME Collaboration + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.org b/README.org deleted file mode 100644 index 64ab89f..0000000 --- a/README.org +++ /dev/null @@ -1,9 +0,0 @@ -#+TITLE: README -#+AUTHOR: Vincent Dumont -#+EMAIL: vincentdumont11@gmail.com - -A full documentation of the software is provided at the following link: - - https://gnome-physics.github.io/gdas - -Feel free to [[mailto:vincentdumont11@gmail.com][email me]] if you have any questions. diff --git a/docs/.buildinfo b/docs/.buildinfo deleted file mode 100644 index c17c206..0000000 --- a/docs/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: a5704f500776dac1385d77f6ad5ed451 -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/docs/_images/jupyter1.png b/docs/_images/jupyter1.png deleted file mode 100644 index 5cb4809..0000000 Binary files a/docs/_images/jupyter1.png and /dev/null differ diff --git a/docs/_images/jupyter2.png b/docs/_images/jupyter2.png deleted file mode 100644 index 676157c..0000000 Binary files a/docs/_images/jupyter2.png and /dev/null differ diff --git a/docs/_images/overview.png b/docs/_images/overview.png deleted file mode 100644 index 07ae685..0000000 Binary files a/docs/_images/overview.png and /dev/null differ diff --git a/docs/_modules/gdas/epower.html b/docs/_modules/gdas/epower.html deleted file mode 100644 index 814d977..0000000 --- a/docs/_modules/gdas/epower.html +++ /dev/null @@ -1,701 +0,0 @@ - - - - - - - - gdas.epower — gdas 0.3.0 documentation - - - - - - - - - - - - - - - -
- - -

Source code for gdas.epower

-import time,math,os,scipy,lal,lalburst
-from plots                            import *
-from pycbc                            import psd,types,filter
-from glue.ligolw                      import lsctables
-from gwpy.spectrum                    import Spectrum
-from gwpy.timeseries                  import TimeSeries
-from scipy.signal                     import fftconvolve
-from utils                            import *
-from glue                             import git_version
-from glue.lal                         import LIGOTimeGPS
-from glue.ligolw                      import ligolw,utils
-from glue.ligolw.utils.search_summary import append_search_summary
-from glue.ligolw.utils.process        import register_to_xmldoc
-from glue.segments                    import segment
-
-
[docs]def excess_power(ts_data,psd_segment_length,psd_segment_stride,psd_estimation,window_fraction,tile_fap,station,nchans=None,band=None,fmin=0,fmax=None,max_duration=None): - """ - Perform excess-power search analysis on magnetic field data. - This method will produce a bunch of time-frequency plots for every - tile duration and bandwidth analysed as well as a XML file identifying - all the triggers found in the selected data within the user-defined - time range. - - Parameters - ---------- - ts_data : TimeSeries - Time Series from magnetic field data - psd_segment_length : float - Length of each segment in seconds - psd_segment_stride : float - Separation between 2 consecutive segments in seconds - psd_estimation : string - Average method - window_fraction : float - Withening window fraction - tile_fap : float - Tile false alarm probability threshold in Gaussian noise. - nchans : int - Total number of channels - band : float - Tile bandwidth - fmin : float - Lowest frequency of the filter bank. - fmax : float - Highest frequency of the filter bank - """ - #print strain.insert_strain_option_group.__dict__ - #print psd.insert_psd_option_group.__dict__ - sample_rate = ts_data.sample_rate - nchans,band,flow = check_filtering_settings(sample_rate,nchans,band,fmin,fmax) - seg_len,fd_psd,lal_psd = calculate_psd(ts_data,sample_rate,psd_segment_length,psd_segment_stride,psd_estimation) - window, spec_corr = calculate_spectral_correlation(seg_len,'tukey',window_fraction=window_fraction) - filter_bank, fdb = create_filter_bank(fd_psd.delta_f,flow+band/2,band,nchans,fd_psd,spec_corr,fmin,fmax) - # This is necessary to compute the mu^2 normalizations - #white_filter_ip = compute_filter_ips_self(filter_bank, spec_corr, None) - #unwhite_filter_ip = compute_filter_ips_self(filter_bank, spec_corr, lal_psd) - # These two are needed for the unwhitened mean square sum (hrss) - #white_ss_ip = compute_filter_ips_adjacent(filter_bank, spec_corr, None) - #unwhite_ss_ip = compute_filter_ips_adjacent(filter_bank, spec_corr, lal_psd) - tdb = convert_to_time_domain(fdb,sample_rate) - plot_bank(fdb) - plot_filters(tdb,flow,band) - mu_sq_dict = compute_channel_renormalization(filter_bank, spec_corr, nchans) - event_list = lsctables.New(lsctables.SnglBurstTable, - ['start_time','start_time_ns','peak_time','peak_time_ns', - 'duration','bandwidth','central_freq','chisq_dof', - 'confidence','snr','amplitude','channel','ifo', - 'process_id','event_id','search','stop_time','stop_time_ns']) - t_idx_min, t_idx_max = 0, seg_len - os.system('mkdir -p segments/time-frequency') - os.system('mkdir -p segments/time-series') - while t_idx_max <= len(ts_data): - start_time, end_time, tmp_ts_data, fs_data = identify_block(ts_data,fd_psd,window,t_idx_min,t_idx_max) - tf_map = create_tf_plane(fd_psd,nchans,seg_len,filter_bank,band,fs_data) - plot_spectrogram(numpy.abs(tf_map).T,tmp_ts_data.delta_t,band,ts_data.sample_rate,start_time,end_time,fname='segments/time-frequency/%i-%i.png'%(start_time,end_time)) - for nc_sum in range(0, int(math.log(nchans, 2)))[::-1]: # nc_sum additional channel adds - nc_sum = 2**nc_sum - 1 - mu_sq = mu_sq_dict[nc_sum] - max_dof, tiles, us_rate, dt, df = construct_tiles(nc_sum,mu_sq,band,ts_data,tf_map,psd_segment_length,window_fraction,max_duration) - t3 = time.time() - for j in [2**l for l in xrange(0, int(math.log(max_dof, 2)))]: - duration = j / 2.0 / df - dof_tiles = create_tile_duration(j,df,duration,tiles) - plot_spectrogram(dof_tiles.T,dt,df,ts_data.sample_rate,start_time,end_time,fname='segments/%i-%i/tf_%02ichans_%02idof.png'%(start_time,end_time,nc_sum+1,2*j)) - threshold = scipy.stats.chi2.isf(tile_fap, j) - print "|------ Threshold for this level: %f" % threshold - spant, spanf = dof_tiles.shape[1] * dt, dof_tiles.shape[0] * df - print "|------ Processing %.2fx%.2f time-frequency map." % (spant, spanf) - # Since we clip the data, the start time needs to be adjusted accordingly - window_offset_epoch = fs_data.epoch + psd_segment_length * window_fraction / 2 - trigger_list_from_map(dof_tiles, event_list, threshold, window_offset_epoch, filter_bank[0].f0 + band/2, duration, df, df, dt, None) - for event in event_list[::-1]: - if event.amplitude != None: - continue - etime_min_idx = float(event.get_start()) - float(fs_data.epoch) - etime_min_idx = int(etime_min_idx / tmp_ts_data.delta_t) - etime_max_idx = float(event.get_start()) - float(fs_data.epoch) + event.duration - etime_max_idx = int(etime_max_idx / tmp_ts_data.delta_t) - # (band / 2) to account for sin^2 wings from finest filters - flow_idx = int((event.central_freq - event.bandwidth / 2 - (band / 2) - flow) / band) - fhigh_idx = int((event.central_freq + event.bandwidth / 2 + (band / 2) - flow) / band) - # TODO: Check that the undersampling rate is always commensurate - # with the indexing: that is to say that - # mod(etime_min_idx, us_rate) == 0 always - z_j_b = tf_map[flow_idx:fhigh_idx,etime_min_idx:etime_max_idx:us_rate] - event.amplitude = 0 - print "|------ Total number of events: %d" % len(event_list) - t_idx_min += int(seg_len * (1 - window_fraction)) - t_idx_max += int(seg_len * (1 - window_fraction)) - create_xml(ts_data,psd_segment_length,window_fraction,event_list,station)
- -
[docs]def check_filtering_settings(sample_rate,channels,tile_bandwidth,fmin,fmax): - """ - Check filtering settings and define the total number of channels - and bandwidth to use for filter bank. - - Parameters - ---------- - sample_rate : float - Sampling rate in Hz of the data retrieved from the metadata - min_frequency : float - Lowest frequency of the filter bank - max_frequency : float - Highest frequency of the filter bank - channels : int - Number of frequency channels to use - tile_bandwidth : float - Bandwidth of the finest filters - - Return - ------ - nchans, band, flow : int, float, float - Number of channels, filter bandwidth, initial frequency offset - """ - # Check if tile maximum frequency is not defined - if fmax is None or fmax>sample_rate/2.: - # Set the tile maximum frequency equal to the Nyquist frequency (i.e. half the sampling rate) - fmax = sample_rate / 2.0 - # Check whether or not tile bandwidth and channel are defined - if tile_bandwidth is None and channels is None: - # Exit program with error message - exit("Either --tile-bandwidth or --channels must be specified to set up time-frequency plane") - else: - # Define as assert statement that tile maximum frequency larger than its minimum frequency - assert fmax >= fmin - # Define spectral band of data - data_band = fmax - fmin - # Check if tile bandwidth or channel is defined - if tile_bandwidth is not None: - # Define number of possible filter bands - band = tile_bandwidth - nchans = channels = int(data_band / tile_bandwidth) - 1 - elif channels is not None: - # Define filter bandwidth - band = tile_bandwidth = data_band / channels - nchans = channels - 1 - assert channels > 1 - # Lowest frequency of the filter bank - flow = fmin - return nchans,band,flow
- -
[docs]def calculate_psd(ts_data,sample_rate,psd_segment_length,psd_segment_stride,psd_estimation): - """ - Estimate Power Spectral Density (PSD) - - Parameters - ---------- - ts_data : TimeSeries - Time series of magnetic field data - sample_rate : float - Sampling rate of data - psd_segment_length : float - Length of data segment in seconds - psd_segment_stride : float - Separation between consecutive segments in seconds - psd_estimation : string - Average method to measure PSD from the data - - Return - ------ - seg_len, fd_psd, lal_psd : Segment length in sample unit, PSD results - in 2 different formats. - - Notes - ----- - Need to contact Chris Pankow for more information on the 2 formats. - """ - print "|- Estimating PSD from segments of time %.2f s in length, with %.2f s stride..." % (psd_segment_length, psd_segment_stride) - # Convert time series as array of float - data = ts_data.astype(numpy.float64) - # Average method to measure PSD from the data - avg_method = psd_estimation - # The segment length for PSD estimation in samples - seg_len = int(psd_segment_length * sample_rate) - # The separation between consecutive segments in samples - seg_stride = int(psd_segment_stride * sample_rate) - # Lifted from the psd.from_cli module - fd_psd = psd.welch(data,avg_method=avg_method,seg_len=seg_len,seg_stride=seg_stride) - # Plot the power spectral density - plot_spectrum(fd_psd) - # We need this for the SWIG functions - lal_psd = fd_psd.lal() - return seg_len,fd_psd,lal_psd
- -
[docs]def calculate_spectral_correlation(fft_window_len,wtype='hann',window_fraction=None): - """ - Calculate the two point spectral correlation introduced by windowing - the data before transforming to the frequency domain -- valid choices - are 'hann' and 'tukey'. The window_fraction parameter only has meaning - for wtype='tukey'. - """ - print "|- Whitening window and spectral correlation..." - if wtype == 'hann': - window = lal.CreateHannREAL8Window(fft_window_len) - elif wtype == 'tukey': - window = lal.CreateTukeyREAL8Window(fft_window_len, window_fraction) - else: - raise ValueError("Can't handle window type %s" % wtype) - fft_plan = lal.CreateForwardREAL8FFTPlan(len(window.data.data), 1) - window = window.data.data - window_sigma_sq = numpy.mean(window**2) - # Pre scale the window by its root mean squared -- see eqn 11 of EP document - #window /= numpy.sqrt(window_sigma_sq) - return window, lal.REAL8WindowTwoPointSpectralCorrelation(window, fft_plan)
- -
[docs]def create_filter_bank(delta_f,flow,band,nchan,psd,spec_corr,fmin=0,fmax=None): - """ - Create filter bank - - Parameters - ---------- - delta_f : float - Bandwidth of each filter - flow : float - Lowest frequency of the filter bank - band : - """ - print "|- Create filter..." - lal_psd = psd.lal() - lal_filters, np_filters = [], [] - for i in range(nchan): - lal_filter = lalburst.CreateExcessPowerFilter(flow + i*band, band, lal_psd, spec_corr) - np_filters.append(Spectrum.from_lal(lal_filter)) - lal_filters.append(lal_filter) - return lal_filters, np_filters
- -def convert_to_time_domain(fdb,sample_rate): - """ - Convert filter bank from frequency to time domain - - Parameters - ---------- - fdb : list - List of filters from the filter bank in frequency domain - sample_rate : float - Sampling rate of magnetic field data - - Return - ------ - tdb : list - List of filters from the filter bank in time domain - """ - print "|- Convert all the frequency domain to the time domain..." - tdb = [] - for fdt in fdb: - zero_padded = numpy.zeros(int((fdt.f0 / fdt.df).value) + len(fdt)) - st = int((fdt.f0 / fdt.df).value) - zero_padded[st:st+len(fdt)] = numpy.real_if_close(fdt.value) - n_freq = int(sample_rate / 2 / fdt.df.value) * 2 - tdt = numpy.fft.irfft(zero_padded, n_freq) * math.sqrt(sample_rate) - tdt = numpy.roll(tdt, len(tdt)/2) - tdt = TimeSeries(tdt, name="", epoch=fdt.epoch, sample_rate=sample_rate) - tdb.append(tdt) - return tdb - -def identify_block(ts_data,fd_psd,window,t_idx_min,t_idx_max): - """ - Get frequency series of the current block - - Parameters - ---------- - ts_data : TimeSeries - Time series of magnetic field data - fd_psd : - Power Spectrum Density - window : - t_idx_min : float - Index in time series of first data point - t_idx_max : float - Index in time series of last data point - - Return - ------ - start_time : float - Starting time of the block - end_time : float - Ending time of the block - tmp_ts_data : TimeSeries - Time series magnetic data of the block - fs_data : FrequencySeries - Frequency series magnetic data of the block - """ - # Define starting and ending time of the segment in seconds - start_time = ts_data.start_time + t_idx_min/float(ts_data.sample_rate) - end_time = ts_data.start_time + t_idx_max/float(ts_data.sample_rate) - print "|-- Analyzing block %i to %i (%.2f percent)"%(start_time,end_time,100*float(t_idx_max)/len(ts_data)) - # Model a withen time series for the block - tmp_ts_data = types.TimeSeries(ts_data[t_idx_min:t_idx_max]*window,delta_t=1./ts_data.sample_rate,epoch=start_time) - # Save time series in relevant repository - segfolder = 'segments/%i-%i'%(start_time,end_time) - os.system('mkdir -p '+segfolder) - plot_ts(tmp_ts_data,fname='segments/time-series/%i-%i.png'%(start_time,end_time)) - # Convert times series to frequency series - fs_data = tmp_ts_data.to_frequencyseries() - print "|-- Frequency series data has variance: %s" % fs_data.data.std()**2 - # Whitening (FIXME: Whiten the filters, not the data) - fs_data.data /= numpy.sqrt(fd_psd) / numpy.sqrt(2 * fd_psd.delta_f) - print "|-- Whitened frequency series data has variance: %s" % fs_data.data.std()**2 - return start_time, end_time, tmp_ts_data, fs_data - -def create_tf_plane(fd_psd,nchans,seg_len,filter_bank,band,fs_data): - """ - Create time-frequency map - - Parameters - ---------- - fd_psd : array - Power Spectrum Density - """ - print "|-- Create time-frequency plane for current block" - # Return the complex snr, along with its associated normalization of the template, - # matched filtered against the data - #filter.matched_filter_core(types.FrequencySeries(tmp_filter_bank,delta_f=fd_psd.delta_f),fs_data,h_norm=1,psd=fd_psd,low_frequency_cutoff=filter_bank[0].f0,high_frequency_cutoff=filter_bank[0].f0+2*band) - print "|-- Filtering all %d channels..." % nchans - # Initialise 2D zero array - tmp_filter_bank = numpy.zeros(len(fd_psd), dtype=numpy.complex128) - # Initialise 2D zero array for time-frequency map - tf_map = numpy.zeros((nchans, seg_len), dtype=numpy.complex128) - # Loop over all the channels - for i in range(nchans): - # Reset filter bank series - tmp_filter_bank *= 0.0 - # Index of starting frequency - f1 = int(filter_bank[i].f0/fd_psd.delta_f) - # Index of ending frequency - f2 = int((filter_bank[i].f0 + 2*band)/fd_psd.delta_f)+1 - # (FIXME: Why is there a factor of 2 here?) - tmp_filter_bank[f1:f2] = filter_bank[i].data.data * 2 - # Define the template to filter the frequency series with - template = types.FrequencySeries(tmp_filter_bank, delta_f=fd_psd.delta_f, copy=False) - # Create filtered series - filtered_series = filter.matched_filter_core(template,fs_data,h_norm=None,psd=None,low_frequency_cutoff=filter_bank[i].f0,high_frequency_cutoff=filter_bank[i].f0+2*band) - # Include filtered series in the map - tf_map[i,:] = filtered_series[0].numpy() - return tf_map - -def compute_filter_ips_self(lal_filters, spec_corr, psd=None): - """ - Compute a set of inner products of input filters with themselves. If psd - argument is given, the unwhitened filter inner products will be returned. - """ - return numpy.array([lalburst.ExcessPowerFilterInnerProduct(f, f, spec_corr, psd) for f in lal_filters]) - -def compute_filter_ips_adjacent(lal_filters, spec_corr, psd=None): - """ - Compute a set of filter inner products between input adjacent filters. - If psd argument is given, the unwhitened filter inner products will be - returned. The returned array index is the inner product between the - lal_filter of the same index, and its (array) adjacent filter --- assumed - to be the frequency adjacent filter. - """ - return numpy.array([lalburst.ExcessPowerFilterInnerProduct(f1, f2, spec_corr, psd) for f1, f2 in zip(lal_filters[:-1], lal_filters[1:])]) - -
[docs]def compute_channel_renormalization(filter_bank, spec_corr, nchans): - """ - Compute the renormalization for the base filters up to a given bandwidth. - """ - mu_sq_dict = {} - for nc_sum in range(0, int(math.log(nchans, 2))): - min_band = (len(filter_bank[0].data.data)-1) * filter_bank[0].deltaF / 2 - print "|- Calculation for %d %dHz channels" % (nc_sum+1, min_band) - nc_sum = 2**nc_sum - 1 - mu_sq = (nc_sum+1)*numpy.array([lalburst.ExcessPowerFilterInnerProduct(f, f, spec_corr, None) for f in filter_bank]) - # Uncomment to get all possible frequency renormalizations - #for n in xrange(nc_sum, nchans): # channel position index - for n in xrange(nc_sum, nchans, nc_sum+1): # channel position index - for k in xrange(0, nc_sum): # channel sum index - # FIXME: We've precomputed this, so use it instead - mu_sq[n] += 2*lalburst.ExcessPowerFilterInnerProduct(filter_bank[n-k], filter_bank[n-1-k], spec_corr, None) - #print mu_sq[nc_sum::nc_sum+1] - mu_sq_dict[nc_sum] = mu_sq - return mu_sq_dict
- -def measure_hrss(z_j_b, uw_ss_ii, uw_ss_ij, w_ss_ij, delta_f, delta_t, filter_len, dof): - """ - Approximation of unwhitened sum of squares signal energy in a given EP tile. - See T1200125 for equation number reference. - - Parameters - ---------- - z_j_b : time frequency map block which the constructed tile covers - uw_ss_ii : unwhitened filter inner products - uw_ss_ij : unwhitened adjacent filter inner products - w_ss_ij : whitened adjacent filter inner products - delta_f : frequency binning of EP filters - delta_t : native time resolution of the time frequency map - filter_len : number of samples in a fitler - dof : degrees of freedom in the tile (twice the time-frequency area) - """ - s_j_b_avg = uw_ss_ii * delta_f / 2 - # unwhitened sum of squares of wide virtual filter - s_j_nb_avg = uw_ss_ii.sum() / 2 + uw_ss_ij.sum() - s_j_nb_avg *= delta_f - s_j_nb_denom = s_j_b_avg.sum() + 2 * 2 / filter_len * \ - numpy.sum(numpy.sqrt(s_j_b_avg[:-1] * s_j_b_avg[1:]) * w_ss_ij) - # eqn. 62 - uw_ups_ratio = s_j_nb_avg / s_j_nb_denom - # eqn. 63 -- approximation of unwhitened signal energy time series - # FIXME: The sum in this equation is over nothing, but indexed by frequency - # I'll make that assumption here too. - s_j_nb = numpy.sum(z_j_b.T * numpy.sqrt(s_j_b_avg), axis=0) - s_j_nb *= numpy.sqrt(uw_ups_ratio / filter_len * 2) - # eqn. 64 -- approximate unwhitened signal energy minus noise contribution - # FIXME: correct axis of summation? - return math.sqrt(numpy.sum(numpy.absolute(s_j_nb)**2) * delta_t - s_j_nb_avg * dof * delta_t) - -def uw_sum_sq(filter1, filter2, spec_corr, psd): - # < s^2_j(f_1, b) > = 1 / 2 / N * \delta_t EPIP{\Theta, \Theta; P} - return lalburst.ExcessPowerFilterInnerProduct(filter1, filter2, spec_corr, psd) - -def measure_hrss_slowly(z_j_b, lal_filters, spec_corr, psd, delta_t, dof): - """ - Approximation of unwhitened sum of squares signal energy in a given EP tile. - See T1200125 for equation number reference. NOTE: This function is deprecated - in favor of measure_hrss, since it requires recomputation of many inner products, - making it particularly slow. - """ - # FIXME: Make sure you sum in time correctly - # Number of finest bands in given tile - nb = len(z_j_b) - # eqn. 56 -- unwhitened mean square of filter with itself - uw_ss_ii = numpy.array([uw_sum_sq(lal_filters[i], lal_filters[i], spec_corr, psd) for i in range(nb)]) - s_j_b_avg = uw_ss_ii * lal_filters[0].deltaF / 2 - # eqn. 57 -- unwhitened mean square of filter with adjacent filter - uw_ss_ij = numpy.array([uw_sum_sq(lal_filters[i], lal_filters[i+1], spec_corr, psd) for i in range(nb-1)]) - # unwhitened sum of squares of wide virtual filter - s_j_nb_avg = uw_ss_ii.sum() / 2 + uw_ss_ij.sum() - s_j_nb_avg *= lal_filters[0].deltaF - # eqn. 61 - w_ss_ij = numpy.array([uw_sum_sq(lal_filters[i], lal_filters[i+1], spec_corr, None) for i in range(nb-1)]) - s_j_nb_denom = s_j_b_avg.sum() + 2 * 2 / len(lal_filters[0].data.data) * \ - (numpy.sqrt(s_j_b_avg[:-1] * s_j_b_avg[1:]) * w_ss_ij).sum() - # eqn. 62 - uw_ups_ratio = s_j_nb_avg / s_j_nb_denom - # eqn. 63 -- approximation of unwhitened signal energy time series - # FIXME: The sum in this equation is over nothing, but indexed by frequency - # I'll make that assumption here too. - s_j_nb = numpy.sum(z_j_b.T * numpy.sqrt(s_j_b_avg), axis=0) - s_j_nb *= numpy.sqrt(uw_ups_ratio / len(lal_filters[0].data.data) * 2) - # eqn. 64 -- approximate unwhitened signal energy minus noise contribution - # FIXME: correct axis of summation? - return math.sqrt((numpy.absolute(s_j_nb)**2).sum() * delta_t - s_j_nb_avg * dof * delta_t) - -def measure_hrss_poorly(tile_energy, sub_psd): - return math.sqrt(tile_energy / numpy.average(1.0 / sub_psd) / 2) - -def trigger_list_from_map(tfmap, event_list, threshold, start_time, start_freq, duration, band, df, dt, psd=None): - - # FIXME: If we don't convert this the calculation takes forever --- - # but we should convert it once and handle deltaF better later - if psd is not None: - npy_psd = psd.numpy() - - start_time = LIGOTimeGPS(float(start_time)) - ndof = 2 * duration * band - - for i, j in zip(*numpy.where(tfmap > threshold)): - event = event_list.RowType() - - # The points are summed forward in time and thus a `summed point' is the - # sum of the previous N points. If this point is above threshold, it - # corresponds to a tile which spans the previous N points. However, the - # 0th point (due to the convolution specifier 'valid') is actually - # already a duration from the start time. All of this means, the + - # duration and the - duration cancels, and the tile 'start' is, by - # definition, the start of the time frequency map if j = 0 - # FIXME: I think this needs a + dt/2 to center the tile properly - event.set_start(start_time + float(j * dt)) - event.set_stop(start_time + float(j * dt) + duration) - event.set_peak(event.get_start() + duration / 2) - event.central_freq = start_freq + i * df + 0.5 * band - - event.duration = duration - event.bandwidth = band - event.chisq_dof = ndof - - event.snr = math.sqrt(tfmap[i,j] / event.chisq_dof - 1) - # FIXME: Magic number 0.62 should be determine empircally - event.confidence = -lal.LogChisqCCDF(event.snr * 0.62, event.chisq_dof * 0.62) - if psd is not None: - # NOTE: I think the pycbc PSDs always start at 0 Hz --- check - psd_idx_min = int((event.central_freq - event.bandwidth / 2) / psd.delta_f) - psd_idx_max = int((event.central_freq + event.bandwidth / 2) / psd.delta_f) - - # FIXME: heuristically this works better with E - D -- it's all - # going away with the better h_rss calculation soon anyway - event.amplitude = measure_hrss_poorly(tfmap[i,j] - event.chisq_dof, npy_psd[psd_idx_min:psd_idx_max]) - else: - event.amplitude = None - - event.process_id = None - event.event_id = event_list.get_next_id() - event_list.append(event) - -def make_tiles(tf_map, nc_sum, mu_sq): - tiles = numpy.zeros(tf_map.shape) - sum_filter = numpy.ones(nc_sum+1) - # Here's the deal: we're going to keep only the valid output and - # it's *always* going to exist in the lowest available indices - for t in xrange(tf_map.shape[1]): - # Sum and drop correlate tiles - # FIXME: don't drop correlated tiles - output = numpy.convolve(tf_map[:,t], sum_filter, 'valid')[::nc_sum+1] - #output = fftconvolve(tf_map[:,t], sum_filter, 'valid')[::nc_sum+1] - tiles[:len(output),t] = numpy.absolute(output) / math.sqrt(2) - return tiles[:len(output)]**2 / mu_sq[nc_sum::nc_sum+1].reshape(-1, 1) - -def make_indp_tiles(tf_map, nc_sum, mu_sq): - """ - Create a time frequency map with resolution of tf_map binning - divided by nc_sum + 1. All tiles will be independent up to - overlap from the original tiling. The mu_sq is applied to the - resulting addition to normalize the outputs to be zero-mean - unit-variance Gaussian variables (if the input is Gaussian). - - Notes - ----- - Optimization plan: If we keep the summed complex TF plane in known - indices, we can save ourselves individual sums at wider frequency - resolutions. - Caveats: - 1. We have to keep track of where we're storing things - 2. We have to do it from the finest resolution (for *all* t0s) - and work our way up - In the end, I think this is a Haar wavelet transform. Look into it. - """ - tiles = tf_map.copy() - # Here's the deal: we're going to keep only the valid output and - # it's *always* going to exist in the lowest available indices - stride = nc_sum + 1 - for i in xrange(tiles.shape[0]/stride): - numpy.absolute(tiles[stride*i:stride*(i+1)].sum(axis=0), tiles[stride*(i+1)-1]) - return tiles[nc_sum::nc_sum+1].real**2 / mu_sq[nc_sum::nc_sum+1].reshape(-1, 1) - -def make_filename(ifo, seg, tag="excesspower", ext="xml.gz"): - if isinstance(ifo, str): - ifostr = ifo - else: - ifostr = "".join(ifo) - st_rnd, end_rnd = int(math.floor(seg[0])), int(math.ceil(seg[1])) - dur = end_rnd - st_rnd - #return "%s-%s-%d-%d.%s" % (ifostr, tag, st_rnd, dur, ext) - return "%s.%s" % (tag, ext) - -def construct_tiles(nc_sum,mu_sq,band,ts_data,tf_map,psd_segment_length,window_fraction,max_duration): - """ - Constructing tile and calculate their energy - """ - # Clip the boundaries to remove window corruption - clip_samples = int(psd_segment_length * window_fraction * ts_data.sample_rate / 2) - print "|--- Constructing tile with %d summed channels..." % (nc_sum+1) - # Current bandwidth of the time-frequency map tiles - df = band * (nc_sum + 1) - # How much each "step" is in the time domain -- under sampling rate - dt = 1.0 / (2 * df) - us_rate = int(round(dt / ts_data.delta_t)) - print "|--- Undersampling rate for this level: %f" % (ts_data.sample_rate/us_rate) - print "|--- Calculating tiles..." - if clip_samples > 0: # because [0:-0] does not give the full array - tiles = make_indp_tiles(tf_map[:,clip_samples:-clip_samples:us_rate], nc_sum, mu_sq) - else: - tiles = make_indp_tiles(tf_map[:,::us_rate], nc_sum, mu_sq) - print "|--- TF-plane is %dx%s samples" % tiles.shape - print "|--- Tile energy mean %f, var %f" % (numpy.mean(tiles), numpy.var(tiles)) - if max_duration is not None: - max_dof = 2 * max_duration * (band * (nc_sum+1)) - else: - max_dof = 32 - assert max_dof >= 2 - return max_dof, tiles, us_rate, dt, df - -def create_tile_duration(j,df,duration,tiles): - # Duration is fixed by the NDOF and bandwidth - duration = j / 2.0 / df - print "|----- Explore signal duration of %f s..." % duration - print "|----- Summing DOF = %d ..." % (2*j) - tlen = tiles.shape[1] - 2*j + 1 + 1 - dof_tiles = numpy.zeros((tiles.shape[0], tlen)) - sum_filter = numpy.array([1,0] * (j-1) + [1]) - for f in range(tiles.shape[0]): - # Sum and drop correlate tiles - dof_tiles[f] = fftconvolve(tiles[f], sum_filter, 'valid') - print "|----- Summed tile energy mean: %f, var %f" % (numpy.mean(dof_tiles), numpy.var(dof_tiles)) - return dof_tiles - -def create_xml(ts_data,psd_segment_length,window_fraction,event_list,station,setname="MagneticFields"): - __program__ = 'pyburst_excesspower' - start_time = LIGOTimeGPS(int(ts_data.start_time)) - end_time = LIGOTimeGPS(int(ts_data.end_time)) - inseg = segment(start_time,end_time) - xmldoc = ligolw.Document() - xmldoc.appendChild(ligolw.LIGO_LW()) - ifo = 'H1'#channel_name.split(":")[0] - straindict = psd.insert_psd_option_group.__dict__ - proc_row = register_to_xmldoc(xmldoc, __program__,straindict, ifos=[ifo],version=git_version.id, cvs_repository=git_version.branch, cvs_entry_time=git_version.date) - outseg = determine_output_segment(inseg, psd_segment_length, ts_data.sample_rate, window_fraction) - ss = append_search_summary(xmldoc, proc_row, ifos=(station,), inseg=inseg, outseg=outseg) - for sb in event_list: - sb.process_id = proc_row.process_id - sb.search = proc_row.program - sb.ifo, sb.channel = station, setname - xmldoc.childNodes[0].appendChild(event_list) - fname = make_filename(station, inseg) - utils.write_filename(xmldoc, fname, gz=fname.endswith("gz")) - -def determine_output_segment(inseg, dt_stride, sample_rate, window_fraction=0.0): - """ - Given an input data stretch segment inseg, a data block stride dt_stride, the data sample rate, and an optional window_fraction, return the amount of data that can be processed without corruption effects from the window. - If window_fration is set to 0 (default), assume no windowing. - """ - # Amount to overlap successive blocks so as not to lose data - window_overlap_samples = window_fraction * sample_rate - outseg = inseg.contract(window_fraction * dt_stride / 2) - # With a given dt_stride, we cannot process the remainder of this data - remainder = math.fmod(abs(outseg), dt_stride * (1 - window_fraction)) - # ...so make an accounting of it - outseg = segment(outseg[0], outseg[1] - remainder) - return outseg -
- -
- - - - - \ No newline at end of file diff --git a/docs/_modules/index.html b/docs/_modules/index.html deleted file mode 100644 index 408a090..0000000 --- a/docs/_modules/index.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - Overview: module code — gdas 0.3.0 documentation - - - - - - - - - - - - - - - -
- - -

All modules for which code is available

- - -
- - - - - \ No newline at end of file diff --git a/docs/_sources/backup/backup.rst.txt b/docs/_sources/backup/backup.rst.txt deleted file mode 100644 index 85ce8b7..0000000 --- a/docs/_sources/backup/backup.rst.txt +++ /dev/null @@ -1,1406 +0,0 @@ -GNOME Data Analysis Software -============================ - -* :ref:`test2` - - .. _test2: - -.. toctree:: - :maxdepth: 2 - :caption: People & By-laws - - test - -.. toctree:: - :maxdepth: 2 - - index.rst - -Introduction -============ - -This package contains functions useful for magnetic field signal processing, with a focus on Excess Power search analysis and application on the data for the GNOME collaboration, see `Pustelny et al. (2013) `_. This documentation details all the available functions and tasks available through this software. Here are some example tasks that can (or will soon to) be handled: - -* Plot usual time series and spectrogram of magnetic field data. -* Perform excess power analysis and plot detected triggers in time-frequency map. -* Create artificial data for testing data analysis. -* Inject fake signal of different bandwidth and durations. -* Cross-correlation of continuous sine wave signals. -* Perform Allan Standard deviation. - -.. raw:: html - - Fork me on GitHub - -Installation -============ - -The program requires the following general packages to run: `Numpy `_, `Matplotlib `_, `Scipy `_ and `Astropy `_. The following LIGO-related packages are also required for full functionality: `Gwpy `_, `PyCBC `_, `Glue `_, `LAL `_, `LALburst `_ and `LALsimulation `_. - -While most of the packages can be installed automatically using `pip `_, some LIGO packages (Glue, LAL, LALburst and LALsimulation) must be installed separately beforehand as they contain several C routines that need specific compilation. However, these packages are already included in a bigger package called `LALsuite `_ which can be installed fairly easily on Debian (Linux) and Mac OS machines. - -LALsuite tools --------------- - -Some useful pages on how to download and install the LIGO software can be found `here `_. - -MacPorts (Mac) -~~~~~~~~~~~~~~ - -For Mac users, the installation is pretty easy, detailed information can be found on `this page `_. You need to have `MacPorts `_ installed. The following commands should suffice to install the LALsuite package on your machine:: - - sudo port install lscsoft-deps - sudo port install glue - sudo port install lalapps - -The first command will install all the dependencies needed for the LIGO software to be installed. The following 2 commands will install the actual packages. - -apt-get (Debian) -~~~~~~~~~~~~~~~~ - -Since the LIGO software is not a default package in the apt package manager system on Debian machine, additional steps will be needed. The first step is to add the following links to the source list located at ``/etc/apt/sources.list``:: - - deb [arch=amd64] http://software.ligo.org/lscsoft/debian jessie contrib - deb-src [arch=amd64] http://software.ligo.org/lscsoft/debian jessie contrib - -Note that the ``[arch=amd64]`` is needed to fix the architecture problem in case it tries to install i386 version on 64-bit Debian. Once the sources have been added, you must first install all the dependencies as follows:: - - apt-get install build-essential automake autoconf libtool devscripts - -The LIGO software can finally be installed using the following command:: - - apt-get install lscsoft-all - -Main Program ------------- - -The best way to install the GNOME software along with the rest of the dependencies is by using `pip`:: - - pip install gdas - -(You may need to put a ``sudo`` in front of this). For this to work -you need to have `pip -`_ installed. This -method allows for easy uninstallation. - -You can also simply download the tarball from the PyPI website, unpack it and then do:: - - python setup.py install - -The latest stable package can be downloaded from PyPI: https://pypi.python.org/pypi/gdas. -The development version can be downloaded from `here `_. - -Multi-user Server -================= - -A GNOME JupyterHub, or multi-user server has been created to allow each member to access the entire available dataset. Member who do not have access to the server but wish to access it should send a request to Dr. Sam Afach. Member who are not part of the GNOME collaboration will not be granted access to the dataset but are free to use our software on their own data. - -The server can be accessed in two ways, either by acceding the `server's webpage `_, or from your terminal through SSH:: - - ssh -X username@budker.uni-mainz.de -p 8022 - -While SSH is very handy for people using UNIX-like operating systems, this can become more complicated for those working on Windows machines. Fortunately, access to a terminal is also possible through the webpage, which means directly from your internet browser! This can be done by clicking on the New tab after login and select Terminal: - -.. figure:: img/jupyter1.png - :width: 70% - :align: center - -You can then use the terminal window to access files and create new Python scripts for your analysis. - -.. figure:: img/jupyter2.png - :width: 70% - :align: center - -Working Example -=============== - -Either on your own computer or on the server, on a Jupyter notebook or on a Python script, the first thing to do is to import the ``gdas`` package that contain all the modules present in the GNOME software. That can be done easily by doing the following:: - - import gdas - -In order to retrieve a specific chunk of data to be analyzed for a particular station, the name of the station along with the start and end dates should be specified:: - - station = 'fribourg01' - start_time = '2016-11-03-04' - end_time = '2016-11-03-04-2' - -where the start and end times should always have at least the year, month and day specified, and with the values separated by a dash symbol. Hour and minute can also be specified. - -If you are not working on the server and the data are located in a different repository than ``/GNOMEDrive/gnome/serverdata/``, a custom path can be defined. For instance:: - - datapath = '/Users/vincent/data/GNOMEDrive/gnome/serverdata/' - -The magnetic field data can then be retrieve as follows:: - - ts_data,ts_list,activity = gdas.magfield(station,start_time,end_time,rep=datapath) - -The ``gdas.magfield`` method will return 3 arrays of data that can then be used to produce different plots:: - - gdas.plot_activity(activity) - gdas.plot_time_series(station,ts_list,seglist=activity) - gdas.plot_asd(station,ts_list) - gdas.plot_whitening(station,ts_list,activity) - -This is a script to do Excess Power analysis:: - - psd_segment_length = 60 - psd_segment_stride = 30 - psd_estimation = 'median-mean' - window_fraction = 0 - tile_fap = 1e-5 - channels = 250 - - gdas.excess_power(ts_data,psd_segment_length,psd_segment_stride,psd_estimation,window_fraction,tile_fap,station,nchans=channels) - gdas.plot_triggers() - -Data extraction -=============== - -Extracting real data --------------------- - -Retrieve metadata -~~~~~~~~~~~~~~~~~ - -The first step is to define some variables related to which data we want to study and their location. The ``os.path.join`` method will join that different paths called as arguments (i.e. in the parenthesis):: - - # Set name of the channel to extract - setname = "MagneticFields" - # Define station name and map - station = "fribourg01" - # Define year, month and day - year,month,day = '2016','11','03' - # Define path to main data repository - path1 = '/Users/vincent/ASTRO/data/GNOMEDrive/gnome/serverdata/' - # Define path to day repository - path2 = "%s/%s/%s/%s/"%(station,year,month,day) - # Define generic hdf5 filenames - path3 = "%s_%s%s%s_*.hdf5"%(station,year,month,day) - # Define full generic path name - fullpath = os.path.join(path1,path2,path3) - -We then use the `glob `_ module to list all the files that satisfy the full path name and loop over each HDF5 file and do the following: - -- Extract its metadata using the `h5py `_ package; -- Calculate the segment in time for which the data corresponds to using the :ref:`file_to_segment ` function; -- Store each filename and metadata on two different dictionary variables ``file_order`` and ``file_order``. - -Finally, we extract the sampling rate from one of the file which will be use later in the analysis. The sampling rate is the same for all the data files:: - - # Initialising dictionary for data - file_order,data_order = {},{} - # Loop over all existing data files - for fname in glob.glob(fullpath): - # Read hdf5 file - hfile = h5py.File(fname, "r") - # Extract segment information from file - segfile = file_to_segment(hfile,setname) - # Associate file in dictionary with association to segment data - file_order[segfile] = fname - data_order[segfile] = hfile - # Retrieve sampling rate from last read file - sample_rate = hfile[setname].attrs["SamplingRate(Hz)"] - -Creating segment lists -~~~~~~~~~~~~~~~~~~~~~~ - -This section will create a continuous list of all the data segments available. We use the following modules in order to create the list properly: - -- The `segmentlist `_ module from the ``glue.segments`` library defines the list of segments. The =coalesce()= method is then used to put all the segments in coalesced state. -- The `DataQualityDict `_ module from the ``gwpy.segments`` library allows to store all the data segments in an ordered dictionary. -- The `DataQualityFlag `_ module from the ``gwpy.segments`` library allows to *record times during which the instrument was operating outside of its nominal condition*. - -The script is as follows:: - - # Generate an ASCII representation of the GPS timestamped segments of time covered by the input data - seglist = segmentlist(data_order.keys()) - # Sort the segment list - seglist.sort() - # Initialise dictionary for segment information - full_seglist = DataQualityDict() - # Save time span for each segment in ASCII file - with open("segments.txt", "w") as fout: - for seg in seglist: - print >>fout, "%10.9f %10.9f" % seg - # FIXME: Active should be masked from the sanity channel - full_seglist[station] = DataQualityFlag(station,active=seglist.coalesce(),known=seglist.coalesce()) - # Define start and end time of entire dataset - start, end = full_seglist[station].active.extent() - -Establishing active times -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here's the script:: - - # Generate an ASCII representation of the GPS timestamped segments of time covered by the input data - seglist = segmentlist(data_order.keys()) - # Sort the segment list - seglist.sort() - # Import gwpy tools - plot = SegmentPlot() - # Initialize plotting figure - ax = plot.gca() - # Plot all segment in figure - ax.plot(full_seglist) - # Save figure - pyplot.savefig("activity.png",dpi=500) - -Retrieve and concatenate the data. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here's the script:: - - # Generate time series for the ensemble of data - data_list = generate_timeseries(file_order,setname) - # Retrieve channel data for all the segments - full_data = numpy.hstack([retrieve_channel_data(data_order[seg],setname) for seg in seglist]) - # Define log base 2 of the total time length of the full data - loglength = math.log(len(full_data)/sample_rate, 2) - # Define zero padding - zpad = math.ceil(loglength) - zpad = int(2**zpad) - len(full_data)/sample_rate - zpad = numpy.zeros(int(zpad*sample_rate / 2.0)) - # Include padding next to the data - full_data = numpy.hstack((zpad, full_data, zpad)) - # Models a time series consisting of uniformly sampled scalar values - ts_data = types.TimeSeries(full_data,delta_t=1/sample_rate,epoch=seglist[0][0]) - # Loop over all the elements in the dictionary - for v in data_order.values(): - # Close the element - v.close() - -Producing fake data -------------------- - -Create simulated time series data -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -It is easy to create fake data, one can use the `numpy.random.normal `_ method from the Numpy library to draw random samples from a normal Gaussian distribution with mean of 0, standard deviation of 1, and a length equal to the sampling rate (``args.sample_rate``) times the length in seconds of individual segments (``args.psd_segment_length``) times the number of segment the user wish to produce. After defining the starting UTC time, one can then create a time series of the data using the `TimeSeries `_ module from the ``gwpy.timeseries`` library.:: - - print "Create fake data..." - start = 1153742437.0 - end = start + args.psd_segment_length * 16 - station = "gaussian-noise" - setname = "MagneticFields" - full_data = numpy.random.normal(0, 1, int(args.sample_rate * args.psd_segment_length * 16)) - ts_data = TimeSeries(full_data, sample_rate=args.sample_rate,epoch=start) - -Produce and plot fake signal -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here's the script:: - - delta_t = 1.0/args.sample_rate - filter_band = 4 - #q = math.sqrt(2)*f_0/filter_band * 2 - #f_0 = 18 - duration = 0.1 - hrss = 0.0275 - #hp, hx = SimBurstSineGaussian(q * 2, f_0, hrss, 1, 0, data_dt) - hp, hx = SimBurstGaussian(duration, hrss, delta_t) - hp = TimeSeries.from_lal(hp) - hx = TimeSeries.from_lal(hx) - # We rescale the amplitude to hide or expose it in the data a bit better - hp *= 100. - - pyplot.figure() - pyplot.plot(hp.times, hp, 'k-') - pyplot.xlim([-0.5, 0.5]) - pyplot.ylim([-0.1, 0.1]); - pyplot.xlabel('Time (s)') - pyplot.ylabel('Magnitude') - pyplot.savefig('fakesignal.png') - pyplot.close() - -Inject fake signal into artificial data -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here's the script:: - - random_time = int((start+end)/2.) - st = (random_time-start)*args.sample_rate - len(hp)/2 - en = st + len(hp) - hp.epoch = random_time - ts_data[st:en] += hp - data_list = [ts_data] - ts_data = types.TimeSeries(ts_data.value,delta_t=1.0/args.sample_rate,epoch=start) - -Plotting Data -============= - -Generate a plot of the data time series ---------------------------------------- - -Here's the script:: - - # Include time series element in dictionary - plot = TimeSeriesPlot() - # Create axis in plot - ax = plot.gca() - # Loop over all the time series - for ts in data_list: - # Plot time series for each segment - ax.plot(ts, color='blue') - # Display title - ax.set_title(station) - # Plot activity segments - plot.add_state_segments(SegmentList(full_seglist[station].active),plotargs={'label':'data present','facecolor': 'g','edgecolor': 'k'}) - # Define edges of the x axis - ax.set_xlim(start, end) - # Save figure - plot.savefig('time_series.png',dpi=500) - -Create sound based on the data ------------------------------- - -Here's the script:: - - wout = wave.open("pure_tone.wav", "w") - wout.setnchannels(1) # mono - wout.setsampwidth(4) # 32 bit audio - wout.setframerate(1000) - wout.writeframes(ts[:]) - wout.close() - -Invoking precision issues -------------------------- - -AGG complexity starts to complain with large numbers of points and we somehow invoke precision issues that need to be ameliorated:: - - for d in data_list: - d.x0 = Quantity(int(d.x0.value * 500), d.xunit) - d.dx = Quantity(1, d.xunit) - data_list.coalesce() - for d in data_list: - d.x0 = Quantity(d.x0.value / 500, d.xunit) - d.dx = Quantity(0.002, d.xunit) - -Amplitude Spectral Density (ASD) --------------------------------- - -Here's the script:: - - # Initialize plotting functionality - plot = SpectrumPlot() - # Loop over all the time series - for d in data_list: - # Generate 8 seconds per FFT with 4 second (50%) overlap - spectrum = d.asd(8, 4) - # Create plotting axis - ax = plot.gca() - # Plot square root of the spectrum - ax.plot(numpy.sqrt(spectrum)) - # Set x axis to log scale - ax.set_xscale('log') - # Set y axis to log scale - ax.set_yscale('log') - # Set x axis limits - ax.set_xlim(1e-1, 500) - # Save figure - plot.savefig("asd.png",dpi=500) - -(Un)normalized Spectrograms ---------------------------- - -The first thing to do is to initialise the plotting axis for both figure as well as some display settings specific to spectrogram and which can be loaded using the `SpectrogramPlot() `_ module from the ``gwpy.plotter`` library:: - - plot = SpectrogramPlot() - ax = plot.gca() - white_plot = SpectrogramPlot() - wax = white_plot.gca() - -The spectrogram is then created using the `spectrogram `_ function from the ``gwpy.timeseries.TimeSeries`` package. This will *calculate the average power spectrogram of this TimeSeries using the specified average spectrum method* (default being the Welch's method). We define the 3 following variables that will be used to construct the spectrogram: - -- ``stride``: number of seconds in single PSD (column of spectrogram), default 20; -- ``fftlength``: number of seconds in single FFT, default 6; -- ``overlap``: number of seconds between FFTs, default 3. - -We can then loop over all the time series made from each loaded HDF5 data file, and construct the spectrogram for each time series. The whitening of the spectrogram is then done by normalisation it, which can be performed using the `ratio `_ method from the ``gwpy.spectrogram.Spectrogram`` library. This will calculate the ratio of the created spectrogram against a specific reference, here we chose the reference to be the median of each spectrum in the given spectrogram: - -.. math:: - \sqrt{S(f,t)}/\sqrt{\overline{S(f)}} - -The script is as follows:: - - for ts in data_list: - if (len(ts) * ts.dt).value < stride: - continue - spec = ts.spectrogram(stride, fftlength=fftlength, overlap=overlap) - ax.plot(spec) - wspec = spec.ratio('median') - wax.plot(wspec, vmin=0.1, vmax=100) - -Finally, the plot can be completed by including the activity period below each figure:: - - ax.set_title(station) - ax.set_xlim(seglist[0][0], seglist[-1][1]) - ax.set_ylim(1e-1, 500) - ax.set_yscale('log') - plot.add_colorbar(log=True) - plot.add_state_segments(SegmentList(full_seglist[station].active),plotargs={'label':'data present','facecolor':'g','edgecolor':'k'}) - plot.savefig("spectrogram.png",dpi=500) - - wax.set_title(station) - wax.set_xlim(seglist[0][0], seglist[-1][1]) - wax.set_ylim(1e-1, 500) - wax.set_yscale('log') - white_plot.add_colorbar(log=True) - white_plot.add_state_segments(SegmentList(full_seglist[station].active),plotargs={'label':'data present','facecolor':'g','edgecolor':'k'}) - white_plot.savefig("whitened_spectrogram.png",dpi=500) - -Excess-Power algorithm -====================== - -General overview ----------------- - -The **Excess Power method** is known as the *optimal detection strategy* to search for burst signals for which only the duration and frequency band are known, which is basically the case for GNOME and its search of Axion-Like Particles (ALP). This method was developed and introduced by `Anderson et al. (200) `_ and has been extensively used in the detection of burst sources of gravitational radiation. A more technical documentation was written by `Brady et al. (2007) `_ describing how the algorithm used by the LIGO collaboration works and how the theory is translated into code. - -We present below a step-by-step procedure followed during the Excess Power search analysis. For a better representation of what is happening, the figure at the end shows how the data is being split and analysed to search for multiple signals of different bandwidth and duration in the time-frequency plane. - -- :ref:`Time domain segmentation and PSD estimate ` - - We first estimate the instrument's noise Power Spectral Density (PSD) by splitting the time-series data into multiple overlapping segments. A periodogram for each segment is calculated separately and then averaged, which will reduce the variance of the individual power measurements. The result is a frequency series where samples are separated in frequency space by :math:`\Delta f` equal to the inverse of a segment’s length and with a high end frequency limit equal to the Nyquist limit. The final power spectrum will help reveal the existence, or the absence, of repetitive patterns and correlation structures in a signal process. - -- :ref:`Comb of frequency channels ` - - We then split the PSD frequency series into multiple channels. For each channel, a frequency domain filter is created with a :math:`\Delta f` determined by the PSD and a total extent in Fourier space that is twice the stated bandwidth of a channel. The result is a list of each channel filter's frequency series. - -- :ref:`Creating analysing blocks ` - - The Excess Power method can lead to moderately-large computational requirements, and it has been found that the computational efficiency of this implementation can be improved upon by considering blocks of data that are much longer than the longest signal time duration. The entire time series is therefore split into separate blocks. We use the length of the segments used for PSD estimate to define the duration of each block. For each block, the time series is c0Aonverted into frequency series which is then filtered by the filter bank throughout all the channels. A time-frequency map is finally created which stores all the filtered frequency series from each channel. - -- :ref:`Creating tiles with different bandwidth ` - - We can now construct tiles with different bandwidth by summing multiple channels together. - -- :ref:`Exploring tiles with different duration ` - - For each given tile's bandwidth, one can investigate different tile's duration. This can be done by exploring different number of degrees of freedom, :math:`d`, which can be calculated as follows: :math:`d=2BT` where :math:`B` and :math:`T` are respectively the bandwidth and duration of the tile. Section 2.2.5 of `Brady et al. `_ gives a great description of how to interpret the number of degrees of freedom. Therefore, by changing the :math:`d`, one can explore multiple tile's duration for different bandwidth. - -- :ref:`Define triggering signal ` - - The energy of each tile in the time-frequency space is calculated and compare to a user-defined threshold value. After defining a tile false alarm probability threshold in Gaussian noise and using the number of degrees of freedom for each tile, one can define a energy threshold value above which a burst trigger can be identified by comparing the energy threshold with the tile's energy in the time-frequency map. A tile energy time frequency map plot similar to Figure 5 in `Pustelny et al. (2013) `_ can then be made which plots the outlying tile energies present in the data. - -.. figure:: ./img/overview.png - - Overview of the Excess Power method and difference between segments, channels, tiles and blocks. - -.. _psdestimate: - -Estimate Power Spectral Density (PSD) -------------------------------------- - -The instrument's noise Power Spectral Density (PSD) will be used to whiten the data and help reveal the existence, or the absence, of repetitive patterns and correlation structures in the signal process. It will also determine the total bandwidth spanned by each of the filters that will subsequently be created. The first thing to do before calculating the PSD is to ensure that the time series data is converted into an array of floating values. :: - - # Convert time series as array of float - data = ts_data.astype(numpy.float64) - -The PSD is calculated by splitting up the signal into overlapping segments and scan through each segment to calculate individual periodogram. The periodograms from each segment are then averaged, reducing the variance of the individual power measurements. In order to proceed, we need to define the average method, ``avg_method``, that will be used to measure the PSD from the data. This can be specified with the ``--psd-estimation`` option. :: - - # Average method to measure PSD from the data - avg_method = args.psd_estimation - -One also needs to specify the length of each segment, ``seg_len``, as well as the separation between 2 consecutive segments, ``seg_stride``. Both parameters can be defined in second units with the ``--psd-segment-length`` and ``--psd-segment-stride`` arguments respectively and can then be converted into sample unit. :: - - # The segment length for PSD estimation in samples - seg_len = int(args.psd_segment_length * args.sample_rate) - # The separation between consecutive segments in samples - seg_stride = int(args.psd_segment_stride * args.sample_rate) - -We then use the `Welch's method `_ to perform the power spectral density estimate using the `welch `_ module from the ``pycbc.psd`` library. What this will do is to compute the discrete Fourier transform for each PSD segment to produce invidual periodograms, and then compute the squared magnitude of the result. The individual periodograms are then averaged using the user-defined average method, ``avg_method``, and return the frequency series, ``fd_psd``, which will store the power measurement for each frequency bin. :: - - # Lifted from the psd.from_cli module - fd_psd = psd.welch(data,avg_method=avg_method,seg_len=seg_len,seg_stride=seg_stride) - # Plot the power spectral density - plot_spectrum(fd_psd) - # We need this for the SWIG functions - lal_psd = fd_psd.lal() - -One can display the power measurements, frequency array and frequency between consecutive samples, :math:`\Delta f` in Hertz, by printing the following variables: :: - - print 'Display power measurements of the first 10 frequency bins' - print fd_psd[:10] - print 'Display central frequency of the first 10 bins' - print fd_psd.sample_frequencies[:10] - print 'Display the frequency separation between bins' - print fd_psd.delta_f - -:math:`\Delta f` corresponds to the inverse of a segment's length which is the smallest frequency (i.e. highest period) of detectable signals in each segment. The frequency range spans from 0 to the Nyquist frequency, i.e. half de the sampling rate. - -Checking filtering settings ---------------------------- - -The first thing to check is that the frequency of the high-pass filter (if defined) is below the minimum frequency of the filter bank. Indeed, a high-pass filter will only let pass frequency that are higher than the cutoff frequency (here defined by the ``strain_high_pass`` argument). If the high pass frequency is greater from the minimum frequency in the filter bank, the signal with frequencies lower than the cutoff frequency will get attenuated. :: - - if args.min_frequency < args.strain_high_pass: - print >>sys.stderr, "Warning: strain high pass frequency %f is greater than the tile minimum frequency %f --- this is likely to cause strange output below the bandpass frequency" % (args.strain_high_pass, args.min_frequency) - -In case the maximum frequency in the filter bank is not defined, we set it to be equal to the Nyquist frequency, i.e. half the sampling rate, which makes sense as a larger signal will not be able to get easily identifiable. :: - - if args.max_frequency is None: - args.max_frequency = args.sample_rate / 2.0 - -If the bandwidth of the finest filter (``--tile-bandwidth`` argument, see section :ref:`construct_args ` or the number of frequency channels (=--channels= argument) is not defined but the total spectral band is (``data_band``), one can then determined all the filter settings as follows: :: - - - if args.tile_bandwidth is None and args.channels is None: - # Exit program with error message - exit("Either --tile-bandwidth or --channels must be specified to set up time-frequency plane") - else: - # Define as assert statement that tile maximum frequency larger than its minimum frequency - assert args.max_frequency >= args.min_frequency - # Define spectral band of data - data_band = args.max_frequency - args.min_frequency - # Check if tile bandwidth or channel is defined - if args.tile_bandwidth is not None: - # Define number of possible filter bands - nchans = args.channels = int(data_band / args.tile_bandwidth) - 1 - elif args.channels is not None: - # Define filter bandwidth - band = args.tile_bandwidth = data_band / (args.channels + 1) - assert args.channels > 1 - -The minimum frequency to be explored can be user-defined by using the ``--min-frequency`` option. :: - - # Lowest frequency of the first filter - flow = args.min_frequency - -Whitening window and spectral correlation ------------------------------------------ - -This part determines how much data on either side of the tukey window is to be discarded. Nominally, this means that one will lose ``window_fraction`` * ``args.psd_segment_length`` to corruption from the window, i.e. this is simply discarded. This is tuned to give an integer offset when used with ``args.psd_segment_length`` equal to 8, smaller windows will have fractions of integers, but larger powers of two will still preseve this (probably not a big deal in the end). :: - - window_fraction = 0 - -The two point spectral correlation is then done with the :ref:`calculate_spectral_correlation ` function which will return both the Tukey window applied to the original time series data and the actual two-point spectral correlation function for the whitened frequency series from the applied whitening window. :: - - # Do two point spectral correlation - window, spec_corr = calculate_spectral_correlation(seg_len,'tukey',window_fraction=window_fraction) - window = window.data.data - window_sigma_sq = numpy.mean(window**2) - # Pre scale the window by its root mean squared -- see eqn 11 of EP document - #window /= numpy.sqrt(window_sigma_sq) - -.. _filterbank: - -Computing the filter bank -------------------------- - -The filter bank will create band-pass filters for each channel in the PSD frequency domain. The :ref:`create_filter_bank ` function will san the bandwidth from the central frequency of the first channel (i.e. flow+band/2) to final frequency of the last channel (i.e. band*nchans) in a increment equal to the frequency band. The filter's total extent in Fourier space is actually twice the stated bandwidth (FWHM). :: - - # Define filters - filter_bank, fdb = create_filter_bank(fd_psd.delta_f, flow+band/2, band, nchans, fd_psd, spec_corr) - -This function will returns 2 arrays: the ``filter_bank`` array which is a list of `COMPLEX16FrequencySeries `_ arrays corresponding to each channel's filter, and the =fdb= array which provides the time-series from each filter. The length of each array is equal to the total number of channel (i.e. =nchans=). The filter's data, :math:`\Delta f` value, and first and last frequencies of any channel's filter can be displayed as followed: :: - - # Print data of first channel's filter - print filter_bank[0].data.data - # Print frequency separation between 2 values in the first channel's filter - print filter_bank[0].deltaF - # Print first frequency of the first channel's filter - print filter_bank[0].f0 - # Print last frequency of the first channel's filter (equal to twice the channel's bandwidth) - print filter_bank[0].f0+(len(filter_bank[0].data.data)-1)*filter_bank[0].deltaF - -Further in the analysis, the following filters will used: -1. ``white_filter_ip``: Whitened filter inner products computed with themselves. -2. ``unwhite_filter_ip``: Unwhitened filter inner products computed with themselves. -3. ``white_ss_ip``: Whitened filter inner products computed between input adjacent filters. -4. ``unwhite_ss_ip``: Unwhitened filter inner products computed between input adjacent filters. - -:: - - # This is necessary to compute the mu^2 normalizations - white_filter_ip = compute_filter_ips_self(filter_bank, spec_corr, None) - unwhite_filter_ip = compute_filter_ips_self(filter_bank, spec_corr, lal_psd) - # These two are needed for the unwhitened mean square sum (hrss) - white_ss_ip = compute_filter_ips_adjacent(filter_bank, spec_corr, None) - unwhite_ss_ip = compute_filter_ips_adjacent(filter_bank, spec_corr, lal_psd) - -Normalization of virtual channel --------------------------------- - -The virtual channels will be used during the excesspower analysis to explore different frequency ranges around each PSD segments and look for possible triggers. Each channel is renormalized using the :ref:`compute_channel_renomalization ` internal function. :: - - # Initialise dictionary - mu_sq_dict = {} - # nc_sum additional channel adds - for nc_sum in range(0, int(math.log(nchans, 2))): - min_band = (len(filter_bank[0].data.data)-1) * filter_bank[0].deltaF / 2 - print tprint(t0,t1),"Calculation for %d %d Hz channels" % (nc_sum+1, min_band) - nc_sum = 2**nc_sum - 1 - mu_sq_dict[nc_sum] = compute_channel_renomalization(nc_sum, filter_bank, spec_corr, nchans) - -Initialise event list and determine stride boundaries ------------------------------------------------------ - -First of all, we create a table similar than the one made by the LIGO Scientific Collaboration (LSC) where all the information will be stored. Such table is commonly know as ``lsctables``. A pre-defined LSC table can be constructed using ``New`` function from the `glue.ligolw.lsctables `_ module. We use the ``SnglBurstTable`` function for the type of data to be stored and define all the columns we wish to record. :: - - # Create event list for single burst table - event_list = lsctables.New(lsctables.SnglBurstTable, - ['start_time','start_time_ns','peak_time','peak_time_ns', - 'duration','bandwidth','central_freq','chisq_dof', - 'confidence','snr','amplitude','channel','ifo', - 'process_id','event_id','search','stop_time','stop_time_ns']) - -We also need to determine the indexes of both starting and ending times for the first segment to analyse, respectively ``t_idx_min`` and ``t_idx_max``. The default values are considered to be 0 for the starting index and the segment length in sample unit for the ending time index. Also, if the user defines a different starting time than the one from the loaded data, the offset index in sample unit is determined and added the both starting and ending time indexes. :: - - # Determine boundaries of stride in time domain - t_idx_min, t_idx_max = 0, seg_len - # Check if user requested starting time is defined - if args.analysis_start_time is not None: - # Define the time difference in seconds between data and user requested starting times - t_idx_off = args.analysis_start_time - ts_data.start_time - # Calculate the index of the user requested starting point in the data - t_idx_off = int(t_idx_off * args.sample_rate) - else: - # Define index of the starting point as first value in data - t_idx_off = 0 - # Initialise minimum index values as offset starting index - t_idx_min += t_idx_off - # Initialise maximum index values as offset starting index - t_idx_max += t_idx_off - -Finally, the index for the ending time after all the segments have been analysed can be estimated for the user-defined parameter or is defined as the length of the time series data ``ts_data``. :: - - # Check if user requested end time is defined - if args.analysis_end_time is not None: - # Define the time difference between data and user requested ending times - t_idx_max_off = args.analysis_end_time - ts_data.start_time - # Calculate the index of the user requested starting point in the data - t_idx_max_off = int(t_idx_max_off * args.sample_rate) - else: - # Define index of the ending point as the length of data array - t_idx_max_off = len(ts_data) - -.. _analysingblocks: - -Define analysing blocks ------------------------ - -The first thing we do is to calculate the time series for the segment that is covered (``tmp_ts_data``) and redefined the metadata, especially the time of the first sample in seconds which is defined by the ``epoch`` argument and is different for every segment. After plotting the time series for that segment, the data are then converted into frequency series (``fs_data``) using the `to_frequencyseries `_ module from the ``pycbc.types.timeseries.TimeSeries`` library. Finally, the frequency data are then whitened. :: - - # Loop over each data within the user requested time period - while t_idx_max <= t_idx_max_off: - # Define starting and ending time of the segment in seconds - start_time = ts_data.start_time + t_idx_min/float(args.sample_rate) - end_time = ts_data.start_time + t_idx_max/float(args.sample_rate) - print tprint(t0,t1),"Analyzing block %i to %i (%.2f percent)"%(start_time,end_time,100*float(t_idx_max)/float(idx_max_off)) - # Model a withen time series for the block - tmp_ts_data = types.TimeSeries(ts_data[t_idx_min:t_idx_max]*window, 1.0/args.sample_rate,epoch=start_time) - # Save time series in segment repository - segfolder = 'segments/%i-%i'%(start_time,end_time) - os.system('mkdir -p '+segfolder) - plot_ts(tmp_ts_data,fname='%s/ts.png'%(segfolder)) - # Convert times series to frequency series - fs_data = tmp_ts_data.to_frequencyseries() - print tprint(t0,t1),"Frequency series data has variance: %s" % fs_data.data.std()**2 - # Whitening (FIXME: Whiten the filters, not the data) - fs_data.data /= numpy.sqrt(fd_psd) / numpy.sqrt(2 * fd_psd.delta_f) - print tprint(t0,t1),"Whitened frequency series data has variance: %s" % fs_data.data.std()**2 - -Create time-frequency map for each block ----------------------------------------- - -We initialise a 2D zero array for a time-frequency map (``tf_map``) which will be computed for each frequency-domain filter associated to each PSD segment and where the filtered time-series for each frequency channels will be stored. The number of rows corresponds to the total number of frequency channels which is defined by the ``nchans`` variable. The number of columns corresponds to the segment length in samples (i.e. the number of samples covering one segment) which is defined by the ``seg_len`` variable. :: - - # Initialise 2D zero array for time-frequency map - tf_map = numpy.zeros((nchans, seg_len), dtype=numpy.complex128) - -We also initialise a zero vector for a temporary filter bank (``tmp_filter_bank``) that will store, for a given channel, the filter's values from the original filter bank (``filter_bank``) for that channel only. The length of the temporary filter bank is equal to the length of the PSD frequency series (``fd_psd``). :: - - # Initialise 1D zero array - tmp_filter_bank = numpy.zeros(len(fd_psd), dtype=numpy.complex128) - -We then loop over all the frequency channels. While in the loop, we first re-initialise the temporary filter bank with zero values everywhere along the frequency series. We then determine the first and last frequency of each channel and re-define the values of the filter in that frequency range based on the values from the original channel's filter from the original filter bank. :: - - # Loop over all the channels - print tprint(t0,t1),"Filtering all %d channels..." % nchans - for i in range(nchans): - # Reset filter bank series - tmp_filter_bank *= 0.0 - # Index of starting frequency - f1 = int(filter_bank[i].f0/fd_psd.delta_f) - # Index of ending frequency - f2 = int((filter_bank[i].f0 + 2*band)/fd_psd.delta_f)+1 - # (FIXME: Why is there a factor of 2 here?) - tmp_filter_bank[f1:f2] = filter_bank[i].data.data * 2 - -We then extract the frequency series from the filter bank for that channel, which will be used as a template waveform to filter the actual data from the channel. :: - - # Define the template to filter the frequency series with - template = types.FrequencySeries(tmp_filter_bank, delta_f=fd_psd.delta_f, copy=False) - -Finally, we use the `matched_filter_core `_ module from the ``pycbc.filter.matchedfilter`` library to filter the frequency series from the channel. This will return both a time series containing the complex signal-to-noise matched filtered against the data, and a frequency series containing the correlation vector. :: - - # Create filtered series - filtered_series = filter.matched_filter_core(template,fs_data,h_norm=None,psd=None, - low_frequency_cutoff=filter_bank[i].f0, - high_frequency_cutoff=filter_bank[i].f0+2*band) - -The `matched filter `_ is the optimal linear filter for maximizing the signal to noise ratio (SNR) in the presence of additive stochastic noise. The filtered time series is stored in the time-frequency map and can be used to produce a spectrogram of the segment of data being analysed. :: - - # Include filtered series in the map - tf_map[i,:] = filtered_series[0].numpy() - -The time-frequency map is a 2D array with a length that corresponds to the number of channels and a width equal to the number of sample present in one segment of data, i.e. segment's length in seconds times the the sampling rate. The map can finally be plotted with a :math:`\Delta t` corresponding to the sampling period of the original dataset (i.e. inverse of the original sampling rate), and :math:`\Delta f` is equal to the bandwidth of one channel. :: - - plot_spectrogram(numpy.abs(tf_map).T,tmp_ts_data.delta_t,fd_psd.delta_f,ts_data.sample_rate,start_time,end_time,fname='%s/tf.png'%(segfolder)) - -.. _tilebandwidth: - -Constructing tiles of different bandwidth ------------------------------------------ - -First and foremost, we define a clipping region in the data to be used to remove window corruption, this is non-zero if the ``window_fraction`` variable is set to a non-zero value. :: - - print tprint(t0,t1),"Beginning tile construction..." - # Clip the boundaries to remove window corruption - clip_samples = int(args.psd_segment_length * window_fraction * args.sample_rate / 2) - -In order to perform a multi-resolution search, tiles of many different bandwidths and durations will be scanned. We first need to setup a loop such that the maximum number of additional channel is equal to the base 2 logarithm of the total number of channels. The number of narrow band channels to be summed (``nc_sum``) would therefore be equal to 2 to the power of the current quantity of additional channels. :: - - for nc_sum in range(0, int(math.log(nchans, 2)))[::-1]: # nc_sum additional channel adds - nc_sum = 2**nc_sum - 1 - print tprint(t0,t1,t2),"Summing %d narrow band channels..." % (nc_sum+1) - -The undersampling rate for this tile can be calculated using the channel frequency band and the number of narrow band channels to be summed such that the bandwidth of the tile is equal to ``band * (nc_sum + 1)``. :: - - us_rate = int(round(1.0 / (2 * band*(nc_sum+1) * ts_data.delta_t))) - print >>sys.stderr, "Undersampling rate for this level: %f" % (args.sample_rate/us_rate) - -"Virtual" wide bandwidth channels are constructed by summing the samples from multiple channels, and correcting for the overlap between adjacent channel filters. We then define the normalised channel at the current level and create a time frequency map for this tile using the :ref:`make_indp_tiles ` internal function. In other word, we are constructing multiple sub-tiles for which we can determined the respective energy in the given frequency band. :: - - mu_sq = mu_sq_dict[nc_sum] - sys.stderr.write("\t...calculating tiles...") - if clip_samples > 0: - tiles = make_indp_tiles(tf_map[:,clip_samples:-clip_samples:us_rate], nc_sum, mu_sq) - else: - tiles = make_indp_tiles(tf_map[:,::us_rate], nc_sum, mu_sq) - sys.stderr.write(" TF-plane is %dx%s samples... " % tiles.shape) - print >>sys.stderr, " done" - print "Tile energy mean: %f, var %f" % (numpy.mean(tiles), numpy.var(tiles)) - -.. _tileduration: - -Explore multiple tile durations -------------------------------- - -Now that we create a tile with a specific bandwidth, we can start exploring different durations for the tile. We will start checking if the user manually defined a value for the longest duration tile to compute, which can be done using the ``--max-duration`` argument. If not, the value will be set to 32. :: - - if args.max_duration is not None: - max_dof = 2 * args.max_duration * (band * (nc_sum+1)) - else: - max_dof = 32 - assert max_dof >= 2 - -Since we produce (initially) tiles with 1 degree of freedom, the duration goes as one over twice the bandwidth. :: - - print "\t\t...getting longer durations..." - #for j in [2**l for l in xrange(1, int(math.log(max_dof, 2))+1)]: - for j in [2**l for l in xrange(0, int(math.log(max_dof, 2)))]: - sys.stderr.write("\t\tSumming DOF = %d ..." % (2*j)) - #tlen = tiles.shape[1] - j + 1 - tlen = tiles.shape[1] - 2*j + 1 + 1 - if tlen <= 0: - print >>sys.stderr, " ...not enough samples." - continue - dof_tiles = numpy.zeros((tiles.shape[0], tlen)) - #:sum_filter = numpy.ones(j) - # FIXME: This is the correct filter for 50% overlap - sum_filter = numpy.array([1,0] * (j-1) + [1]) - #sum_filter = numpy.array([1,0] * int(math.log(j, 2)-1) + [1]) - for f in range(tiles.shape[0]): - # Sum and drop correlate tiles - # FIXME: don't drop correlated tiles - #output = numpy.convolve(tiles[f,:], sum_filter, 'valid') - dof_tiles[f] = fftconvolve(tiles[f], sum_filter, 'valid') - print >>sys.stderr, " done" - print "Summed tile energy mean: %f, var %f" % (numpy.mean(dof_tiles), numpy.var(dof_tiles)) - level_tdiff = time.time() - tdiff - print >>sys.stderr, "Done with this resolution, total %f" % level_tdiff - -Finally, the bandwidth and duration of the tile can be defined as followed: :: - - # Current bandwidth of the time-frequency map tiles - current_band = band * (nc_sum + 1) - # How much each "step" is in the frequency domain -- almost - # assuredly the fundamental bandwidth - df = current_band - # How much each "step" is in the time domain -- under sampling rate - # FIXME: THis won't work if the sample rate isn't a power of 2 - dt = 1.0 / 2 / (2 * current_band) * 2 - full_band = 250 - dt = current_band / full_band * ts_data.sample_rate - dt = 1.0/dt - # Duration is fixed by the NDOF and bandwidth - duration = j / 2.0 / current_band - -.. _triggerfinding: - -Trigger finding ---------------- - -In order to find any trigger in the data, we first need to set a false alarm probability threshold in Gaussian noise above which signal will be distinguished from the noise. Such threshold can be determined by using the /inverse survival function/ method from the `scipy.stats.chi2 `_ package. :: - - threshold = scipy.stats.chi2.isf(args.tile_fap, j) - print "Threshold for this level: %f" % threshold - #if numpy.any(dof_tiles > threshold): - #plot_spectrogram(dof_tiles.T) - #import pdb; pdb.set_trace() - -Once the threshold is set, one can then run the :ref:`trigger_list_from_map ` function to quickly find the trigger signal from the ``dof_tiles`` array that :: - - # Since we clip the data, the start time needs to be adjusted accordingly - window_offset_epoch = fs_data.epoch + args.psd_segment_length * window_fraction / 2 - trigger_list_from_map(dof_tiles, event_list, threshold, window_offset_epoch, filter_bank[0].f0 + band/2, duration, current_band, df, dt, None) - for event in event_list[::-1]: - if event.amplitude != None: - continue - etime_min_idx = float(event.get_start()) - float(fs_data.epoch) - etime_min_idx = int(etime_min_idx / tmp_ts_data.delta_t) - etime_max_idx = float(event.get_start()) - float(fs_data.epoch) + event.duration - etime_max_idx = int(etime_max_idx / tmp_ts_data.delta_t) - # (band / 2) to account for sin^2 wings from finest filters - flow_idx = int((event.central_freq - event.bandwidth / 2 - (band / 2) - flow) / band) - fhigh_idx = int((event.central_freq + event.bandwidth / 2 + (band / 2) - flow) / band) - # TODO: Check that the undersampling rate is always commensurate - # with the indexing: that is to say that - # mod(etime_min_idx, us_rate) == 0 always - z_j_b = tf_map[flow_idx:fhigh_idx,etime_min_idx:etime_max_idx:us_rate] - # FIXME: Deal with negative hrss^2 -- e.g. remove the event - try: - event.amplitude = measure_hrss(z_j_b, unwhite_filter_ip[flow_idx:fhigh_idx], unwhite_ss_ip[flow_idx:fhigh_idx-1], white_ss_ip[flow_idx:fhigh_idx-1], fd_psd.delta_f, tmp_ts_data.delta_t, len(filter_bank[0].data.data), event.chisq_dof) - except ValueError: - event.amplitude = 0 - - print "Total number of events: %d" % len(event_list) - -Switch to new block -------------------- - -The following will move the frequency band to the next segment: :: - - tdiff = time.time() - tdiff - print "Done with this block: total %f" % tdiff - - t_idx_min += int(seg_len * (1 - window_fraction)) - t_idx_max += int(seg_len * (1 - window_fraction)) - -Extracting GPS time range -------------------------- - -We use the `LIGOTimeGPS `_ structure from the =glue.lal= package to /store the starting and ending time in the dataset to nanosecond precision and synchronized to the Global Positioning System time reference/. Once both times are defined, the range of value is stored in a semi-open interval using the `segment `_ module from the =glue.segments= package. :: - - # Starting epoch relative to GPS starting epoch - start_time = LIGOTimeGPS(args.analysis_start_time or args.gps_start_time) - # Ending epoch relative to GPS ending epoch - end_time = LIGOTimeGPS(args.analysis_end_time or args.gps_end_time) - # Represent the range of values in the semi-open interval - inseg = segment(start_time,end_time) - -Prepare output file for given time range ----------------------------------------- - -:: - - xmldoc = ligolw.Document() - xmldoc.appendChild(ligolw.LIGO_LW()) - - ifo = args.channel_name.split(":")[0] - proc_row = register_to_xmldoc(xmldoc, __program__, args.__dict__, ifos=[ifo],version=glue.git_version.id, cvs_repository=glue.git_version.branch, cvs_entry_time=glue.git_version.date) - - # Figure out the data we actually analyzed - outseg = determine_output_segment(inseg, args.psd_segment_length, args.sample_rate, window_fraction) - - ss = append_search_summary(xmldoc, proc_row, ifos=(station,), inseg=inseg, outseg=outseg) - - for sb in event_list: - sb.process_id = proc_row.process_id - sb.search = proc_row.program - #sb.ifo, sb.channel = args.channel_name.split(":") - sb.ifo, sb.channel = station, setname - - xmldoc.childNodes[0].appendChild(event_list) - fname = make_filename(station, inseg) - - utils.write_filename(xmldoc, fname, gz=fname.endswith("gz"), verbose=True) - -Plot trigger results --------------------- - -:: - - events = SnglBurstTable.read(fname+'.gz') - #del events[10000:] - plot = events.plot('time', 'central_freq', "duration", "bandwidth", color="snr") - #plot = events.plot('time', 'central_freq', color='snr') - #plot.set_yscale("log") - plot.set_ylim(1e-0, 250) - t0 = 1153742417 - plot.set_xlim(t0 + 0*60, t0 + 1*60) - #plot.set_xlim(t0 + 28, t0 + 32) - pyplot.axvline(t0 + 30, color='r') - cb = plot.add_colorbar(cmap='viridis') - plot.savefig("triggers.png") - -Module Access -============= - -Extract Magnetic Field Data ---------------------------- - -Extract magnetic field data from HDF5 files. - -.. currentmodule:: gdas.retrieve - -.. autosummary:: - :toctree: generated/ - - magfield - file_to_segment - construct_utc_from_metadata - generate_timeseries - create_activity_list - retrieve_data_timeseries - retrieve_channel_data - -Plotting routines ------------------ - -Methods to produce time-frequency plots and others - -.. currentmodule:: gdas.plots - -.. autosummary:: - :toctree: generated/ - - plot_activity - plot_time_series - plot_asd - plot_whitening - plot_ts - plot_spectrum - plot_spectrogram - plot_spectrogram_from_ts - plot_triggers - -Excess Power Search Analysis ----------------------------- - -Main class to do excess-power search analysis - -.. currentmodule:: gdas.epower - -.. autosummary:: - :toctree: generated/ - - excess_power - check_filtering_settings - calculate_psd - calculate_spectral_correlation - create_filter_bank - convert_to_time_domain - identify_block - create_tf_plane - compute_filter_ips_self - compute_filter_ips_adjacent - compute_channel_renormalization - measure_hrss - measure_hrss_slowly - measure_hrss_poorly - trigger_list_from_map - determine_output_segment - make_tiles - make_indp_tiles - make_filename - construct_tiles - create_tile_duration - create_xml - -Utilities ---------- - -Independent routines to do various other things - -.. currentmodule:: gdas.utils - -.. autosummary:: - :toctree: generated/ - - create_sound - - -.. _file_to_segment: - -.. ** Extract segment information -.. -.. The starting and ending UTC times for a specific HDF5 file are determined by using the =Date=, =t0= and =t1= attributes from the metadata. The [[construct_utc_from_metadata][=construct_utc_from_metadata=]] function is then used to calculate the UTC time. Finally, the [[http://software.ligo.org/docs/glue/glue.__segments.segment-class.html][=segment=]] module from the =glue.segments= library is used to represent the range of times in a semi-open interval. -.. -.. #+BEGIN_SRC python -.. def file_to_segment(hfile,segname): -.. # Extract all atributes from the data -.. attrs = hfile[segname].attrs -.. # Define each attribute -.. dstr, t0, t1 = attrs["Date"], attrs["t0"], attrs["t1"] -.. # Construct GPS starting time from data -.. start_utc = construct_utc_from_metadata(dstr, t0) -.. # Construct GPS starting time from data -.. end_utc = construct_utc_from_metadata(dstr, t1) -.. # Represent the range of times in the semi-open interval -.. return segment(start_utc,end_utc) -.. #+END_SRC -.. -.. ** Constructing UTC from metadata -.. <> -.. -.. #+BEGIN_SRC python -.. def construct_utc_from_metadata(datestr, t0str): -.. instr = "%d-%d-%02dT" % tuple(map(int, datestr.split('/'))) -.. instr += t0str -.. t = Time(instr, format='isot', scale='utc') -.. return t.gps -.. #+END_SRC -.. -.. ** Generate time series -.. <> -.. -.. #+BEGIN_SRC python -.. def generate_timeseries(data_list, setname="MagneticFields"): -.. full_data = TimeSeriesList() -.. for seg in sorted(data_list): -.. hfile = h5py.File(data_list[seg], "r") -.. full_data.append(retrieve_data_timeseries(hfile, "MagneticFields")) -.. hfile.close() -.. return full_data -.. #+END_SRC -.. -.. ** Retrieve data time series -.. <> -.. -.. #+BEGIN_SRC python -.. def retrieve_data_timeseries(hfile, setname): -.. dset = hfile[setname] -.. sample_rate = dset.attrs["SamplingRate(Hz)"] -.. gps_epoch = construct_utc_from_metadata(dset.attrs["Date"], dset.attrs["t0"]) -.. data = retrieve_channel_data(hfile, setname) -.. ts_data = TimeSeries(data, sample_rate=sample_rate, epoch=gps_epoch) -.. return ts_data -.. #+END_SRC -.. -.. ** Retrieve channel data -.. <> -.. -.. #+BEGIN_SRC python -.. def retrieve_channel_data(hfile, setname): -.. return hfile[setname][:] -.. #+END_SRC -.. -.. .. _calculate_spectral_correlation: -.. -.. ** Two point spectral correlation -.. -.. For our data, we apply a Tukey window whose flat bit corresponds to =window_fraction= (in percentage) of the segment length (in samples) used for PSD estimation (i.e. =fft_window_len=). This can be done by using the [[http://software.ligo.org/docs/lalsuite/lal/_window_8c_source.html#l00597][=CreateTukeyREAL8Window=]] module from the =lal= library. -.. -.. #+BEGIN_SRC python -.. def calculate_spectral_correlation(fft_window_len, wtype='hann', window_fraction=None): -.. if wtype == 'hann': -.. window = lal.CreateHannREAL8Window(fft_window_len) -.. elif wtype == 'tukey': -.. window = lal.CreateTukeyREAL8Window(fft_window_len, window_fraction) -.. else: -.. raise ValueError("Can't handle window type %s" % wtype) -.. #+END_SRC -.. -.. Once the window is built, a new frequency plan is created which will help performing a [[http://fourier.eng.hmc.edu/e101/lectures/fourier_transform_d/node1.html][forward transform]] on the data. This is done with the [[http://software.ligo.org/docs/lalsuite/lal/group___real_f_f_t__h.html#gac4413752db2d19cbe48742e922670af4][=CreateForwardREAL8FFTPlan=]] module which takes as argument the total number of points in the real data and the measurement level for plan creation (here 1 stands for measuring the best plan). -.. -.. #+BEGIN_SRC python -.. fft_plan = lal.CreateForwardREAL8FFTPlan(len(window.data.data), 1) -.. #+END_SRC -.. -.. We can finally compute and return the two-point spectral correlation function for the whitened frequency series (=fft_plan=) from the window applied to the original time series using the [[http://software.ligo.org/docs/lalsuite/lal/group___time_freq_f_f_t__h.html#ga2bd5c4258eff57cc80103d2ed489e076][=REAL8WindowTwoPointSpectralCorrelation=]] module. -.. -.. #+BEGIN_SRC python -.. return window, lal.REAL8WindowTwoPointSpectralCorrelation(window, fft_plan) -.. #+END_SRC -.. -.. ** Create filter bank -.. <> -.. -.. The construction of a filter bank is fairly simple. For each channel, a frequency domain channel filter function will be created using the [[http://software.ligo.org/docs/lalsuite/lalburst/group___e_p_search__h.html#ga899990cbd45111ba907772650c265ec9][=CreateExcessPowerFilter=]] module from the =lalburst= package. Each channel filter is divided by the square root of the PSD frequency series prior to normalization, which has the effect of de-emphasizing frequency bins with high noise content, and is called "over whitening". The data and metadata are finally stored in the =filter_fseries= and =filter_bank= arrays respectively. Finally, we store on a final array, called =np_filters= the all time-series generated from each filter so that we can plot them afterwards -.. -.. #+BEGIN_SRC python -.. def create_filter_bank(delta_f, flow, band, nchan, psd, spec_corr): -.. lal_psd = psd.lal() -.. lal_filters, np_filters = [],[] -.. for i in range(nchan): -.. lal_filter = lalburst.CreateExcessPowerFilter(flow + i*band, band, lal_psd, spec_corr) -.. np_filters.append(Spectrum.from_lal(lal_filter)) -.. lal_filters.append(lal_filter) -.. return filter_fseries, lal_filters, np_filters -.. #+END_SRC -.. -.. ** Compute filter inner products with themselves -.. <> -.. #+BEGIN_SRC python -.. def compute_filter_ips_self(lal_filters, spec_corr, psd=None): -.. """ -.. Compute a set of inner products of input filters with themselves. If psd -.. argument is given, the unwhitened filter inner products will be returned. -.. """ -.. return numpy.array([lalburst.ExcessPowerFilterInnerProduct(f, f, spec_corr, psd) for f in lal_filters]) -.. #+END_SRC -.. -.. ** Compute filter inner products with adjecant filters -.. <> -.. -.. #+BEGIN_SRC python -.. def compute_filter_ips_adjacent(lal_filters, spec_corr, psd=None): -.. """ -.. Compute a set of filter inner products between input adjacent filters. -.. If psd argument is given, the unwhitened filter inner products will be -.. returned. The returned array index is the inner product between the -.. lal_filter of the same index, and its (array) adjacent filter --- assumed -.. to be the frequency adjacent filter. -.. """ -.. return numpy.array([lalburst.ExcessPowerFilterInnerProduct(f1, f2, spec_corr, psd) for f1, f2 in zip(lal_filters[:-1], lal_filters[1:])]) -.. #+END_SRC -.. -.. .. _compute_channel_renomalization: -.. -.. Compute channel renormalization -.. ------------------------------- -.. -.. Compute the renormalization for the base filters up to a given bandwidth. -.. -.. #+BEGIN_SRC python -.. def compute_channel_renomalization(nc_sum, lal_filters, spec_corr, nchans, verbose=True): -.. mu_sq = (nc_sum+1)*numpy.array([lalburst.ExcessPowerFilterInnerProduct(f, f, spec_corr, None) for f in lal_filters]) -.. # Uncomment to get all possible frequency renormalizations -.. #for n in xrange(nc_sum, nchans): # channel position index -.. for n in xrange(nc_sum, nchans, nc_sum+1): # channel position index -.. for k in xrange(0, nc_sum): # channel sum index -.. # FIXME: We've precomputed this, so use it instead -.. mu_sq[n] += 2*lalburst.ExcessPowerFilterInnerProduct(lal_filters[n-k], lal_filters[n-1-k], spec_corr, None) -.. #print mu_sq[nc_sum::nc_sum+1] -.. return mu_sq -.. #+END_SRC -.. -.. ** Measure root-sum-square strain (hrss) -.. <> -.. -.. #+BEGIN_SRC python -.. def measure_hrss(z_j_b, uw_ss_ii, uw_ss_ij, w_ss_ij, delta_f, delta_t, filter_len, dof): -.. """ -.. Approximation of unwhitened sum of squares signal energy in a given EP tile. -.. See T1200125 for equation number reference. -.. z_j_b - time frequency map block which the constructed tile covers -.. uw_ss_ii - unwhitened filter inner products -.. uw_ss_ij - unwhitened adjacent filter inner products -.. w_ss_ij - whitened adjacent filter inner products -.. delta_f - frequency binning of EP filters -.. delta_t - native time resolution of the time frequency map -.. filter_len - number of samples in a fitler -.. dof - degrees of freedom in the tile (twice the time-frequency area) -.. """ -.. s_j_b_avg = uw_ss_ii * delta_f / 2 -.. # unwhitened sum of squares of wide virtual filter -.. s_j_nb_avg = uw_ss_ii.sum() / 2 + uw_ss_ij.sum() -.. s_j_nb_avg *= delta_f -.. s_j_nb_denom = s_j_b_avg.sum() + 2 * 2 / filter_len * \ -.. numpy.sum(numpy.sqrt(s_j_b_avg[:-1] * s_j_b_avg[1:]) * w_ss_ij) -.. # eqn. 62 -.. uw_ups_ratio = s_j_nb_avg / s_j_nb_denom -.. # eqn. 63 -- approximation of unwhitened signal energy time series -.. # FIXME: The sum in this equation is over nothing, but indexed by frequency -.. # I'll make that assumption here too. -.. s_j_nb = numpy.sum(z_j_b.T * numpy.sqrt(s_j_b_avg), axis=0) -.. s_j_nb *= numpy.sqrt(uw_ups_ratio / filter_len * 2) -.. # eqn. 64 -- approximate unwhitened signal energy minus noise contribution -.. # FIXME: correct axis of summation? -.. return math.sqrt(numpy.sum(numpy.absolute(s_j_nb)**2) * delta_t - s_j_nb_avg * dof * delta_t) -.. #+END_SRC -.. -.. ** Unwhitened inner products filtering -.. <> -.. -.. #+BEGIN_SRC python -.. # < s^2_j(f_1, b) > = 1 / 2 / N * \delta_t EPIP{\Theta, \Theta; P} -.. def uw_sum_sq(filter1, filter2, spec_corr, psd): -.. return lalburst.ExcessPowerFilterInnerProduct(filter1, filter2, spec_corr, psd) -.. #+END_SRC -.. -.. ** Unwhitened sum of squares signal -.. <> -.. -.. #+BEGIN_SRC python -.. def measure_hrss_slowly(z_j_b, lal_filters, spec_corr, psd, delta_t, dof): -.. """ -.. Approximation of unwhitened sum of squares signal energy in a given EP tile. -.. See T1200125 for equation number reference. NOTE: This function is deprecated -.. in favor of measure_hrss, since it requires recomputation of many inner products, -.. making it particularly slow. -.. """ -.. # FIXME: Make sure you sum in time correctly -.. # Number of finest bands in given tile -.. nb = len(z_j_b) -.. # eqn. 56 -- unwhitened mean square of filter with itself -.. uw_ss_ii = numpy.array([uw_sum_sq(lal_filters[i], lal_filters[i], spec_corr, psd) for i in range(nb)]) -.. s_j_b_avg = uw_ss_ii * lal_filters[0].deltaF / 2 -.. # eqn. 57 -- unwhitened mean square of filter with adjacent filter -.. uw_ss_ij = numpy.array([uw_sum_sq(lal_filters[i], lal_filters[i+1], spec_corr, psd) for i in range(nb-1)]) -.. # unwhitened sum of squares of wide virtual filter -.. s_j_nb_avg = uw_ss_ii.sum() / 2 + uw_ss_ij.sum() -.. s_j_nb_avg *= lal_filters[0].deltaF -.. -.. # eqn. 61 -.. w_ss_ij = numpy.array([uw_sum_sq(lal_filters[i], lal_filters[i+1], spec_corr, None) for i in range(nb-1)]) -.. s_j_nb_denom = s_j_b_avg.sum() + 2 * 2 / len(lal_filters[0].data.data) * \ -.. (numpy.sqrt(s_j_b_avg[:-1] * s_j_b_avg[1:]) * w_ss_ij).sum() -.. -.. # eqn. 62 -.. uw_ups_ratio = s_j_nb_avg / s_j_nb_denom -.. -.. # eqn. 63 -- approximation of unwhitened signal energy time series -.. # FIXME: The sum in this equation is over nothing, but indexed by frequency -.. # I'll make that assumption here too. -.. s_j_nb = numpy.sum(z_j_b.T * numpy.sqrt(s_j_b_avg), axis=0) -.. s_j_nb *= numpy.sqrt(uw_ups_ratio / len(lal_filters[0].data.data) * 2) -.. # eqn. 64 -- approximate unwhitened signal energy minus noise contribution -.. # FIXME: correct axis of summation? -.. return math.sqrt((numpy.absolute(s_j_nb)**2).sum() * delta_t - s_j_nb_avg * dof * delta_t) -.. #+END_SRC -.. -.. ** Measure root-mean square strain poorly -.. <> -.. -.. #+BEGIN_SRC python -.. def measure_hrss_poorly(tile_energy, sub_psd): -.. return math.sqrt(tile_energy / numpy.average(1.0 / sub_psd) / 2) -.. #+END_SRC -.. -.. ** List triggers from map -.. <> -.. -.. #+BEGIN_SRC python -.. def trigger_list_from_map(tfmap, event_list, threshold, start_time, start_freq, duration, band, df, dt, psd=None): -.. -.. # FIXME: If we don't convert this the calculation takes forever --- but we should convert it once and handle deltaF better later -.. if psd is not None: -.. npy_psd = psd.numpy() -.. -.. start_time = LIGOTimeGPS(float(start_time)) -.. ndof = 2 * duration * band -.. -.. spanf, spant = tfmap.shape[0] * df, tfmap.shape[1] * dt -.. print "Processing %.2fx%.2f time-frequency map." % (spant, spanf) -.. -.. for i, j in zip(*numpy.where(tfmap > threshold)): -.. event = event_list.RowType() -.. -.. # The points are summed forward in time and thus a `summed point' is the -.. # sum of the previous N points. If this point is above threshold, it -.. # corresponds to a tile which spans the previous N points. However, th -.. # 0th point (due to the convolution specifier 'valid') is actually -.. # already a duration from the start time. All of this means, the + -.. # duration and the - duration cancels, and the tile 'start' is, by -.. # definition, the start of the time frequency map if j = 0 -.. # FIXME: I think this needs a + dt/2 to center the tile properly -.. event.set_start(start_time + float(j * dt)) -.. event.set_stop(start_time + float(j * dt) + duration) -.. event.set_peak(event.get_start() + duration / 2) -.. event.central_freq = start_freq + i * df + 0.5 * band -.. -.. event.duration = duration -.. event.bandwidth = band -.. event.chisq_dof = ndof -.. -.. event.snr = math.sqrt(tfmap[i,j] / event.chisq_dof - 1) -.. # FIXME: Magic number 0.62 should be determine empircally -.. event.confidence = -lal.LogChisqCCDF(event.snr * 0.62, event.chisq_dof * 0.62) -.. if psd is not None: -.. # NOTE: I think the pycbc PSDs always start at 0 Hz --- check -.. psd_idx_min = int((event.central_freq - event.bandwidth / 2) / psd.delta_f) -.. psd_idx_max = int((event.central_freq + event.bandwidth / 2) / psd.delta_f) -.. -.. # FIXME: heuristically this works better with E - D -- it's all -.. # going away with the better h_rss calculation soon anyway -.. event.amplitude = measure_hrss_poorly(tfmap[i,j] - event.chisq_dof, npy_psd[psd_idx_min:psd_idx_max]) -.. else: -.. event.amplitude = None -.. -.. event.process_id = None -.. event.event_id = event_list.get_next_id() -.. event_list.append(event) -.. #+END_SRC -.. -.. ** Determine output segment -.. <> -.. -.. #+BEGIN_SRC python -.. def determine_output_segment(inseg, dt_stride, sample_rate, window_fraction=0.0): -.. """ -.. Given an input data stretch segment inseg, a data block stride dt_stride, the data sample rate, and an optional window_fraction, return the amount of data that can be processed without corruption effects from the window. -.. -.. If window_fration is set to 0 (default), assume no windowing. -.. """ -.. # Amount to overlap successive blocks so as not to lose data -.. window_overlap_samples = window_fraction * sample_rate -.. outseg = inseg.contract(window_fraction * dt_stride / 2) -.. -.. # With a given dt_stride, we cannot process the remainder of this data -.. remainder = math.fmod(abs(outseg), dt_stride * (1 - window_fraction)) -.. # ...so make an accounting of it -.. outseg = segment(outseg[0], outseg[1] - remainder) -.. return outseg -.. #+END_SRC -.. -.. ** Make tiles -.. <> -.. -.. #+BEGIN_SRC python -.. def make_tiles(tf_map, nc_sum, mu_sq): -.. tiles = numpy.zeros(tf_map.shape) -.. sum_filter = numpy.ones(nc_sum+1) -.. # Here's the deal: we're going to keep only the valid output and -.. # it's *always* going to exist in the lowest available indices -.. for t in xrange(tf_map.shape[1]): -.. # Sum and drop correlate tiles -.. # FIXME: don't drop correlated tiles -.. output = numpy.convolve(tf_map[:,t], sum_filter, 'valid')[::nc_sum+1] -.. #output = fftconvolve(tf_map[:,t], sum_filter, 'valid')[::nc_sum+1] -.. tiles[:len(output),t] = numpy.absolute(output) / math.sqrt(2) -.. return tiles[:len(output)]**2 / mu_sq[nc_sum::nc_sum+1].reshape(-1, 1) -.. #+END_SRC -.. -.. ** Create a time frequency map -.. <> -.. -.. In this function, we create a time frequency map with resolution similar than =tf_map= but rescale by a factor of =nc_sum= + 1. All tiles will be independent up to overlap from the original tiling. The =mu_sq= is applied to the resulting addition to normalize the outputs to be zero-mean unit-variance Gaussian variables (if the input is Gaussian). -.. -.. #+BEGIN_SRC python -.. def make_indp_tiles(tf_map, nc_sum, mu_sq): -.. tiles = tf_map.copy() -.. # Here's the deal: we're going to keep only the valid output and -.. # it's *always* going to exist in the lowest available indices -.. stride = nc_sum + 1 -.. for i in xrange(tiles.shape[0]/stride): -.. numpy.absolute(tiles[stride*i:stride*(i+1)].sum(axis=0), tiles[stride*(i+1)-1]) -.. return tiles[nc_sum::nc_sum+1].real**2 / mu_sq[nc_sum::nc_sum+1].reshape(-1, 1) -.. #+END_SRC -.. -.. ** Create output filename -.. <> -.. -.. #+BEGIN_SRC python -.. def make_filename(ifo, seg, tag="excesspower", ext="xml.gz"): -.. if isinstance(ifo, str): -.. ifostr = ifo -.. else: -.. ifostr = "".join(ifo) -.. st_rnd, end_rnd = int(math.floor(seg[0])), int(math.ceil(seg[1])) -.. dur = end_rnd - st_rnd -.. return "%s-%s-%d-%d.%s" % (ifostr, tag, st_rnd, dur, ext) -.. #+END_SRC - diff --git a/docs/_sources/backup/excess_power.rst.txt b/docs/_sources/backup/excess_power.rst.txt deleted file mode 100644 index 8f18ff6..0000000 --- a/docs/_sources/backup/excess_power.rst.txt +++ /dev/null @@ -1,697 +0,0 @@ -.. _analysingblocks: - -Define analysing blocks ------------------------ - -The first thing we do is to calculate the time series for the segment that is covered (``tmp_ts_data``) and redefined the metadata, especially the time of the first sample in seconds which is defined by the ``epoch`` argument and is different for every segment. After plotting the time series for that segment, the data are then converted into frequency series (``fs_data``) using the `to_frequencyseries `_ module from the ``pycbc.types.timeseries.TimeSeries`` library. Finally, the frequency data are then whitened. :: - - # Loop over each data within the user requested time period - while t_idx_max <= t_idx_max_off: - # Define starting and ending time of the segment in seconds - start_time = ts_data.start_time + t_idx_min/float(args.sample_rate) - end_time = ts_data.start_time + t_idx_max/float(args.sample_rate) - print tprint(t0,t1),"Analyzing block %i to %i (%.2f percent)"%(start_time,end_time,100*float(t_idx_max)/float(idx_max_off)) - # Model a withen time series for the block - tmp_ts_data = types.TimeSeries(ts_data[t_idx_min:t_idx_max]*window, 1.0/args.sample_rate,epoch=start_time) - # Save time series in segment repository - segfolder = 'segments/%i-%i'%(start_time,end_time) - os.system('mkdir -p '+segfolder) - plot_ts(tmp_ts_data,fname='%s/ts.png'%(segfolder)) - # Convert times series to frequency series - fs_data = tmp_ts_data.to_frequencyseries() - print tprint(t0,t1),"Frequency series data has variance: %s" % fs_data.data.std()**2 - # Whitening (FIXME: Whiten the filters, not the data) - fs_data.data /= numpy.sqrt(fd_psd) / numpy.sqrt(2 * fd_psd.delta_f) - print tprint(t0,t1),"Whitened frequency series data has variance: %s" % fs_data.data.std()**2 - -Create time-frequency map for each block ----------------------------------------- - -We initialise a 2D zero array for a time-frequency map (``tf_map``) which will be computed for each frequency-domain filter associated to each PSD segment and where the filtered time-series for each frequency channels will be stored. The number of rows corresponds to the total number of frequency channels which is defined by the ``nchans`` variable. The number of columns corresponds to the segment length in samples (i.e. the number of samples covering one segment) which is defined by the ``seg_len`` variable. :: - - # Initialise 2D zero array for time-frequency map - tf_map = numpy.zeros((nchans, seg_len), dtype=numpy.complex128) - -We also initialise a zero vector for a temporary filter bank (``tmp_filter_bank``) that will store, for a given channel, the filter's values from the original filter bank (``filter_bank``) for that channel only. The length of the temporary filter bank is equal to the length of the PSD frequency series (``fd_psd``). :: - - # Initialise 1D zero array - tmp_filter_bank = numpy.zeros(len(fd_psd), dtype=numpy.complex128) - -We then loop over all the frequency channels. While in the loop, we first re-initialise the temporary filter bank with zero values everywhere along the frequency series. We then determine the first and last frequency of each channel and re-define the values of the filter in that frequency range based on the values from the original channel's filter from the original filter bank. :: - - # Loop over all the channels - print tprint(t0,t1),"Filtering all %d channels..." % nchans - for i in range(nchans): - # Reset filter bank series - tmp_filter_bank *= 0.0 - # Index of starting frequency - f1 = int(filter_bank[i].f0/fd_psd.delta_f) - # Index of ending frequency - f2 = int((filter_bank[i].f0 + 2*band)/fd_psd.delta_f)+1 - # (FIXME: Why is there a factor of 2 here?) - tmp_filter_bank[f1:f2] = filter_bank[i].data.data * 2 - -We then extract the frequency series from the filter bank for that channel, which will be used as a template waveform to filter the actual data from the channel. :: - - # Define the template to filter the frequency series with - template = types.FrequencySeries(tmp_filter_bank, delta_f=fd_psd.delta_f, copy=False) - -Finally, we use the `matched_filter_core `_ module from the ``pycbc.filter.matchedfilter`` library to filter the frequency series from the channel. This will return both a time series containing the complex signal-to-noise matched filtered against the data, and a frequency series containing the correlation vector. :: - - # Create filtered series - filtered_series = filter.matched_filter_core(template,fs_data,h_norm=None,psd=None, - low_frequency_cutoff=filter_bank[i].f0, - high_frequency_cutoff=filter_bank[i].f0+2*band) - -The `matched filter `_ is the optimal linear filter for maximizing the signal to noise ratio (SNR) in the presence of additive stochastic noise. The filtered time series is stored in the time-frequency map and can be used to produce a spectrogram of the segment of data being analysed. :: - - # Include filtered series in the map - tf_map[i,:] = filtered_series[0].numpy() - -The time-frequency map is a 2D array with a length that corresponds to the number of channels and a width equal to the number of sample present in one segment of data, i.e. segment's length in seconds times the the sampling rate. The map can finally be plotted with a :math:`\Delta t` corresponding to the sampling period of the original dataset (i.e. inverse of the original sampling rate), and :math:`\Delta f` is equal to the bandwidth of one channel. :: - - plot_spectrogram(numpy.abs(tf_map).T,tmp_ts_data.delta_t,fd_psd.delta_f,ts_data.sample_rate,start_time,end_time,fname='%s/tf.png'%(segfolder)) - -.. _tilebandwidth: - -Constructing tiles of different bandwidth ------------------------------------------ - -First and foremost, we define a clipping region in the data to be used to remove window corruption, this is non-zero if the ``window_fraction`` variable is set to a non-zero value. :: - - print tprint(t0,t1),"Beginning tile construction..." - # Clip the boundaries to remove window corruption - clip_samples = int(args.psd_segment_length * window_fraction * args.sample_rate / 2) - -In order to perform a multi-resolution search, tiles of many different bandwidths and durations will be scanned. We first need to setup a loop such that the maximum number of additional channel is equal to the base 2 logarithm of the total number of channels. The number of narrow band channels to be summed (``nc_sum``) would therefore be equal to 2 to the power of the current quantity of additional channels. :: - - for nc_sum in range(0, int(math.log(nchans, 2)))[::-1]: # nc_sum additional channel adds - nc_sum = 2**nc_sum - 1 - print tprint(t0,t1,t2),"Summing %d narrow band channels..." % (nc_sum+1) - -The undersampling rate for this tile can be calculated using the channel frequency band and the number of narrow band channels to be summed such that the bandwidth of the tile is equal to ``band * (nc_sum + 1)``. :: - - us_rate = int(round(1.0 / (2 * band*(nc_sum+1) * ts_data.delta_t))) - print >>sys.stderr, "Undersampling rate for this level: %f" % (args.sample_rate/us_rate) - -"Virtual" wide bandwidth channels are constructed by summing the samples from multiple channels, and correcting for the overlap between adjacent channel filters. We then define the normalised channel at the current level and create a time frequency map for this tile using the :ref:`make_indp_tiles ` internal function. In other word, we are constructing multiple sub-tiles for which we can determined the respective energy in the given frequency band. :: - - mu_sq = mu_sq_dict[nc_sum] - sys.stderr.write("\t...calculating tiles...") - if clip_samples > 0: - tiles = make_indp_tiles(tf_map[:,clip_samples:-clip_samples:us_rate], nc_sum, mu_sq) - else: - tiles = make_indp_tiles(tf_map[:,::us_rate], nc_sum, mu_sq) - sys.stderr.write(" TF-plane is %dx%s samples... " % tiles.shape) - print >>sys.stderr, " done" - print "Tile energy mean: %f, var %f" % (numpy.mean(tiles), numpy.var(tiles)) - -.. _tileduration: - -Explore multiple tile durations -------------------------------- - -Now that we create a tile with a specific bandwidth, we can start exploring different durations for the tile. We will start checking if the user manually defined a value for the longest duration tile to compute, which can be done using the ``--max-duration`` argument. If not, the value will be set to 32. :: - - if args.max_duration is not None: - max_dof = 2 * args.max_duration * (band * (nc_sum+1)) - else: - max_dof = 32 - assert max_dof >= 2 - -Since we produce (initially) tiles with 1 degree of freedom, the duration goes as one over twice the bandwidth. :: - - print "\t\t...getting longer durations..." - #for j in [2**l for l in xrange(1, int(math.log(max_dof, 2))+1)]: - for j in [2**l for l in xrange(0, int(math.log(max_dof, 2)))]: - sys.stderr.write("\t\tSumming DOF = %d ..." % (2*j)) - #tlen = tiles.shape[1] - j + 1 - tlen = tiles.shape[1] - 2*j + 1 + 1 - if tlen <= 0: - print >>sys.stderr, " ...not enough samples." - continue - dof_tiles = numpy.zeros((tiles.shape[0], tlen)) - #:sum_filter = numpy.ones(j) - # FIXME: This is the correct filter for 50% overlap - sum_filter = numpy.array([1,0] * (j-1) + [1]) - #sum_filter = numpy.array([1,0] * int(math.log(j, 2)-1) + [1]) - for f in range(tiles.shape[0]): - # Sum and drop correlate tiles - # FIXME: don't drop correlated tiles - #output = numpy.convolve(tiles[f,:], sum_filter, 'valid') - dof_tiles[f] = fftconvolve(tiles[f], sum_filter, 'valid') - print >>sys.stderr, " done" - print "Summed tile energy mean: %f, var %f" % (numpy.mean(dof_tiles), numpy.var(dof_tiles)) - level_tdiff = time.time() - tdiff - print >>sys.stderr, "Done with this resolution, total %f" % level_tdiff - -Finally, the bandwidth and duration of the tile can be defined as followed: :: - - # Current bandwidth of the time-frequency map tiles - current_band = band * (nc_sum + 1) - # How much each "step" is in the frequency domain -- almost - # assuredly the fundamental bandwidth - df = current_band - # How much each "step" is in the time domain -- under sampling rate - # FIXME: THis won't work if the sample rate isn't a power of 2 - dt = 1.0 / 2 / (2 * current_band) * 2 - full_band = 250 - dt = current_band / full_band * ts_data.sample_rate - dt = 1.0/dt - # Duration is fixed by the NDOF and bandwidth - duration = j / 2.0 / current_band - -.. _triggerfinding: - -Trigger finding ---------------- - -In order to find any trigger in the data, we first need to set a false alarm probability threshold in Gaussian noise above which signal will be distinguished from the noise. Such threshold can be determined by using the /inverse survival function/ method from the `scipy.stats.chi2 `_ package. :: - - threshold = scipy.stats.chi2.isf(args.tile_fap, j) - print "Threshold for this level: %f" % threshold - #if numpy.any(dof_tiles > threshold): - #plot_spectrogram(dof_tiles.T) - #import pdb; pdb.set_trace() - -Once the threshold is set, one can then run the :ref:`trigger_list_from_map ` function to quickly find the trigger signal from the ``dof_tiles`` array that :: - - # Since we clip the data, the start time needs to be adjusted accordingly - window_offset_epoch = fs_data.epoch + args.psd_segment_length * window_fraction / 2 - trigger_list_from_map(dof_tiles, event_list, threshold, window_offset_epoch, filter_bank[0].f0 + band/2, duration, current_band, df, dt, None) - for event in event_list[::-1]: - if event.amplitude != None: - continue - etime_min_idx = float(event.get_start()) - float(fs_data.epoch) - etime_min_idx = int(etime_min_idx / tmp_ts_data.delta_t) - etime_max_idx = float(event.get_start()) - float(fs_data.epoch) + event.duration - etime_max_idx = int(etime_max_idx / tmp_ts_data.delta_t) - # (band / 2) to account for sin^2 wings from finest filters - flow_idx = int((event.central_freq - event.bandwidth / 2 - (band / 2) - flow) / band) - fhigh_idx = int((event.central_freq + event.bandwidth / 2 + (band / 2) - flow) / band) - # TODO: Check that the undersampling rate is always commensurate - # with the indexing: that is to say that - # mod(etime_min_idx, us_rate) == 0 always - z_j_b = tf_map[flow_idx:fhigh_idx,etime_min_idx:etime_max_idx:us_rate] - # FIXME: Deal with negative hrss^2 -- e.g. remove the event - try: - event.amplitude = measure_hrss(z_j_b, unwhite_filter_ip[flow_idx:fhigh_idx], unwhite_ss_ip[flow_idx:fhigh_idx-1], white_ss_ip[flow_idx:fhigh_idx-1], fd_psd.delta_f, tmp_ts_data.delta_t, len(filter_bank[0].data.data), event.chisq_dof) - except ValueError: - event.amplitude = 0 - - print "Total number of events: %d" % len(event_list) - -Switch to new block -------------------- - -The following will move the frequency band to the next segment: :: - - tdiff = time.time() - tdiff - print "Done with this block: total %f" % tdiff - - t_idx_min += int(seg_len * (1 - window_fraction)) - t_idx_max += int(seg_len * (1 - window_fraction)) - -Extracting GPS time range -------------------------- - -We use the `LIGOTimeGPS `_ structure from the =glue.lal= package to /store the starting and ending time in the dataset to nanosecond precision and synchronized to the Global Positioning System time reference/. Once both times are defined, the range of value is stored in a semi-open interval using the `segment `_ module from the =glue.segments= package. :: - - # Starting epoch relative to GPS starting epoch - start_time = LIGOTimeGPS(args.analysis_start_time or args.gps_start_time) - # Ending epoch relative to GPS ending epoch - end_time = LIGOTimeGPS(args.analysis_end_time or args.gps_end_time) - # Represent the range of values in the semi-open interval - inseg = segment(start_time,end_time) - -Prepare output file for given time range ----------------------------------------- - -:: - - xmldoc = ligolw.Document() - xmldoc.appendChild(ligolw.LIGO_LW()) - - ifo = args.channel_name.split(":")[0] - proc_row = register_to_xmldoc(xmldoc, __program__, args.__dict__, ifos=[ifo],version=glue.git_version.id, cvs_repository=glue.git_version.branch, cvs_entry_time=glue.git_version.date) - - # Figure out the data we actually analyzed - outseg = determine_output_segment(inseg, args.psd_segment_length, args.sample_rate, window_fraction) - - ss = append_search_summary(xmldoc, proc_row, ifos=(station,), inseg=inseg, outseg=outseg) - - for sb in event_list: - sb.process_id = proc_row.process_id - sb.search = proc_row.program - #sb.ifo, sb.channel = args.channel_name.split(":") - sb.ifo, sb.channel = station, setname - - xmldoc.childNodes[0].appendChild(event_list) - fname = make_filename(station, inseg) - - utils.write_filename(xmldoc, fname, gz=fname.endswith("gz"), verbose=True) - -Plot trigger results --------------------- - -:: - - events = SnglBurstTable.read(fname+'.gz') - #del events[10000:] - plot = events.plot('time', 'central_freq', "duration", "bandwidth", color="snr") - #plot = events.plot('time', 'central_freq', color='snr') - #plot.set_yscale("log") - plot.set_ylim(1e-0, 250) - t0 = 1153742417 - plot.set_xlim(t0 + 0*60, t0 + 1*60) - #plot.set_xlim(t0 + 28, t0 + 32) - pyplot.axvline(t0 + 30, color='r') - cb = plot.add_colorbar(cmap='viridis') - plot.savefig("triggers.png") - -Module Access -============= - -Extract Magnetic Field Data ---------------------------- - -Extract magnetic field data from HDF5 files. - -.. currentmodule:: gdas.retrieve - -.. autosummary:: - :toctree: generated/ - - magfield - file_to_segment - construct_utc_from_metadata - generate_timeseries - create_activity_list - retrieve_data_timeseries - retrieve_channel_data - -Plotting routines ------------------ - -Methods to produce time-frequency plots and others - -.. currentmodule:: gdas.plots - -.. autosummary:: - :toctree: generated/ - - plot_activity - plot_time_series - plot_asd - plot_whitening - plot_ts - plot_spectrum - plot_spectrogram - plot_spectrogram_from_ts - plot_triggers - -Utilities ---------- - -Independent routines to do various other things - -.. currentmodule:: gdas.utils - -.. autosummary:: - :toctree: generated/ - - create_sound - - -.. _file_to_segment: - -.. ** Extract segment information -.. -.. The starting and ending UTC times for a specific HDF5 file are determined by using the =Date=, =t0= and =t1= attributes from the metadata. The [[construct_utc_from_metadata][=construct_utc_from_metadata=]] function is then used to calculate the UTC time. Finally, the [[http://software.ligo.org/docs/glue/glue.__segments.segment-class.html][=segment=]] module from the =glue.segments= library is used to represent the range of times in a semi-open interval. -.. -.. #+BEGIN_SRC python -.. def file_to_segment(hfile,segname): -.. # Extract all atributes from the data -.. attrs = hfile[segname].attrs -.. # Define each attribute -.. dstr, t0, t1 = attrs["Date"], attrs["t0"], attrs["t1"] -.. # Construct GPS starting time from data -.. start_utc = construct_utc_from_metadata(dstr, t0) -.. # Construct GPS starting time from data -.. end_utc = construct_utc_from_metadata(dstr, t1) -.. # Represent the range of times in the semi-open interval -.. return segment(start_utc,end_utc) -.. #+END_SRC -.. -.. ** Constructing UTC from metadata -.. <> -.. -.. #+BEGIN_SRC python -.. def construct_utc_from_metadata(datestr, t0str): -.. instr = "%d-%d-%02dT" % tuple(map(int, datestr.split('/'))) -.. instr += t0str -.. t = Time(instr, format='isot', scale='utc') -.. return t.gps -.. #+END_SRC -.. -.. ** Generate time series -.. <> -.. -.. #+BEGIN_SRC python -.. def generate_timeseries(data_list, setname="MagneticFields"): -.. full_data = TimeSeriesList() -.. for seg in sorted(data_list): -.. hfile = h5py.File(data_list[seg], "r") -.. full_data.append(retrieve_data_timeseries(hfile, "MagneticFields")) -.. hfile.close() -.. return full_data -.. #+END_SRC -.. -.. ** Retrieve data time series -.. <> -.. -.. #+BEGIN_SRC python -.. def retrieve_data_timeseries(hfile, setname): -.. dset = hfile[setname] -.. sample_rate = dset.attrs["SamplingRate(Hz)"] -.. gps_epoch = construct_utc_from_metadata(dset.attrs["Date"], dset.attrs["t0"]) -.. data = retrieve_channel_data(hfile, setname) -.. ts_data = TimeSeries(data, sample_rate=sample_rate, epoch=gps_epoch) -.. return ts_data -.. #+END_SRC -.. -.. ** Retrieve channel data -.. <> -.. -.. #+BEGIN_SRC python -.. def retrieve_channel_data(hfile, setname): -.. return hfile[setname][:] -.. #+END_SRC -.. -.. .. _calculate_spectral_correlation: -.. -.. ** Two point spectral correlation -.. -.. For our data, we apply a Tukey window whose flat bit corresponds to =window_fraction= (in percentage) of the segment length (in samples) used for PSD estimation (i.e. =fft_window_len=). This can be done by using the [[http://software.ligo.org/docs/lalsuite/lal/_window_8c_source.html#l00597][=CreateTukeyREAL8Window=]] module from the =lal= library. -.. -.. #+BEGIN_SRC python -.. def calculate_spectral_correlation(fft_window_len, wtype='hann', window_fraction=None): -.. if wtype == 'hann': -.. window = lal.CreateHannREAL8Window(fft_window_len) -.. elif wtype == 'tukey': -.. window = lal.CreateTukeyREAL8Window(fft_window_len, window_fraction) -.. else: -.. raise ValueError("Can't handle window type %s" % wtype) -.. #+END_SRC -.. -.. Once the window is built, a new frequency plan is created which will help performing a [[http://fourier.eng.hmc.edu/e101/lectures/fourier_transform_d/node1.html][forward transform]] on the data. This is done with the [[http://software.ligo.org/docs/lalsuite/lal/group___real_f_f_t__h.html#gac4413752db2d19cbe48742e922670af4][=CreateForwardREAL8FFTPlan=]] module which takes as argument the total number of points in the real data and the measurement level for plan creation (here 1 stands for measuring the best plan). -.. -.. #+BEGIN_SRC python -.. fft_plan = lal.CreateForwardREAL8FFTPlan(len(window.data.data), 1) -.. #+END_SRC -.. -.. We can finally compute and return the two-point spectral correlation function for the whitened frequency series (=fft_plan=) from the window applied to the original time series using the [[http://software.ligo.org/docs/lalsuite/lal/group___time_freq_f_f_t__h.html#ga2bd5c4258eff57cc80103d2ed489e076][=REAL8WindowTwoPointSpectralCorrelation=]] module. -.. -.. #+BEGIN_SRC python -.. return window, lal.REAL8WindowTwoPointSpectralCorrelation(window, fft_plan) -.. #+END_SRC -.. -.. ** Create filter bank -.. <> -.. -.. The construction of a filter bank is fairly simple. For each channel, a frequency domain channel filter function will be created using the [[http://software.ligo.org/docs/lalsuite/lalburst/group___e_p_search__h.html#ga899990cbd45111ba907772650c265ec9][=CreateExcessPowerFilter=]] module from the =lalburst= package. Each channel filter is divided by the square root of the PSD frequency series prior to normalization, which has the effect of de-emphasizing frequency bins with high noise content, and is called "over whitening". The data and metadata are finally stored in the =filter_fseries= and =filter_bank= arrays respectively. Finally, we store on a final array, called =np_filters= the all time-series generated from each filter so that we can plot them afterwards -.. -.. #+BEGIN_SRC python -.. def create_filter_bank(delta_f, flow, band, nchan, psd, spec_corr): -.. lal_psd = psd.lal() -.. lal_filters, np_filters = [],[] -.. for i in range(nchan): -.. lal_filter = lalburst.CreateExcessPowerFilter(flow + i*band, band, lal_psd, spec_corr) -.. np_filters.append(Spectrum.from_lal(lal_filter)) -.. lal_filters.append(lal_filter) -.. return filter_fseries, lal_filters, np_filters -.. #+END_SRC -.. -.. ** Compute filter inner products with themselves -.. <> -.. #+BEGIN_SRC python -.. def compute_filter_ips_self(lal_filters, spec_corr, psd=None): -.. """ -.. Compute a set of inner products of input filters with themselves. If psd -.. argument is given, the unwhitened filter inner products will be returned. -.. """ -.. return numpy.array([lalburst.ExcessPowerFilterInnerProduct(f, f, spec_corr, psd) for f in lal_filters]) -.. #+END_SRC -.. -.. ** Compute filter inner products with adjecant filters -.. <> -.. -.. #+BEGIN_SRC python -.. def compute_filter_ips_adjacent(lal_filters, spec_corr, psd=None): -.. """ -.. Compute a set of filter inner products between input adjacent filters. -.. If psd argument is given, the unwhitened filter inner products will be -.. returned. The returned array index is the inner product between the -.. lal_filter of the same index, and its (array) adjacent filter --- assumed -.. to be the frequency adjacent filter. -.. """ -.. return numpy.array([lalburst.ExcessPowerFilterInnerProduct(f1, f2, spec_corr, psd) for f1, f2 in zip(lal_filters[:-1], lal_filters[1:])]) -.. #+END_SRC -.. -.. .. _compute_channel_renomalization: -.. -.. Compute channel renormalization -.. ------------------------------- -.. -.. Compute the renormalization for the base filters up to a given bandwidth. -.. -.. #+BEGIN_SRC python -.. def compute_channel_renomalization(nc_sum, lal_filters, spec_corr, nchans, verbose=True): -.. mu_sq = (nc_sum+1)*numpy.array([lalburst.ExcessPowerFilterInnerProduct(f, f, spec_corr, None) for f in lal_filters]) -.. # Uncomment to get all possible frequency renormalizations -.. #for n in xrange(nc_sum, nchans): # channel position index -.. for n in xrange(nc_sum, nchans, nc_sum+1): # channel position index -.. for k in xrange(0, nc_sum): # channel sum index -.. # FIXME: We've precomputed this, so use it instead -.. mu_sq[n] += 2*lalburst.ExcessPowerFilterInnerProduct(lal_filters[n-k], lal_filters[n-1-k], spec_corr, None) -.. #print mu_sq[nc_sum::nc_sum+1] -.. return mu_sq -.. #+END_SRC -.. -.. ** Measure root-sum-square strain (hrss) -.. <> -.. -.. #+BEGIN_SRC python -.. def measure_hrss(z_j_b, uw_ss_ii, uw_ss_ij, w_ss_ij, delta_f, delta_t, filter_len, dof): -.. """ -.. Approximation of unwhitened sum of squares signal energy in a given EP tile. -.. See T1200125 for equation number reference. -.. z_j_b - time frequency map block which the constructed tile covers -.. uw_ss_ii - unwhitened filter inner products -.. uw_ss_ij - unwhitened adjacent filter inner products -.. w_ss_ij - whitened adjacent filter inner products -.. delta_f - frequency binning of EP filters -.. delta_t - native time resolution of the time frequency map -.. filter_len - number of samples in a fitler -.. dof - degrees of freedom in the tile (twice the time-frequency area) -.. """ -.. s_j_b_avg = uw_ss_ii * delta_f / 2 -.. # unwhitened sum of squares of wide virtual filter -.. s_j_nb_avg = uw_ss_ii.sum() / 2 + uw_ss_ij.sum() -.. s_j_nb_avg *= delta_f -.. s_j_nb_denom = s_j_b_avg.sum() + 2 * 2 / filter_len * \ -.. numpy.sum(numpy.sqrt(s_j_b_avg[:-1] * s_j_b_avg[1:]) * w_ss_ij) -.. # eqn. 62 -.. uw_ups_ratio = s_j_nb_avg / s_j_nb_denom -.. # eqn. 63 -- approximation of unwhitened signal energy time series -.. # FIXME: The sum in this equation is over nothing, but indexed by frequency -.. # I'll make that assumption here too. -.. s_j_nb = numpy.sum(z_j_b.T * numpy.sqrt(s_j_b_avg), axis=0) -.. s_j_nb *= numpy.sqrt(uw_ups_ratio / filter_len * 2) -.. # eqn. 64 -- approximate unwhitened signal energy minus noise contribution -.. # FIXME: correct axis of summation? -.. return math.sqrt(numpy.sum(numpy.absolute(s_j_nb)**2) * delta_t - s_j_nb_avg * dof * delta_t) -.. #+END_SRC -.. -.. ** Unwhitened inner products filtering -.. <> -.. -.. #+BEGIN_SRC python -.. # < s^2_j(f_1, b) > = 1 / 2 / N * \delta_t EPIP{\Theta, \Theta; P} -.. def uw_sum_sq(filter1, filter2, spec_corr, psd): -.. return lalburst.ExcessPowerFilterInnerProduct(filter1, filter2, spec_corr, psd) -.. #+END_SRC -.. -.. ** Unwhitened sum of squares signal -.. <> -.. -.. #+BEGIN_SRC python -.. def measure_hrss_slowly(z_j_b, lal_filters, spec_corr, psd, delta_t, dof): -.. """ -.. Approximation of unwhitened sum of squares signal energy in a given EP tile. -.. See T1200125 for equation number reference. NOTE: This function is deprecated -.. in favor of measure_hrss, since it requires recomputation of many inner products, -.. making it particularly slow. -.. """ -.. # FIXME: Make sure you sum in time correctly -.. # Number of finest bands in given tile -.. nb = len(z_j_b) -.. # eqn. 56 -- unwhitened mean square of filter with itself -.. uw_ss_ii = numpy.array([uw_sum_sq(lal_filters[i], lal_filters[i], spec_corr, psd) for i in range(nb)]) -.. s_j_b_avg = uw_ss_ii * lal_filters[0].deltaF / 2 -.. # eqn. 57 -- unwhitened mean square of filter with adjacent filter -.. uw_ss_ij = numpy.array([uw_sum_sq(lal_filters[i], lal_filters[i+1], spec_corr, psd) for i in range(nb-1)]) -.. # unwhitened sum of squares of wide virtual filter -.. s_j_nb_avg = uw_ss_ii.sum() / 2 + uw_ss_ij.sum() -.. s_j_nb_avg *= lal_filters[0].deltaF -.. -.. # eqn. 61 -.. w_ss_ij = numpy.array([uw_sum_sq(lal_filters[i], lal_filters[i+1], spec_corr, None) for i in range(nb-1)]) -.. s_j_nb_denom = s_j_b_avg.sum() + 2 * 2 / len(lal_filters[0].data.data) * \ -.. (numpy.sqrt(s_j_b_avg[:-1] * s_j_b_avg[1:]) * w_ss_ij).sum() -.. -.. # eqn. 62 -.. uw_ups_ratio = s_j_nb_avg / s_j_nb_denom -.. -.. # eqn. 63 -- approximation of unwhitened signal energy time series -.. # FIXME: The sum in this equation is over nothing, but indexed by frequency -.. # I'll make that assumption here too. -.. s_j_nb = numpy.sum(z_j_b.T * numpy.sqrt(s_j_b_avg), axis=0) -.. s_j_nb *= numpy.sqrt(uw_ups_ratio / len(lal_filters[0].data.data) * 2) -.. # eqn. 64 -- approximate unwhitened signal energy minus noise contribution -.. # FIXME: correct axis of summation? -.. return math.sqrt((numpy.absolute(s_j_nb)**2).sum() * delta_t - s_j_nb_avg * dof * delta_t) -.. #+END_SRC -.. -.. ** Measure root-mean square strain poorly -.. <> -.. -.. #+BEGIN_SRC python -.. def measure_hrss_poorly(tile_energy, sub_psd): -.. return math.sqrt(tile_energy / numpy.average(1.0 / sub_psd) / 2) -.. #+END_SRC -.. -.. ** List triggers from map -.. <> -.. -.. #+BEGIN_SRC python -.. def trigger_list_from_map(tfmap, event_list, threshold, start_time, start_freq, duration, band, df, dt, psd=None): -.. -.. # FIXME: If we don't convert this the calculation takes forever --- but we should convert it once and handle deltaF better later -.. if psd is not None: -.. npy_psd = psd.numpy() -.. -.. start_time = LIGOTimeGPS(float(start_time)) -.. ndof = 2 * duration * band -.. -.. spanf, spant = tfmap.shape[0] * df, tfmap.shape[1] * dt -.. print "Processing %.2fx%.2f time-frequency map." % (spant, spanf) -.. -.. for i, j in zip(*numpy.where(tfmap > threshold)): -.. event = event_list.RowType() -.. -.. # The points are summed forward in time and thus a `summed point' is the -.. # sum of the previous N points. If this point is above threshold, it -.. # corresponds to a tile which spans the previous N points. However, th -.. # 0th point (due to the convolution specifier 'valid') is actually -.. # already a duration from the start time. All of this means, the + -.. # duration and the - duration cancels, and the tile 'start' is, by -.. # definition, the start of the time frequency map if j = 0 -.. # FIXME: I think this needs a + dt/2 to center the tile properly -.. event.set_start(start_time + float(j * dt)) -.. event.set_stop(start_time + float(j * dt) + duration) -.. event.set_peak(event.get_start() + duration / 2) -.. event.central_freq = start_freq + i * df + 0.5 * band -.. -.. event.duration = duration -.. event.bandwidth = band -.. event.chisq_dof = ndof -.. -.. event.snr = math.sqrt(tfmap[i,j] / event.chisq_dof - 1) -.. # FIXME: Magic number 0.62 should be determine empircally -.. event.confidence = -lal.LogChisqCCDF(event.snr * 0.62, event.chisq_dof * 0.62) -.. if psd is not None: -.. # NOTE: I think the pycbc PSDs always start at 0 Hz --- check -.. psd_idx_min = int((event.central_freq - event.bandwidth / 2) / psd.delta_f) -.. psd_idx_max = int((event.central_freq + event.bandwidth / 2) / psd.delta_f) -.. -.. # FIXME: heuristically this works better with E - D -- it's all -.. # going away with the better h_rss calculation soon anyway -.. event.amplitude = measure_hrss_poorly(tfmap[i,j] - event.chisq_dof, npy_psd[psd_idx_min:psd_idx_max]) -.. else: -.. event.amplitude = None -.. -.. event.process_id = None -.. event.event_id = event_list.get_next_id() -.. event_list.append(event) -.. #+END_SRC -.. -.. ** Determine output segment -.. <> -.. -.. #+BEGIN_SRC python -.. def determine_output_segment(inseg, dt_stride, sample_rate, window_fraction=0.0): -.. """ -.. Given an input data stretch segment inseg, a data block stride dt_stride, the data sample rate, and an optional window_fraction, return the amount of data that can be processed without corruption effects from the window. -.. -.. If window_fration is set to 0 (default), assume no windowing. -.. """ -.. # Amount to overlap successive blocks so as not to lose data -.. window_overlap_samples = window_fraction * sample_rate -.. outseg = inseg.contract(window_fraction * dt_stride / 2) -.. -.. # With a given dt_stride, we cannot process the remainder of this data -.. remainder = math.fmod(abs(outseg), dt_stride * (1 - window_fraction)) -.. # ...so make an accounting of it -.. outseg = segment(outseg[0], outseg[1] - remainder) -.. return outseg -.. #+END_SRC -.. -.. ** Make tiles -.. <> -.. -.. #+BEGIN_SRC python -.. def make_tiles(tf_map, nc_sum, mu_sq): -.. tiles = numpy.zeros(tf_map.shape) -.. sum_filter = numpy.ones(nc_sum+1) -.. # Here's the deal: we're going to keep only the valid output and -.. # it's *always* going to exist in the lowest available indices -.. for t in xrange(tf_map.shape[1]): -.. # Sum and drop correlate tiles -.. # FIXME: don't drop correlated tiles -.. output = numpy.convolve(tf_map[:,t], sum_filter, 'valid')[::nc_sum+1] -.. #output = fftconvolve(tf_map[:,t], sum_filter, 'valid')[::nc_sum+1] -.. tiles[:len(output),t] = numpy.absolute(output) / math.sqrt(2) -.. return tiles[:len(output)]**2 / mu_sq[nc_sum::nc_sum+1].reshape(-1, 1) -.. #+END_SRC -.. -.. ** Create a time frequency map -.. <> -.. -.. In this function, we create a time frequency map with resolution similar than =tf_map= but rescale by a factor of =nc_sum= + 1. All tiles will be independent up to overlap from the original tiling. The =mu_sq= is applied to the resulting addition to normalize the outputs to be zero-mean unit-variance Gaussian variables (if the input is Gaussian). -.. -.. #+BEGIN_SRC python -.. def make_indp_tiles(tf_map, nc_sum, mu_sq): -.. tiles = tf_map.copy() -.. # Here's the deal: we're going to keep only the valid output and -.. # it's *always* going to exist in the lowest available indices -.. stride = nc_sum + 1 -.. for i in xrange(tiles.shape[0]/stride): -.. numpy.absolute(tiles[stride*i:stride*(i+1)].sum(axis=0), tiles[stride*(i+1)-1]) -.. return tiles[nc_sum::nc_sum+1].real**2 / mu_sq[nc_sum::nc_sum+1].reshape(-1, 1) -.. #+END_SRC -.. -.. ** Create output filename -.. <> -.. -.. #+BEGIN_SRC python -.. def make_filename(ifo, seg, tag="excesspower", ext="xml.gz"): -.. if isinstance(ifo, str): -.. ifostr = ifo -.. else: -.. ifostr = "".join(ifo) -.. st_rnd, end_rnd = int(math.floor(seg[0])), int(math.ceil(seg[1])) -.. dur = end_rnd - st_rnd -.. return "%s-%s-%d-%d.%s" % (ifostr, tag, st_rnd, dur, ext) -.. #+END_SRC - diff --git a/docs/_sources/calculate_psd.rst.txt b/docs/_sources/calculate_psd.rst.txt deleted file mode 100644 index 8055185..0000000 --- a/docs/_sources/calculate_psd.rst.txt +++ /dev/null @@ -1,54 +0,0 @@ -.. _psdestimate: - -Calculate Power Spectral Density (PSD) -====================================== - -The instrument's noise Power Spectral Density (PSD) will be used to whiten the data and help reveal the existence, or the absence, of repetitive patterns and correlation structures in the signal process. It will also determine the total bandwidth spanned by each of the filters that will subsequently be created. The first thing to do before calculating the PSD is to ensure that the time series data is converted into an array of floating values. :: - - # Convert time series as array of float - data = ts_data.astype(numpy.float64) - -The PSD is calculated by splitting up the signal into overlapping segments and scan through each segment to calculate individual periodogram. The periodograms from each segment are then averaged, reducing the variance of the individual power measurements. In order to proceed, we need to define the average method, ``avg_method``, that will be used to measure the PSD from the data. This can be specified with the ``--psd-estimation`` option. :: - - # Average method to measure PSD from the data - avg_method = args.psd_estimation - -One also needs to specify the length of each segment, ``seg_len``, as well as the separation between 2 consecutive segments, ``seg_stride``. Both parameters can be defined in second units with the ``--psd-segment-length`` and ``--psd-segment-stride`` arguments respectively and can then be converted into sample unit. :: - - # The segment length for PSD estimation in samples - seg_len = int(args.psd_segment_length * args.sample_rate) - # The separation between consecutive segments in samples - seg_stride = int(args.psd_segment_stride * args.sample_rate) - -We then use the `Welch's method `_ to perform the power spectral density estimate using the `welch `_ module from the ``pycbc.psd`` library. What this will do is to compute the discrete Fourier transform for each PSD segment to produce invidual periodograms, and then compute the squared magnitude of the result. The individual periodograms are then averaged using the user-defined average method, ``avg_method``, and return the frequency series, ``fd_psd``, which will store the power measurement for each frequency bin. :: - - # Lifted from the psd.from_cli module - fd_psd = psd.welch(data,avg_method=avg_method,seg_len=seg_len,seg_stride=seg_stride) - # Plot the power spectral density - plot_spectrum(fd_psd) - # We need this for the SWIG functions - lal_psd = fd_psd.lal() - -One can display the power measurements, frequency array and frequency between consecutive samples, :math:`\Delta f` in Hertz, by printing the following variables: :: - - print 'Display power measurements of the first 10 frequency bins' - print fd_psd[:10] - print 'Display central frequency of the first 10 bins' - print fd_psd.sample_frequencies[:10] - print 'Display the frequency separation between bins' - print fd_psd.delta_f - -:math:`\Delta f` corresponds to the inverse of a segment's length which is the smallest frequency (i.e. highest period) of detectable signals in each segment. The frequency range spans from 0 to the Nyquist frequency, i.e. half de the sampling rate. - -Code access ------------ - -.. currentmodule:: gdas.epower - -.. autosummary:: - :toctree: generated/ - - calculate_psd - - - diff --git a/docs/_sources/calculate_spectral_correlation.rst.txt b/docs/_sources/calculate_spectral_correlation.rst.txt deleted file mode 100644 index e3df67a..0000000 --- a/docs/_sources/calculate_spectral_correlation.rst.txt +++ /dev/null @@ -1,25 +0,0 @@ -Two point spectral correlation -============================== - -This part determines how much data on either side of the tukey window is to be discarded. Nominally, this means that one will lose ``window_fraction`` * ``args.psd_segment_length`` to corruption from the window, i.e. this is simply discarded. This is tuned to give an integer offset when used with ``args.psd_segment_length`` equal to 8, smaller windows will have fractions of integers, but larger powers of two will still preseve this (probably not a big deal in the end). :: - - window_fraction = 0 - -The two point spectral correlation is then done with the :ref:`calculate_spectral_correlation ` function which will return both the Tukey window applied to the original time series data and the actual two-point spectral correlation function for the whitened frequency series from the applied whitening window. :: - - # Do two point spectral correlation - window, spec_corr = calculate_spectral_correlation(seg_len,'tukey',window_fraction=window_fraction) - window = window.data.data - window_sigma_sq = numpy.mean(window**2) - # Pre scale the window by its root mean squared -- see eqn 11 of EP document - #window /= numpy.sqrt(window_sigma_sq) - -Code access ------------ - -.. currentmodule:: gdas.epower - -.. autosummary:: - :toctree: generated/ - - calculate_spectral_correlation diff --git a/docs/_sources/check_filtering_settings.rst.txt b/docs/_sources/check_filtering_settings.rst.txt deleted file mode 100644 index 43e5357..0000000 --- a/docs/_sources/check_filtering_settings.rst.txt +++ /dev/null @@ -1,50 +0,0 @@ -Checking filtering settings -=========================== - -The first thing to check is that the frequency of the high-pass filter (if defined) is below the minimum frequency of the filter bank. Indeed, a high-pass filter will only let pass frequency that are higher than the cutoff frequency (here defined by the ``strain_high_pass`` argument). If the high pass frequency is greater from the minimum frequency in the filter bank, the signal with frequencies lower than the cutoff frequency will get attenuated. :: - - if args.min_frequency < args.strain_high_pass: - print >>sys.stderr, "Warning: strain high pass frequency %f is greater than the tile minimum frequency %f --- this is likely to cause strange output below the bandpass frequency" % (args.strain_high_pass, args.min_frequency) - -In case the maximum frequency in the filter bank is not defined, we set it to be equal to the Nyquist frequency, i.e. half the sampling rate, which makes sense as a larger signal will not be able to get easily identifiable. :: - - if args.max_frequency is None: - args.max_frequency = args.sample_rate / 2.0 - -If the bandwidth of the finest filter (``--tile-bandwidth`` argument, see section :ref:`construct_args ` or the number of frequency channels (=--channels= argument) is not defined but the total spectral band is (``data_band``), one can then determined all the filter settings as follows: :: - - - if args.tile_bandwidth is None and args.channels is None: - # Exit program with error message - exit("Either --tile-bandwidth or --channels must be specified to set up time-frequency plane") - else: - # Define as assert statement that tile maximum frequency larger than its minimum frequency - assert args.max_frequency >= args.min_frequency - # Define spectral band of data - data_band = args.max_frequency - args.min_frequency - # Check if tile bandwidth or channel is defined - if args.tile_bandwidth is not None: - # Define number of possible filter bands - nchans = args.channels = int(data_band / args.tile_bandwidth) - 1 - elif args.channels is not None: - # Define filter bandwidth - band = args.tile_bandwidth = data_band / (args.channels + 1) - assert args.channels > 1 - -The minimum frequency to be explored can be user-defined by using the ``--min-frequency`` option. :: - - # Lowest frequency of the first filter - flow = args.min_frequency - -Code access ------------ - -.. currentmodule:: gdas.epower - -.. autosummary:: - :toctree: generated/ - - check_filtering_settings - - - diff --git a/docs/_sources/compute_channel_renormalization.rst.txt b/docs/_sources/compute_channel_renormalization.rst.txt deleted file mode 100644 index cc36882..0000000 --- a/docs/_sources/compute_channel_renormalization.rst.txt +++ /dev/null @@ -1,23 +0,0 @@ -Normalization of virtual channel -================================ - -The virtual channels will be used during the excesspower analysis to explore different frequency ranges around each PSD segments and look for possible triggers. Each channel is renormalized using the :ref:`compute_channel_renomalization ` internal function. :: - - # Initialise dictionary - mu_sq_dict = {} - # nc_sum additional channel adds - for nc_sum in range(0, int(math.log(nchans, 2))): - min_band = (len(filter_bank[0].data.data)-1) * filter_bank[0].deltaF / 2 - print tprint(t0,t1),"Calculation for %d %d Hz channels" % (nc_sum+1, min_band) - nc_sum = 2**nc_sum - 1 - mu_sq_dict[nc_sum] = compute_channel_renomalization(nc_sum, filter_bank, spec_corr, nchans) - -Code access ------------ - -.. currentmodule:: gdas.epower - -.. autosummary:: - :toctree: generated/ - - compute_channel_renormalization diff --git a/docs/_sources/construct_tiles.rst.txt b/docs/_sources/construct_tiles.rst.txt deleted file mode 100644 index ec0f619..0000000 --- a/docs/_sources/construct_tiles.rst.txt +++ /dev/null @@ -1,33 +0,0 @@ -.. _tilebandwidth: - -Constructing tiles of different bandwidth -========================================= - -First and foremost, we define a clipping region in the data to be used to remove window corruption, this is non-zero if the ``window_fraction`` variable is set to a non-zero value. :: - - print tprint(t0,t1),"Beginning tile construction..." - # Clip the boundaries to remove window corruption - clip_samples = int(args.psd_segment_length * window_fraction * args.sample_rate / 2) - -In order to perform a multi-resolution search, tiles of many different bandwidths and durations will be scanned. We first need to setup a loop such that the maximum number of additional channel is equal to the base 2 logarithm of the total number of channels. The number of narrow band channels to be summed (``nc_sum``) would therefore be equal to 2 to the power of the current quantity of additional channels. :: - - for nc_sum in range(0, int(math.log(nchans, 2)))[::-1]: # nc_sum additional channel adds - nc_sum = 2**nc_sum - 1 - print tprint(t0,t1,t2),"Summing %d narrow band channels..." % (nc_sum+1) - -The undersampling rate for this tile can be calculated using the channel frequency band and the number of narrow band channels to be summed such that the bandwidth of the tile is equal to ``band * (nc_sum + 1)``. :: - - us_rate = int(round(1.0 / (2 * band*(nc_sum+1) * ts_data.delta_t))) - print >>sys.stderr, "Undersampling rate for this level: %f" % (args.sample_rate/us_rate) - -"Virtual" wide bandwidth channels are constructed by summing the samples from multiple channels, and correcting for the overlap between adjacent channel filters. We then define the normalised channel at the current level and create a time frequency map for this tile using the :ref:`make_indp_tiles ` internal function. In other word, we are constructing multiple sub-tiles for which we can determined the respective energy in the given frequency band. :: - - mu_sq = mu_sq_dict[nc_sum] - sys.stderr.write("\t...calculating tiles...") - if clip_samples > 0: - tiles = make_indp_tiles(tf_map[:,clip_samples:-clip_samples:us_rate], nc_sum, mu_sq) - else: - tiles = make_indp_tiles(tf_map[:,::us_rate], nc_sum, mu_sq) - sys.stderr.write(" TF-plane is %dx%s samples... " % tiles.shape) - print >>sys.stderr, " done" - print "Tile energy mean: %f, var %f" % (numpy.mean(tiles), numpy.var(tiles)) diff --git a/docs/_sources/create_filter_bank.rst.txt b/docs/_sources/create_filter_bank.rst.txt deleted file mode 100644 index 4b18bef..0000000 --- a/docs/_sources/create_filter_bank.rst.txt +++ /dev/null @@ -1,52 +0,0 @@ -.. _filterbank: - -Computing the filter bank -========================= - -The filter bank will create band-pass filters for each channel in the PSD frequency domain. The :ref:`create_filter_bank ` function will san the bandwidth from the central frequency of the first channel (i.e. flow+band/2) to final frequency of the last channel (i.e. band*nchans) in a increment equal to the frequency band. The filter's total extent in Fourier space is actually twice the stated bandwidth (FWHM). :: - - # Define filters - filter_bank, fdb = create_filter_bank(fd_psd.delta_f, flow+band/2, band, nchans, fd_psd, spec_corr) - -This function will returns 2 arrays: the ``filter_bank`` array which is a list of `COMPLEX16FrequencySeries `_ arrays corresponding to each channel's filter, and the =fdb= array which provides the time-series from each filter. The length of each array is equal to the total number of channel (i.e. =nchans=). The filter's data, :math:`\Delta f` value, and first and last frequencies of any channel's filter can be displayed as followed: :: - - # Print data of first channel's filter - print filter_bank[0].data.data - # Print frequency separation between 2 values in the first channel's filter - print filter_bank[0].deltaF - # Print first frequency of the first channel's filter - print filter_bank[0].f0 - # Print last frequency of the first channel's filter (equal to twice the channel's bandwidth) - print filter_bank[0].f0+(len(filter_bank[0].data.data)-1)*filter_bank[0].deltaF - -Further in the analysis, the following filters will used: -1. ``white_filter_ip``: Whitened filter inner products computed with themselves. -2. ``unwhite_filter_ip``: Unwhitened filter inner products computed with themselves. -3. ``white_ss_ip``: Whitened filter inner products computed between input adjacent filters. -4. ``unwhite_ss_ip``: Unwhitened filter inner products computed between input adjacent filters. - -:: - - # This is necessary to compute the mu^2 normalizations - white_filter_ip = compute_filter_ips_self(filter_bank, spec_corr, None) - unwhite_filter_ip = compute_filter_ips_self(filter_bank, spec_corr, lal_psd) - # These two are needed for the unwhitened mean square sum (hrss) - white_ss_ip = compute_filter_ips_adjacent(filter_bank, spec_corr, None) - unwhite_ss_ip = compute_filter_ips_adjacent(filter_bank, spec_corr, lal_psd) - -Some extra plots can also be made :: - - tdb = convert_to_time_domain(fdb,sample_rate) - plot_bank(fdb) - plot_filters(tdb,flow,band) - - -Code access ------------ - -.. currentmodule:: gdas.epower - -.. autosummary:: - :toctree: generated/ - - create_filter_bank diff --git a/docs/_sources/create_tf_plane.rst.txt b/docs/_sources/create_tf_plane.rst.txt deleted file mode 100644 index dd95435..0000000 --- a/docs/_sources/create_tf_plane.rst.txt +++ /dev/null @@ -1,47 +0,0 @@ -Create time-frequency map -========================= - -We initialise a 2D zero array for a time-frequency map (``tf_map``) which will be computed for each frequency-domain filter associated to each PSD segment and where the filtered time-series for each frequency channels will be stored. The number of rows corresponds to the total number of frequency channels which is defined by the ``nchans`` variable. The number of columns corresponds to the segment length in samples (i.e. the number of samples covering one segment) which is defined by the ``seg_len`` variable. :: - - # Initialise 2D zero array for time-frequency map - tf_map = numpy.zeros((nchans, seg_len), dtype=numpy.complex128) - -We also initialise a zero vector for a temporary filter bank (``tmp_filter_bank``) that will store, for a given channel, the filter's values from the original filter bank (``filter_bank``) for that channel only. The length of the temporary filter bank is equal to the length of the PSD frequency series (``fd_psd``). :: - - # Initialise 1D zero array - tmp_filter_bank = numpy.zeros(len(fd_psd), dtype=numpy.complex128) - -We then loop over all the frequency channels. While in the loop, we first re-initialise the temporary filter bank with zero values everywhere along the frequency series. We then determine the first and last frequency of each channel and re-define the values of the filter in that frequency range based on the values from the original channel's filter from the original filter bank. :: - - # Loop over all the channels - print tprint(t0,t1),"Filtering all %d channels..." % nchans - for i in range(nchans): - # Reset filter bank series - tmp_filter_bank *= 0.0 - # Index of starting frequency - f1 = int(filter_bank[i].f0/fd_psd.delta_f) - # Index of ending frequency - f2 = int((filter_bank[i].f0 + 2*band)/fd_psd.delta_f)+1 - # (FIXME: Why is there a factor of 2 here?) - tmp_filter_bank[f1:f2] = filter_bank[i].data.data * 2 - -We then extract the frequency series from the filter bank for that channel, which will be used as a template waveform to filter the actual data from the channel. :: - - # Define the template to filter the frequency series with - template = types.FrequencySeries(tmp_filter_bank, delta_f=fd_psd.delta_f, copy=False) - -Finally, we use the `matched_filter_core `_ module from the ``pycbc.filter.matchedfilter`` library to filter the frequency series from the channel. This will return both a time series containing the complex signal-to-noise matched filtered against the data, and a frequency series containing the correlation vector. :: - - # Create filtered series - filtered_series = filter.matched_filter_core(template,fs_data,h_norm=None,psd=None, - low_frequency_cutoff=filter_bank[i].f0, - high_frequency_cutoff=filter_bank[i].f0+2*band) - -The `matched filter `_ is the optimal linear filter for maximizing the signal to noise ratio (SNR) in the presence of additive stochastic noise. The filtered time series is stored in the time-frequency map and can be used to produce a spectrogram of the segment of data being analysed. :: - - # Include filtered series in the map - tf_map[i,:] = filtered_series[0].numpy() - -The time-frequency map is a 2D array with a length that corresponds to the number of channels and a width equal to the number of sample present in one segment of data, i.e. segment's length in seconds times the the sampling rate. The map can finally be plotted with a :math:`\Delta t` corresponding to the sampling period of the original dataset (i.e. inverse of the original sampling rate), and :math:`\Delta f` is equal to the bandwidth of one channel. :: - - plot_spectrogram(numpy.abs(tf_map).T,tmp_ts_data.delta_t,fd_psd.delta_f,ts_data.sample_rate,start_time,end_time,fname='%s/tf.png'%(segfolder)) diff --git a/docs/_sources/create_xml.rst.txt b/docs/_sources/create_xml.rst.txt deleted file mode 100644 index 53c771e..0000000 --- a/docs/_sources/create_xml.rst.txt +++ /dev/null @@ -1,56 +0,0 @@ -Extracting GPS time range -========================= - -We use the `LIGOTimeGPS `_ structure from the =glue.lal= package to /store the starting and ending time in the dataset to nanosecond precision and synchronized to the Global Positioning System time reference/. Once both times are defined, the range of value is stored in a semi-open interval using the `segment `_ module from the =glue.segments= package. :: - - # Starting epoch relative to GPS starting epoch - start_time = LIGOTimeGPS(args.analysis_start_time or args.gps_start_time) - # Ending epoch relative to GPS ending epoch - end_time = LIGOTimeGPS(args.analysis_end_time or args.gps_end_time) - # Represent the range of values in the semi-open interval - inseg = segment(start_time,end_time) - -Prepare output file for given time range -======================================== - -:: - - xmldoc = ligolw.Document() - xmldoc.appendChild(ligolw.LIGO_LW()) - - ifo = args.channel_name.split(":")[0] - proc_row = register_to_xmldoc(xmldoc, __program__, args.__dict__, ifos=[ifo],version=glue.git_version.id, cvs_repository=glue.git_version.branch, cvs_entry_time=glue.git_version.date) - - # Figure out the data we actually analyzed - outseg = determine_output_segment(inseg, args.psd_segment_length, args.sample_rate, window_fraction) - - ss = append_search_summary(xmldoc, proc_row, ifos=(station,), inseg=inseg, outseg=outseg) - - for sb in event_list: - sb.process_id = proc_row.process_id - sb.search = proc_row.program - #sb.ifo, sb.channel = args.channel_name.split(":") - sb.ifo, sb.channel = station, setname - - xmldoc.childNodes[0].appendChild(event_list) - fname = make_filename(station, inseg) - - utils.write_filename(xmldoc, fname, gz=fname.endswith("gz"), verbose=True) - -Plot trigger results -==================== - -:: - - events = SnglBurstTable.read(fname+'.gz') - #del events[10000:] - plot = events.plot('time', 'central_freq', "duration", "bandwidth", color="snr") - #plot = events.plot('time', 'central_freq', color='snr') - #plot.set_yscale("log") - plot.set_ylim(1e-0, 250) - t0 = 1153742417 - plot.set_xlim(t0 + 0*60, t0 + 1*60) - #plot.set_xlim(t0 + 28, t0 + 32) - pyplot.axvline(t0 + 30, color='r') - cb = plot.add_colorbar(cmap='viridis') - plot.savefig("triggers.png") diff --git a/docs/_sources/example.rst.txt b/docs/_sources/example.rst.txt deleted file mode 100644 index 6677764..0000000 --- a/docs/_sources/example.rst.txt +++ /dev/null @@ -1,41 +0,0 @@ -Working Example -=============== - -Either on your own computer or on the server, on a Jupyter notebook or on a Python script, the first thing to do is to import the ``gdas`` package that contain all the modules present in the GNOME software. That can be done easily by doing the following:: - - import gdas - -In order to retrieve a specific chunk of data to be analyzed for a particular station, the name of the station along with the start and end dates should be specified:: - - station = 'fribourg01' - start_time = '2016-11-03-04' - end_time = '2016-11-03-04-2' - -where the start and end times should always have at least the year, month and day specified, and with the values separated by a dash symbol. Hour and minute can also be specified. - -If you are not working on the server and the data are located in a different repository than ``/GNOMEDrive/gnome/serverdata/``, a custom path can be defined. For instance:: - - datapath = '/Users/vincent/data/GNOMEDrive/gnome/serverdata/' - -The magnetic field data can then be retrieve as follows:: - - ts_data,ts_list,activity = gdas.magfield(station,start_time,end_time,rep=datapath) - -The ``gdas.magfield`` method will return 3 arrays of data that can then be used to produce different plots:: - - gdas.plot_activity(activity) - gdas.plot_time_series(station,ts_list,seglist=activity) - gdas.plot_asd(station,ts_list) - gdas.plot_whitening(station,ts_list,activity) - -This is a script to do Excess Power analysis:: - - psd_segment_length = 60 - psd_segment_stride = 30 - psd_estimation = 'median-mean' - window_fraction = 0 - tile_fap = 1e-5 - channels = 250 - - gdas.excess_power(ts_data,psd_segment_length,psd_segment_stride,psd_estimation,window_fraction,tile_fap,station,nchans=channels) - gdas.plot_triggers() diff --git a/docs/_sources/excess_power.rst.txt b/docs/_sources/excess_power.rst.txt deleted file mode 100644 index 840e8d3..0000000 --- a/docs/_sources/excess_power.rst.txt +++ /dev/null @@ -1,47 +0,0 @@ -Excess Power Overview -===================== - -The **Excess Power method** is known as the *optimal detection strategy* to search for burst signals for which only the duration and frequency band are known, which is basically the case for GNOME and its search of Axion-Like Particles (ALP). This method was developed and introduced by `Anderson et al. (200) `_ and has been extensively used in the detection of burst sources of gravitational radiation. A more technical documentation was written by `Brady et al. (2007) `_ describing how the algorithm used by the LIGO collaboration works and how the theory is translated into code. - -We present below a step-by-step procedure followed during the Excess Power search analysis. For a better representation of what is happening, the figure at the end shows how the data is being split and analysed to search for multiple signals of different bandwidth and duration in the time-frequency plane. - -- :ref:`Time domain segmentation and PSD estimate ` - - We first estimate the instrument's noise Power Spectral Density (PSD) by splitting the time-series data into multiple overlapping segments. A periodogram for each segment is calculated separately and then averaged, which will reduce the variance of the individual power measurements. The result is a frequency series where samples are separated in frequency space by :math:`\Delta f` equal to the inverse of a segment’s length and with a high end frequency limit equal to the Nyquist limit. The final power spectrum will help reveal the existence, or the absence, of repetitive patterns and correlation structures in a signal process. - -- :ref:`Comb of frequency channels ` - - We then split the PSD frequency series into multiple channels. For each channel, a frequency domain filter is created with a :math:`\Delta f` determined by the PSD and a total extent in Fourier space that is twice the stated bandwidth of a channel. The result is a list of each channel filter's frequency series. - -- :ref:`Creating analysing blocks ` - - The Excess Power method can lead to moderately-large computational requirements, and it has been found that the computational efficiency of this implementation can be improved upon by considering blocks of data that are much longer than the longest signal time duration. The entire time series is therefore split into separate blocks. We use the length of the segments used for PSD estimate to define the duration of each block. For each block, the time series is c0Aonverted into frequency series which is then filtered by the filter bank throughout all the channels. A time-frequency map is finally created which stores all the filtered frequency series from each channel. - -- :ref:`Creating tiles with different bandwidth ` - - We can now construct tiles with different bandwidth by summing multiple channels together. - -- :ref:`Exploring tiles with different duration ` - - For each given tile's bandwidth, one can investigate different tile's duration. This can be done by exploring different number of degrees of freedom, :math:`d`, which can be calculated as follows: :math:`d=2BT` where :math:`B` and :math:`T` are respectively the bandwidth and duration of the tile. Section 2.2.5 of `Brady et al. `_ gives a great description of how to interpret the number of degrees of freedom. Therefore, by changing the :math:`d`, one can explore multiple tile's duration for different bandwidth. - -- :ref:`Define triggering signal ` - - The energy of each tile in the time-frequency space is calculated and compare to a user-defined threshold value. After defining a tile false alarm probability threshold in Gaussian noise and using the number of degrees of freedom for each tile, one can define a energy threshold value above which a burst trigger can be identified by comparing the energy threshold with the tile's energy in the time-frequency map. A tile energy time frequency map plot similar to Figure 5 in `Pustelny et al. (2013) `_ can then be made which plots the outlying tile energies present in the data. - -.. figure:: ./img/overview.png - - Overview of the Excess Power method and difference between segments, channels, tiles and blocks. - -Code access ------------ - -.. currentmodule:: gdas.epower - -.. autosummary:: - :toctree: generated/ - - excess_power - - - diff --git a/docs/_sources/explore_duration.rst.txt b/docs/_sources/explore_duration.rst.txt deleted file mode 100644 index b27114a..0000000 --- a/docs/_sources/explore_duration.rst.txt +++ /dev/null @@ -1,54 +0,0 @@ -.. _tileduration: - -Explore multiple tile durations -=============================== - -Now that we create a tile with a specific bandwidth, we can start exploring different durations for the tile. We will start checking if the user manually defined a value for the longest duration tile to compute, which can be done using the ``--max-duration`` argument. If not, the value will be set to 32. :: - - if args.max_duration is not None: - max_dof = 2 * args.max_duration * (band * (nc_sum+1)) - else: - max_dof = 32 - assert max_dof >= 2 - -Since we produce (initially) tiles with 1 degree of freedom, the duration goes as one over twice the bandwidth. :: - - print "\t\t...getting longer durations..." - #for j in [2**l for l in xrange(1, int(math.log(max_dof, 2))+1)]: - for j in [2**l for l in xrange(0, int(math.log(max_dof, 2)))]: - sys.stderr.write("\t\tSumming DOF = %d ..." % (2*j)) - #tlen = tiles.shape[1] - j + 1 - tlen = tiles.shape[1] - 2*j + 1 + 1 - if tlen <= 0: - print >>sys.stderr, " ...not enough samples." - continue - dof_tiles = numpy.zeros((tiles.shape[0], tlen)) - #:sum_filter = numpy.ones(j) - # FIXME: This is the correct filter for 50% overlap - sum_filter = numpy.array([1,0] * (j-1) + [1]) - #sum_filter = numpy.array([1,0] * int(math.log(j, 2)-1) + [1]) - for f in range(tiles.shape[0]): - # Sum and drop correlate tiles - # FIXME: don't drop correlated tiles - #output = numpy.convolve(tiles[f,:], sum_filter, 'valid') - dof_tiles[f] = fftconvolve(tiles[f], sum_filter, 'valid') - print >>sys.stderr, " done" - print "Summed tile energy mean: %f, var %f" % (numpy.mean(dof_tiles), numpy.var(dof_tiles)) - level_tdiff = time.time() - tdiff - print >>sys.stderr, "Done with this resolution, total %f" % level_tdiff - -Finally, the bandwidth and duration of the tile can be defined as followed: :: - - # Current bandwidth of the time-frequency map tiles - current_band = band * (nc_sum + 1) - # How much each "step" is in the frequency domain -- almost - # assuredly the fundamental bandwidth - df = current_band - # How much each "step" is in the time domain -- under sampling rate - # FIXME: THis won't work if the sample rate isn't a power of 2 - dt = 1.0 / 2 / (2 * current_band) * 2 - full_band = 250 - dt = current_band / full_band * ts_data.sample_rate - dt = 1.0/dt - # Duration is fixed by the NDOF and bandwidth - duration = j / 2.0 / current_band diff --git a/docs/_sources/generated/gdas.epower.calculate_psd.rst.txt b/docs/_sources/generated/gdas.epower.calculate_psd.rst.txt deleted file mode 100644 index 859226f..0000000 --- a/docs/_sources/generated/gdas.epower.calculate_psd.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -gdas.epower.calculate_psd -========================= - -.. currentmodule:: gdas.epower - -.. autofunction:: calculate_psd \ No newline at end of file diff --git a/docs/_sources/generated/gdas.epower.calculate_spectral_correlation.rst.txt b/docs/_sources/generated/gdas.epower.calculate_spectral_correlation.rst.txt deleted file mode 100644 index de867fe..0000000 --- a/docs/_sources/generated/gdas.epower.calculate_spectral_correlation.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -gdas.epower.calculate_spectral_correlation -========================================== - -.. currentmodule:: gdas.epower - -.. autofunction:: calculate_spectral_correlation \ No newline at end of file diff --git a/docs/_sources/generated/gdas.epower.check_filtering_settings.rst.txt b/docs/_sources/generated/gdas.epower.check_filtering_settings.rst.txt deleted file mode 100644 index a648bc8..0000000 --- a/docs/_sources/generated/gdas.epower.check_filtering_settings.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -gdas.epower.check_filtering_settings -==================================== - -.. currentmodule:: gdas.epower - -.. autofunction:: check_filtering_settings \ No newline at end of file diff --git a/docs/_sources/generated/gdas.epower.compute_channel_renormalization.rst.txt b/docs/_sources/generated/gdas.epower.compute_channel_renormalization.rst.txt deleted file mode 100644 index ed456af..0000000 --- a/docs/_sources/generated/gdas.epower.compute_channel_renormalization.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -gdas.epower.compute_channel_renormalization -=========================================== - -.. currentmodule:: gdas.epower - -.. autofunction:: compute_channel_renormalization \ No newline at end of file diff --git a/docs/_sources/generated/gdas.epower.create_filter_bank.rst.txt b/docs/_sources/generated/gdas.epower.create_filter_bank.rst.txt deleted file mode 100644 index a43b152..0000000 --- a/docs/_sources/generated/gdas.epower.create_filter_bank.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -gdas.epower.create_filter_bank -============================== - -.. currentmodule:: gdas.epower - -.. autofunction:: create_filter_bank \ No newline at end of file diff --git a/docs/_sources/generated/gdas.epower.excess_power.rst.txt b/docs/_sources/generated/gdas.epower.excess_power.rst.txt deleted file mode 100644 index 69bcaea..0000000 --- a/docs/_sources/generated/gdas.epower.excess_power.rst.txt +++ /dev/null @@ -1,6 +0,0 @@ -gdas.epower.excess_power -======================== - -.. currentmodule:: gdas.epower - -.. autofunction:: excess_power \ No newline at end of file diff --git a/docs/_sources/identify_block.rst.txt b/docs/_sources/identify_block.rst.txt deleted file mode 100644 index b11537f..0000000 --- a/docs/_sources/identify_block.rst.txt +++ /dev/null @@ -1,25 +0,0 @@ -.. _analysingblocks: - -Define analysing blocks ------------------------ - -The first thing we do is to calculate the time series for the segment that is covered (``tmp_ts_data``) and redefined the metadata, especially the time of the first sample in seconds which is defined by the ``epoch`` argument and is different for every segment. After plotting the time series for that segment, the data are then converted into frequency series (``fs_data``) using the `to_frequencyseries `_ module from the ``pycbc.types.timeseries.TimeSeries`` library. Finally, the frequency data are then whitened. :: - - # Loop over each data within the user requested time period - while t_idx_max <= t_idx_max_off: - # Define starting and ending time of the segment in seconds - start_time = ts_data.start_time + t_idx_min/float(args.sample_rate) - end_time = ts_data.start_time + t_idx_max/float(args.sample_rate) - print tprint(t0,t1),"Analyzing block %i to %i (%.2f percent)"%(start_time,end_time,100*float(t_idx_max)/float(idx_max_off)) - # Model a withen time series for the block - tmp_ts_data = types.TimeSeries(ts_data[t_idx_min:t_idx_max]*window, 1.0/args.sample_rate,epoch=start_time) - # Save time series in segment repository - segfolder = 'segments/%i-%i'%(start_time,end_time) - os.system('mkdir -p '+segfolder) - plot_ts(tmp_ts_data,fname='%s/ts.png'%(segfolder)) - # Convert times series to frequency series - fs_data = tmp_ts_data.to_frequencyseries() - print tprint(t0,t1),"Frequency series data has variance: %s" % fs_data.data.std()**2 - # Whitening (FIXME: Whiten the filters, not the data) - fs_data.data /= numpy.sqrt(fd_psd) / numpy.sqrt(2 * fd_psd.delta_f) - print tprint(t0,t1),"Whitened frequency series data has variance: %s" % fs_data.data.std()**2 diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt deleted file mode 100644 index 1cca81a..0000000 --- a/docs/_sources/index.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -Welcome! -======== - -This package contains functions useful for magnetic field signal processing, with a focus on Excess Power search analysis and application on the data for the GNOME collaboration, see `Pustelny et al. (2013) `_. This documentation details all the available functions and tasks available through this software. Here are some example tasks that can (or will soon to) be handled: - -* Plot usual time series and spectrogram of magnetic field data. -* Perform excess power analysis and plot detected triggers in time-frequency map. -* Create artificial data for testing data analysis. -* Inject fake signal of different bandwidth and durations. -* Cross-correlation of continuous sine wave signals. -* Perform Allan Standard deviation. - -Should you have any questions or suggested corrections to be made, do not hesitate to `contact me `_. - -.. raw:: html - - Fork me on GitHub - -Getting Started ---------------- - -.. toctree:: - :maxdepth: 1 - - installation - server - example - -Excess Power Search Analysis ----------------------------- - -.. toctree:: - :maxdepth: 1 - - excess_power - -Pre-calculations and initialization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. toctree:: - :maxdepth: 1 - - check_filtering_settings - calculate_psd - calculate_spectral_correlation - create_filter_bank - compute_channel_renormalization - initialization - -Looping over each block -~~~~~~~~~~~~~~~~~~~~~~~ - -.. toctree:: - :maxdepth: 1 - - identify_block - create_tf_plane - construct_tiles - explore_duration - -Save results -~~~~~~~~~~~~ - -.. toctree:: - :maxdepth: 1 - - create_xml - diff --git a/docs/_sources/initialization.rst.txt b/docs/_sources/initialization.rst.txt deleted file mode 100644 index 02e17f4..0000000 --- a/docs/_sources/initialization.rst.txt +++ /dev/null @@ -1,41 +0,0 @@ -Initialise event list and determine stride boundaries -===================================================== - -First of all, we create a table similar than the one made by the LIGO Scientific Collaboration (LSC) where all the information will be stored. Such table is commonly know as ``lsctables``. A pre-defined LSC table can be constructed using ``New`` function from the `glue.ligolw.lsctables `_ module. We use the ``SnglBurstTable`` function for the type of data to be stored and define all the columns we wish to record. :: - - # Create event list for single burst table - event_list = lsctables.New(lsctables.SnglBurstTable, - ['start_time','start_time_ns','peak_time','peak_time_ns', - 'duration','bandwidth','central_freq','chisq_dof', - 'confidence','snr','amplitude','channel','ifo', - 'process_id','event_id','search','stop_time','stop_time_ns']) - -We also need to determine the indexes of both starting and ending times for the first segment to analyse, respectively ``t_idx_min`` and ``t_idx_max``. The default values are considered to be 0 for the starting index and the segment length in sample unit for the ending time index. Also, if the user defines a different starting time than the one from the loaded data, the offset index in sample unit is determined and added the both starting and ending time indexes. :: - - # Determine boundaries of stride in time domain - t_idx_min, t_idx_max = 0, seg_len - # Check if user requested starting time is defined - if args.analysis_start_time is not None: - # Define the time difference in seconds between data and user requested starting times - t_idx_off = args.analysis_start_time - ts_data.start_time - # Calculate the index of the user requested starting point in the data - t_idx_off = int(t_idx_off * args.sample_rate) - else: - # Define index of the starting point as first value in data - t_idx_off = 0 - # Initialise minimum index values as offset starting index - t_idx_min += t_idx_off - # Initialise maximum index values as offset starting index - t_idx_max += t_idx_off - -Finally, the index for the ending time after all the segments have been analysed can be estimated for the user-defined parameter or is defined as the length of the time series data ``ts_data``. :: - - # Check if user requested end time is defined - if args.analysis_end_time is not None: - # Define the time difference between data and user requested ending times - t_idx_max_off = args.analysis_end_time - ts_data.start_time - # Calculate the index of the user requested starting point in the data - t_idx_max_off = int(t_idx_max_off * args.sample_rate) - else: - # Define index of the ending point as the length of data array - t_idx_max_off = len(ts_data) diff --git a/docs/_sources/installation.rst.txt b/docs/_sources/installation.rst.txt deleted file mode 100644 index cb5ed4e..0000000 --- a/docs/_sources/installation.rst.txt +++ /dev/null @@ -1,57 +0,0 @@ -Installation -============ - -The program requires the following general packages to run: `Numpy `_, `Matplotlib `_, `Scipy `_ and `Astropy `_. The following LIGO-related packages are also required for full functionality: `Gwpy `_, `PyCBC `_, `Glue `_, `LAL `_, `LALburst `_ and `LALsimulation `_. - -While most of the packages can be installed automatically using `pip `_, some LIGO packages (Glue, LAL, LALburst and LALsimulation) must be installed separately beforehand as they contain several C routines that need specific compilation. However, these packages are already included in a bigger package called `LALsuite `_ which can be installed fairly easily on Debian (Linux) and Mac OS machines. - -LALsuite tools --------------- - -Some useful pages on how to download and install the LIGO software can be found `here `_. - -MacPorts (Mac) -~~~~~~~~~~~~~~ - -For Mac users, the installation is pretty easy, detailed information can be found on `this page `_. You need to have `MacPorts `_ installed. The following commands should suffice to install the LALsuite package on your machine:: - - sudo port install lscsoft-deps - sudo port install glue - sudo port install lalapps - -The first command will install all the dependencies needed for the LIGO software to be installed. The following 2 commands will install the actual packages. - -apt-get (Debian) -~~~~~~~~~~~~~~~~ - -Since the LIGO software is not a default package in the apt package manager system on Debian machine, additional steps will be needed. The first step is to add the following links to the source list located at ``/etc/apt/sources.list``:: - - deb [arch=amd64] http://software.ligo.org/lscsoft/debian jessie contrib - deb-src [arch=amd64] http://software.ligo.org/lscsoft/debian jessie contrib - -Note that the ``[arch=amd64]`` is needed to fix the architecture problem in case it tries to install i386 version on 64-bit Debian. Once the sources have been added, you must first install all the dependencies as follows:: - - apt-get install build-essential automake autoconf libtool devscripts - -The LIGO software can finally be installed using the following command:: - - apt-get install lscsoft-all - -Main Program ------------- - -The best way to install the GNOME software along with the rest of the dependencies is by using `pip`:: - - pip install gdas - -(You may need to put a ``sudo`` in front of this). For this to work -you need to have `pip -`_ installed. This -method allows for easy uninstallation. - -You can also simply download the tarball from the PyPI website, unpack it and then do:: - - python setup.py install - -The latest stable package can be downloaded from PyPI: https://pypi.python.org/pypi/gdas. -The development version can be downloaded from `here `_. diff --git a/docs/_sources/server.rst.txt b/docs/_sources/server.rst.txt deleted file mode 100644 index b8b5172..0000000 --- a/docs/_sources/server.rst.txt +++ /dev/null @@ -1,20 +0,0 @@ -Multi-user Server -================= - -A GNOME JupyterHub, or multi-user server has been created to allow each member to access the entire available dataset. Member who do not have access to the server but wish to access it should send a request to Dr. Sam Afach. Member who are not part of the GNOME collaboration will not be granted access to the dataset but are free to use our software on their own data. - -The server can be accessed in two ways, either by acceding the `server's webpage `_, or from your terminal through SSH:: - - ssh -X username@budker.uni-mainz.de -p 8022 - -While SSH is very handy for people using UNIX-like operating systems, this can become more complicated for those working on Windows machines. Fortunately, access to a terminal is also possible through the webpage, which means directly from your internet browser! This can be done by clicking on the New tab after login and select Terminal: - -.. figure:: img/jupyter1.png - :width: 70% - :align: center - -You can then use the terminal window to access files and create new Python scripts for your analysis. - -.. figure:: img/jupyter2.png - :width: 70% - :align: center diff --git a/docs/_static/ajax-loader.gif b/docs/_static/ajax-loader.gif deleted file mode 100644 index 61faf8c..0000000 Binary files a/docs/_static/ajax-loader.gif and /dev/null differ diff --git a/docs/_static/alert_info_32.png b/docs/_static/alert_info_32.png deleted file mode 100644 index ea4d1ba..0000000 Binary files a/docs/_static/alert_info_32.png and /dev/null differ diff --git a/docs/_static/alert_warning_32.png b/docs/_static/alert_warning_32.png deleted file mode 100644 index a687c3d..0000000 Binary files a/docs/_static/alert_warning_32.png and /dev/null differ diff --git a/docs/_static/basic.css b/docs/_static/basic.css deleted file mode 100644 index 7ed0e58..0000000 --- a/docs/_static/basic.css +++ /dev/null @@ -1,632 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox input[type="text"] { - width: 170px; -} - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px 7px 0 7px; - background-color: #ffe; - width: 40%; - float: right; -} - -p.sidebar-title { - font-weight: bold; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px 7px 0 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -div.admonition dl { - margin-bottom: 0; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - border: 0; - border-collapse: collapse; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -dl { - margin-bottom: 15px; -} - -dd p { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dt:target, .highlighted { - background-color: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; -} - -td.linenos pre { - padding: 5px 0px; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -div.code-block-caption { - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -div.code-block-caption + div > div.highlight > pre { - margin-top: 0; -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - padding: 1em 1em 0; -} - -div.literal-block-wrapper div.highlight { - margin: 0; -} - -code.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -code.descclassname { - background-color: transparent; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: relative; - left: 0px; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/docs/_static/bg-page.png b/docs/_static/bg-page.png deleted file mode 100644 index fe0a6dc..0000000 Binary files a/docs/_static/bg-page.png and /dev/null differ diff --git a/docs/_static/bullet_orange.png b/docs/_static/bullet_orange.png deleted file mode 100644 index 1cb8097..0000000 Binary files a/docs/_static/bullet_orange.png and /dev/null differ diff --git a/docs/_static/comment-bright.png b/docs/_static/comment-bright.png deleted file mode 100644 index 15e27ed..0000000 Binary files a/docs/_static/comment-bright.png and /dev/null differ diff --git a/docs/_static/comment-close.png b/docs/_static/comment-close.png deleted file mode 100644 index 4d91bcf..0000000 Binary files a/docs/_static/comment-close.png and /dev/null differ diff --git a/docs/_static/comment.png b/docs/_static/comment.png deleted file mode 100644 index dfbc0cb..0000000 Binary files a/docs/_static/comment.png and /dev/null differ diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js deleted file mode 100644 index 8163495..0000000 --- a/docs/_static/doctools.js +++ /dev/null @@ -1,287 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for all documentation. - * - * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", - "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -} - */ - -/** - * small helper function to urldecode strings - */ -jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s == 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node) { - if (node.nodeType == 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { - var span = document.createElement("span"); - span.className = className; - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this); - }); - } - } - return this.each(function() { - highlight(this); - }); -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated == 'undefined') - return string; - return (typeof translated == 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated == 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - if (!body.length) { - body = $('body'); - } - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) == 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this == '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - }, - - initOnKeyListeners: function() { - $(document).keyup(function(event) { - var activeElementType = document.activeElement.tagName; - // don't navigate when in search box or textarea - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { - switch (event.keyCode) { - case 37: // left - var prevHref = $('link[rel="prev"]').prop('href'); - if (prevHref) { - window.location.href = prevHref; - return false; - } - case 39: // right - var nextHref = $('link[rel="next"]').prop('href'); - if (nextHref) { - window.location.href = nextHref; - return false; - } - } - } - }); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); \ No newline at end of file diff --git a/docs/_static/down-pressed.png b/docs/_static/down-pressed.png deleted file mode 100644 index 5756c8c..0000000 Binary files a/docs/_static/down-pressed.png and /dev/null differ diff --git a/docs/_static/down.png b/docs/_static/down.png deleted file mode 100644 index 1b3bdad..0000000 Binary files a/docs/_static/down.png and /dev/null differ diff --git a/docs/_static/file.png b/docs/_static/file.png deleted file mode 100644 index a858a41..0000000 Binary files a/docs/_static/file.png and /dev/null differ diff --git a/docs/_static/haiku.css b/docs/_static/haiku.css deleted file mode 100644 index d4615fa..0000000 --- a/docs/_static/haiku.css +++ /dev/null @@ -1,376 +0,0 @@ -/* - * haiku.css_t - * ~~~~~~~~~~~ - * - * Sphinx stylesheet -- haiku theme. - * - * Adapted from http://haiku-os.org/docs/Haiku-doc.css. - * Original copyright message: - * - * Copyright 2008-2009, Haiku. All rights reserved. - * Distributed under the terms of the MIT License. - * - * Authors: - * Francois Revol - * Stephan Assmus - * Braden Ewing - * Humdinger - * - * :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -html { - margin: 0px; - padding: 0px; - background: #FFF url(bg-page.png) top left repeat-x; -} - -body { - line-height: 1.5; - margin: auto; - padding: 0px; - font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; - min-width: 59em; - max-width: 70em; - color: #333333; -} - -div.footer { - padding: 8px; - font-size: 11px; - text-align: center; - letter-spacing: 0.5px; -} - -/* link colors and text decoration */ - -a:link { - font-weight: bold; - text-decoration: none; - color: #dc3c01; -} - -a:visited { - font-weight: bold; - text-decoration: none; - color: #892601; -} - -a:hover, a:active { - text-decoration: underline; - color: #ff4500; -} - -/* Some headers act as anchors, don't give them a hover effect */ - -h1 a:hover, a:active { - text-decoration: none; - color: #0c3762; -} - -h2 a:hover, a:active { - text-decoration: none; - color: #0c3762; -} - -h3 a:hover, a:active { - text-decoration: none; - color: #0c3762; -} - -h4 a:hover, a:active { - text-decoration: none; - color: #0c3762; -} - -a.headerlink { - color: #a7ce38; - padding-left: 5px; -} - -a.headerlink:hover { - color: #a7ce38; -} - -/* basic text elements */ - -div.content { - margin-top: 20px; - margin-left: 40px; - margin-right: 40px; - margin-bottom: 50px; - font-size: 0.9em; -} - -/* heading and navigation */ - -div.header { - position: relative; - left: 0px; - top: 0px; - height: 85px; - /* background: #eeeeee; */ - padding: 0 40px; -} -div.header h1 { - font-size: 1.6em; - font-weight: normal; - letter-spacing: 1px; - color: #0c3762; - border: 0; - margin: 0; - padding-top: 15px; -} -div.header h1 a { - font-weight: normal; - color: #0c3762; -} -div.header h2 { - font-size: 1.3em; - font-weight: normal; - letter-spacing: 1px; - text-transform: uppercase; - color: #aaa; - border: 0; - margin-top: -3px; - padding: 0; -} - -div.header img.rightlogo { - float: right; -} - - -div.title { - font-size: 1.3em; - font-weight: bold; - color: #0c3762; - border-bottom: dotted thin #e0e0e0; - margin-bottom: 25px; -} -div.topnav { - /* background: #e0e0e0; */ -} -div.topnav p { - margin-top: 0; - margin-left: 40px; - margin-right: 40px; - margin-bottom: 0px; - text-align: right; - font-size: 0.8em; -} -div.bottomnav { - background: #eeeeee; -} -div.bottomnav p { - margin-right: 40px; - text-align: right; - font-size: 0.8em; -} - -a.uplink { - font-weight: normal; -} - - -/* contents box */ - -table.index { - margin: 0px 0px 30px 30px; - padding: 1px; - border-width: 1px; - border-style: dotted; - border-color: #e0e0e0; -} -table.index tr.heading { - background-color: #e0e0e0; - text-align: center; - font-weight: bold; - font-size: 1.1em; -} -table.index tr.index { - background-color: #eeeeee; -} -table.index td { - padding: 5px 20px; -} - -table.index a:link, table.index a:visited { - font-weight: normal; - text-decoration: none; - color: #dc3c01; -} -table.index a:hover, table.index a:active { - text-decoration: underline; - color: #ff4500; -} - - -/* Haiku User Guide styles and layout */ - -/* Rounded corner boxes */ -/* Common declarations */ -div.admonition { - -webkit-border-radius: 10px; - -khtml-border-radius: 10px; - -moz-border-radius: 10px; - border-radius: 10px; - border-style: dotted; - border-width: thin; - border-color: #dcdcdc; - padding: 10px 15px 10px 15px; - margin-bottom: 15px; - margin-top: 15px; -} -div.note { - padding: 10px 15px 10px 80px; - background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat; - min-height: 42px; -} -div.warning { - padding: 10px 15px 10px 80px; - background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat; - min-height: 42px; -} -div.seealso { - background: #e4ffde; -} - -/* More layout and styles */ -h1 { - font-size: 1.3em; - font-weight: bold; - color: #0c3762; - border-bottom: dotted thin #e0e0e0; - margin-top: 30px; -} - -h2 { - font-size: 1.2em; - font-weight: normal; - color: #0c3762; - border-bottom: dotted thin #e0e0e0; - margin-top: 30px; -} - -h3 { - font-size: 1.1em; - font-weight: normal; - color: #0c3762; - margin-top: 30px; -} - -h4 { - font-size: 1.0em; - font-weight: normal; - color: #0c3762; - margin-top: 30px; -} - -p { - text-align: justify; -} - -p.last { - margin-bottom: 0; -} - -ol { - padding-left: 20px; -} - -ul { - padding-left: 5px; - margin-top: 3px; -} - -li { - line-height: 1.3; -} - -div.content ul > li { - -moz-background-clip:border; - -moz-background-inline-policy:continuous; - -moz-background-origin:padding; - background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em; - list-style-image: none; - list-style-type: none; - padding: 0 0 0 1.666em; - margin-bottom: 3px; -} - -td { - vertical-align: top; -} - -code { - background-color: #e2e2e2; - font-size: 1.0em; - font-family: monospace; -} - -pre { - border-color: #0c3762; - border-style: dotted; - border-width: thin; - margin: 0 0 12px 0; - padding: 0.8em; - background-color: #f0f0f0; -} - -hr { - border-top: 1px solid #ccc; - border-bottom: 0; - border-right: 0; - border-left: 0; - margin-bottom: 10px; - margin-top: 20px; -} - -/* printer only pretty stuff */ -@media print { - .noprint { - display: none; - } - /* for acronyms we want their definitions inlined at print time */ - acronym[title]:after { - font-size: small; - content: " (" attr(title) ")"; - font-style: italic; - } - /* and not have mozilla dotted underline */ - acronym { - border: none; - } - div.topnav, div.bottomnav, div.header, table.index { - display: none; - } - div.content { - margin: 0px; - padding: 0px; - } - html { - background: #FFF; - } -} - -.viewcode-back { - font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; - margin: -1px -10px; - padding: 0 12px; -} - -/* math display */ -div.math p { - text-align: center; -} \ No newline at end of file diff --git a/docs/_static/jquery-3.1.0.js b/docs/_static/jquery-3.1.0.js deleted file mode 100644 index f2fc274..0000000 --- a/docs/_static/jquery-3.1.0.js +++ /dev/null @@ -1,10074 +0,0 @@ -/*eslint-disable no-unused-vars*/ -/*! - * jQuery JavaScript Library v3.1.0 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2016-07-07T21:44Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var document = window.document; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var concat = arr.concat; - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - - - - function DOMEval( code, doc ) { - doc = doc || document; - - var script = doc.createElement( "script" ); - - script.text = code; - doc.head.appendChild( script ).parentNode.removeChild( script ); - } -/* global Symbol */ -// Defining this global in .eslintrc would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.1.0", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num != null ? - - // Return just the one element from the set - ( num < 0 ? this[ num + this.length ] : this[ num ] ) : - - // Return all the elements in a clean array - slice.call( this ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = jQuery.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isArray: Array.isArray, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // As of jQuery 3.0, isNumeric is limited to - // strings and numbers (primitives or objects) - // that can be coerced to finite numbers (gh-2662) - var type = jQuery.type( obj ); - return ( type === "number" || type === "string" ) && - - // parseFloat NaNs numeric-cast false positives ("") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - !isNaN( obj - parseFloat( obj ) ); - }, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE <=9 - 11, Edge 12 - 13 - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.0 - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-01-04 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - disabledAncestor = addCombinator( - function( elem ) { - return elem.disabled === true; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement("fieldset"); - - try { - return !!fn( el ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - // Known :disabled false positives: - // IE: *[disabled]:not(button, input, select, textarea, optgroup, option, menuitem, fieldset) - // not IE: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Check form elements and option elements for explicit disabling - return "label" in elem && elem.disabled === disabled || - "form" in elem && elem.disabled === disabled || - - // Check non-disabled form elements for fieldset[disabled] ancestors - "form" in elem && elem.disabled === false && ( - // Support: IE6-11+ - // Ancestry is covered for us - elem.isDisabled === disabled || - - // Otherwise, assume any non-