def apcorrect_cps(lc, band, aper=gt.aper2deg(7)): """ Apply the aperture correction in units of linear counts-per-second. Aperture correction is linear in magnitude units, so convert the count rate into AB mag, correct it, and then convert it back. """ return (gt.mag2counts( gt.counts2mag(lc['cps'].values, band) - gt.apcorrect1(aper, band), band))
def calculate_flare_energy(lc, frange, distance, binsize=30, band='NUV', effective_widths={ 'NUV': 729.94, 'FUV': 255.45 }, quiescence=None): """ Calculates the energy of a flare in erg. """ if not quiescence: q, _ = get_inff(lc) # Convert to aperture-corrected flux q = gt.mag2counts( gt.counts2mag(q, band) - gt.apcorrect1(gt.aper2deg(6), band), band) else: q = quiescence[0] # Convert from parsecs to cm distance_cm = distance * 3.086e+18 if 'cps_apcorrected' in lc.keys(): # Converting from counts / sec to flux units. flare_flux = (np.array( gt.counts2flux(np.array(lc.iloc[frange]['cps_apcorrected']), band)) - gt.counts2flux(q, band)) else: # Really need to have aperture-corrected counts/sec. raise ValueError("Need aperture-corrected cps fluxes to continue.") # Zero any flux values where the flux is below the INFF so that we don't subtract from the total flux! flare_flux = np.array([0 if f < 0 else f for f in flare_flux]) flare_flux_err = gt.counts2flux(np.array(lc.iloc[frange]['cps_err']), band) tbins = (np.array(lc.iloc[frange]['t1'].values) - np.array(lc.iloc[frange]['t0'].values)) # Caluclate the area under the curve. integrated_flux = (binsize * flare_flux).sum() """ GALEX effective widths from http://svo2.cab.inta-csic.es/svo/theory/fps3/index.php?id=GALEX/GALEX.NUV width = 729.94 A http://svo2.cab.inta-csic.es/svo/theory/fps3/index.php?id=GALEX/GALEX.FUV width = 255.45 A """ # Convert integrated flux to a fluence using the GALEX effective widths. fluence = integrated_flux * effective_widths[band] fluence_err = (np.sqrt( ((gt.counts2flux(lc.iloc[frange]['cps_err'], band) * binsize)** 2).sum()) * effective_widths[band]) energy = (4 * np.pi * (distance_cm**2) * fluence) energy_err = (4 * np.pi * (distance_cm**2) * fluence_err) return energy, energy_err
def model_errors(catmag, band, sigma=3., mode='mag', trange=[1, 1600]): """ Give upper and lower expected bounds as a function of the nominal magnitude of a source. Very useful for identifying outliers. :param catmag: Nominal AB magnitude of the source. :type catmag: float :param band: The band to use, either 'FUV' or 'NUV'. :type band: str :param sigma: How many sigma out to set the bounds. :type sigma: float :param mode: Units in which to report bounds. Either 'cps' or 'mag'. :type mode: str :param trange: Set of integration times to compute the bounds on, in seconds. :type trange: list :returns: tuple -- A two-element tuple containing the lower and upper bounds, respectively. """ if mode != 'cps' and mode != 'mag': print('mode must be set to "cps" or "mag"') exit(0) x = np.arange(trange[0], trange[1]) cnt = gt.mag2counts(catmag, band) ymin = (cnt * x / x) - sigma * np.sqrt(cnt * x) / x ymax = (cnt * x / x) + sigma * np.sqrt(cnt * x) / x if mode == 'mag': ymin = gt.counts2mag(ymin, band) ymax = gt.counts2mag(ymax, band) return ymin, ymax
def model_errors(catmag, band, sigma=3., mode='mag', trange=[1, 1600]): """ Give upper and lower expected bounds as a function of the nominal magnitude of a source. Very useful for identifying outliers. :param catmag: Nominal AB magnitude of the source. :type catmag: float :param band: The band to use, either 'FUV' or 'NUV'. :type band: str :param sigma: How many sigma out to set the bounds. :type sigma: float :param mode: Units in which to report bounds. Either 'cps' or 'mag'. :type mode: str :param trange: Set of integration times to compute the bounds on, in seconds. :type trange: list :returns: tuple -- A two-element tuple containing the lower and upper bounds, respectively. """ if mode != 'cps' and mode != 'mag': print('mode must be set to "cps" or "mag"') exit(0) x = np.arange(trange[0], trange[1]) cnt = gt.mag2counts(catmag, band) ymin = (cnt*x/x)-sigma*np.sqrt(cnt*x)/x ymax = (cnt*x/x)+sigma*np.sqrt(cnt*x)/x if mode == 'mag': ymin = gt.counts2mag(ymin, band) ymax = gt.counts2mag(ymax, band) return ymin, ymax
def data_errors(catmag, band, t, sigma=3., mode='mag'): """ Given an array (of counts or mags), return an array of n-sigma error values. :param catmag: Nominal AB magnitude of the source. :type catmag: float :param t: Set of integration times to compute the bounds on, in seconds. :type t: list @CHASE - is this scalar or list? Also, consider trange instead of t to match first method?@ :param band: The band to use, either 'FUV' or 'NUV'. :type band: str :param sigma: How many sigma out to set the bounds. :type sigma: float :param mode: Units in which to report bounds. Either 'cps' or 'mag'. :type mode: str :returns: tuple -- A two-element tuple containing the lower and upper uncertainty, respectively. """ if mode != 'cps' and mode != 'mag': print('mode must be set to "cps" or "mag"') exit(0) cnt = gt.mag2counts(catmag, band) ymin = (cnt * t / t) - sigma * np.sqrt(cnt * t) / t ymax = (cnt * t / t) + sigma * np.sqrt(cnt * t) / t if mode == 'mag': ymin = gt.counts2mag(ymin, band) ymax = gt.counts2mag(ymax, band) return ymin, ymax
def data_errors(catmag, band, t, sigma=3., mode='mag'): """ Given an array (of counts or mags), return an array of n-sigma error values. :param catmag: Nominal AB magnitude of the source. :type catmag: float :param t: Set of integration times to compute the bounds on, in seconds. :type t: list @CHASE - is this scalar or list? Also, consider trange instead of t to match first method?@ :param band: The band to use, either 'FUV' or 'NUV'. :type band: str :param sigma: How many sigma out to set the bounds. :type sigma: float :param mode: Units in which to report bounds. Either 'cps' or 'mag'. :type mode: str :returns: tuple -- A two-element tuple containing the lower and upper uncertainty, respectively. """ if mode != 'cps' and mode != 'mag': print('mode must be set to "cps" or "mag"') exit(0) cnt = gt.mag2counts(catmag, band) ymin = (cnt*t/t)-sigma*np.sqrt(cnt*t)/t ymax = (cnt*t/t)+sigma*np.sqrt(cnt*t)/t if mode == 'mag': ymin = gt.counts2mag(ymin, band) ymax = gt.counts2mag(ymax, band) return ymin, ymax
def dmag_errors(t, band, sigma=3., mode='mag', mags=np.arange(13, 24, 0.1)): """ Given an exposure time, give dmag error bars at a range of magnitudes. :param t: Set of integration times to compute the bounds on, in seconds. :type t: list @CHASE - is this scalar or list? Also, consider trange instead of t to match first method?@ :param band: The band to use, either 'FUV' or 'NUV'. :type band: str :param sigma: How many sigma out to set the bounds. :type sigma: float :param mode: Units in which to report bounds. Either 'cps' or 'mag'. :type mode: str :param mags: Set of magnitudes to compute uncertainties on. :type mags: numpy.ndarray :returns: tuple -- A three-element tuple containing the magnitudes and their lower and upper uncertainties, respectively. """ cnts = gt.mag2counts(mags, band) * t ymin = (cnts - sigma / np.sqrt(cnts)) / t ymax = (cnts + sigma / np.sqrt(cnts)) / t if mode == 'mag': ymin = mags - gt.counts2mag(ymin, band) ymax = mags - gt.counts2mag(ymax, band) return mags, ymin, ymax
def dmag_errors(t, band, sigma=3., mode='mag', mags=np.arange(13, 24, 0.1)): """ Given an exposure time, give dmag error bars at a range of magnitudes. :param t: Set of integration times to compute the bounds on, in seconds. :type t: list @CHASE - is this scalar or list? Also, consider trange instead of t to match first method?@ :param band: The band to use, either 'FUV' or 'NUV'. :type band: str :param sigma: How many sigma out to set the bounds. :type sigma: float :param mode: Units in which to report bounds. Either 'cps' or 'mag'. :type mode: str :param mags: Set of magnitudes to compute uncertainties on. :type mags: numpy.ndarray :returns: tuple -- A three-element tuple containing the magnitudes and their lower and upper uncertainties, respectively. """ cnts = gt.mag2counts(mags, band)*t ymin = (cnts-sigma/np.sqrt(cnts))/t ymax = (cnts+sigma/np.sqrt(cnts))/t if mode == 'mag': ymin = mags-gt.counts2mag(ymin, band) ymax = mags-gt.counts2mag(ymax, band) return mags, ymin, ymax
import gPhoton.galextools as gt magrange = np.arange(14, 24, 1) expt_ratio = 0.8 # Estimate of ration of effective to raw exposure time t_raw = np.arange(1, 1600, 1) t_eff = expt_ratio * t_raw for sigma in [1, 3]: fig = plt.figure(figsize=(8 * 2, 6 * 2)) for i, band in enumerate(['FUV', 'NUV']): plt.subplot(2, 1, i) plt.grid(b=True) plt.semilogx() plt.xlim(1, 1600) plt.title('{b} Detection Threshholds'.format(b=band)) plt.xlabel('Exposure Bin Depth (s)') plt.ylabel('{n} Sigma Error (AB Mag)'.format(n=sigma)) for mag in magrange: cps = gt.mag2counts(mag, band) cps_err = sigma * np.sqrt(cps * t_eff) / t_eff mag_err = mag - gt.counts2mag(cps + cps_err, band) plt.plot(t_raw, mag_err, label=mag) plt.legend() plt.savefig('GALEX {n} Sigma Detection Limits.png'.format(n=sigma))
def fake_a_flare( band='NUV', quiescent_mag=18, fpeak_mags=[17], # peak flux of flares stepsz=30., # integration depth in seconds trange=[0, 1600], # visit length in seconds tpeaks=[250], # peak time of flares fwidths=[60], # "fwhm" of flares resolution=0.05, # normal photon time resolution flat_err=0.15 # 15% error in the NUV flat field ): quiescent_cps = mag2counts(quiescent_mag, band) t = np.arange(trange[0], trange[1], resolution) tbins = np.arange(trange[0], trange[1], stepsz) models = [] for ii, fpeak_mag, tpeak, fwidth in zip(range(len(fpeak_mags)), fpeak_mags, tpeaks, fwidths): fpeak_cps = mag2counts(fpeak_mag, band) flare = (aflare(t, [tpeak, fwidth, max(fpeak_cps - quiescent_cps, 0)]) + quiescent_cps) flare_binned = [] for t0 in tbins: ix = np.where((t >= t0) & (t < t0 + stepsz)) flare_binned += [np.array(flare)[ix].sum() / len(ix[0])] flare_binned_counts = np.array(flare_binned) * stepsz flare_obs = np.array([ np.random.normal(loc=counts, scale=np.sqrt(counts)) / stepsz for counts in flare_binned_counts ]) flare_obs_err = np.array( [np.sqrt(counts) / stepsz for counts in flare_binned_counts]) model_dict = { 't0': t, 't1': t + resolution, 'cps': flare, 'cps_err': np.zeros(len(flare)), 'flux': counts2flux(flare, band), 'flux_err': np.zeros(len(flare)), 'flags': np.zeros(len(flare)) } models.append(pd.DataFrame(model_dict)) # Construct a simulated lightcurve dataframe. # NOTE: Since we don't need to worry about aperture corrections, we # copy the cps and cps_err into those so the paper's pipeline can run on # this simulated light curve too. We assume no missing time coverage in # the time bins, since those with bad coverage are avoided in our paper's # pipeline anyways, and thus set the 'expt' to be the same as the # requested bin size. if ii == 0: # First flare in visit, setup the entire light curve. lc_dict = { 't0': tbins, 't1': tbins + stepsz, 'cps': flare_obs, 'cps_err': flare_obs_err, 'cps_apcorrected': flare_obs, 'counts': flare_binned_counts, 'flux': counts2flux(flare_obs, band), 'flux_err': counts2flux(flare_obs_err, band), 'expt': np.full(len(tbins), stepsz), 'flags': np.zeros(len(tbins)) } else: # More flares in this light curve, only need to adjust the fluxes. lc_dict['cps'] += flare_obs lc_dict['cps_err'] = ((lc_dict['cps_err'])**2. + (flare_obs_err)**2.)**0.5 lc_dict['cps_apcorrected'] += flare_obs lc_dict['counts'] += flare_binned_counts lc_dict['flux'] += counts2flux(flare_obs, band) lc_dict['flux_err'] = ((lc_dict['flux_err'])**2. + (counts2flux(flare_obs_err, band))**2.)**0.5 # "TRUTH" + simulated lightcurve dataframe return models, pd.DataFrame(lc_dict)
def inject_and_recover( n=1000, omit_incompletes=True, band='NUV', stepsz=30., trange=[0, 1600], resolution=0.05, distance=2.7, # parsecs quiescent_mag=18, # approx. NUV mag mag_range=[13, 18], # approx. GALEX NUV bright limit detection_threshold=5): """ NOTE: deault for distance, quiescent_mag, and mag_range are for UV Ceti. 'detection_threshold' is specified as a sigma value """ output = pd.DataFrame({ 'energy_true': [], 'energy_measured': [], 'energy_measured_err': [], 'energy_measured_w_q': [], 'energy_measured_w_q_err': [], 'q': [], 'q_err': [], 'q_true': [], 'i_fpeak_mag': [], 'i_tpeak': [], 'i_fwidth': [] }) while len(output['energy_true']) < n: printed = False if not len(output['energy_true']) % 10000 and not printed: print('Injecting: {x}% done...'.format( x=float(len(output['energy_true'])) / float(n) * 100.)) # Turn off counter in cases where the injected flare is rejected # for being truncated, so you don't get the same status update # printed multiple times. printed = True fpeak_mag = np.random.uniform(low=mag_range[0], high=mag_range[1]) # Peaks within the visit tpeak = np.random.uniform(low=trange[0], high=trange[1]) # FWHM in seconds fwidth = np.random.uniform(low=1, high=300) model, lc = fake_a_flare(band=band, quiescent_mag=quiescent_mag, fpeak_mag=fpeak_mag, stepsz=stepsz, trange=trange, tpeak=tpeak, fwidth=fwidth, resolution=resolution) # Calculate the "true" flare energy. model_energy = calculate_ideal_flare_energy( model, mag2counts(quiescent_mag, band), distance) # Estimate the INFF. q, q_err = get_inff(lc) # Detect flares. fr, quiescence, quiescence_err = refine_flare_ranges( lc, sigma=detection_threshold, makeplot=False) if not len(fr): output = output.append( pd.Series({ 'energy_true': model_energy, 'energy_measured': 0, #energy[0], 'energy_measured_err': 0, #energy[1], 'energy_measured_w_q': 0, #energy_w_q[0], 'energy_measured_w_q_err': 0, #energy_w_q[1], 'q': q, 'q_err': q_err, 'q_true': quiescent_mag, 'i_fpeak_mag': fpeak_mag, 'i_tpeak': tpeak, 'i_fwidth': fwidth }), ignore_index=True) for f in fr: if omit_incompletes and ((f[0] == 0) or f[-1] == len(lc) - 1): continue energy = calculate_flare_energy(lc, f, distance, band=band) energy_w_q = calculate_flare_energy( lc, f, distance, band=band, quiescence=[mag2counts(quiescent_mag, band), 0.0]) output = output.append(pd.Series({ 'energy_true': model_energy, 'energy_measured': energy[0], 'energy_measured_err': energy[1], 'energy_measured_w_q': energy_w_q[0], 'energy_measured_w_q_err': energy_w_q[1], 'q': q, 'q_err': q_err, 'q_true': quiescent_mag, 'i_fpeak_mag': fpeak_mag, 'i_tpeak': tpeak, 'i_fwidth': fwidth }), ignore_index=True) # Update counter again. printed = False return output
def test_mag2counts_NUV(self): self.assertAlmostEqual(gt.mag2counts(16,'NUV'),10.**(-(16-20.08)/2.5))
def test_mag2counts_FUV(self): self.assertAlmostEqual(gt.mag2counts(16,'FUV'),10.**(-(16-18.82)/2.5))
import gPhoton.galextools as gt magrange = np.arange(14,24,1) expt_ratio = 0.8 # Estimate of ration of effective to raw exposure time t_raw = np.arange(1,1600,1) t_eff = expt_ratio*t_raw for sigma in [1,3]: fig = plt.figure(figsize=(8*2,6*2)) for i,band in enumerate(['FUV','NUV']): plt.subplot(2,1,i) plt.grid(b=True) plt.semilogx() plt.xlim(1,1600) plt.title('{b} Detection Threshholds'.format(b=band)) plt.xlabel('Exposure Bin Depth (s)') plt.ylabel('{n} Sigma Error (AB Mag)'.format(n=sigma)) for mag in magrange: cps = gt.mag2counts(mag,band) cps_err = sigma*np.sqrt(cps*t_eff)/t_eff mag_err = mag-gt.counts2mag(cps+cps_err,band) plt.plot(t_raw,mag_err,label=mag) plt.legend() plt.savefig('GALEX {n} Sigma Detection Limits.png'.format(n=sigma))