def plot_plane(planecolor=0.5, coord_system='gal', plane='SGP', **kwargs): """ Plot a the supergalactic plane onto skymap. :param planecolor: color of plane :param coord_system: default galactic ('gal') / equatorial ('eq') :param plane: plots 'SGP' or 'GAL' or both (list) into plot :param kwargs: additional named keyword arguments passed to plt.plot() """ phi0 = np.linspace(0, 2 * np.pi, 100) if coord_system.upper() == 'GAL': # only plotting the SGP makes sense phi, theta = coord.vec2ang( coord.sgal2gal(coord.ang2vec(phi0, np.zeros_like(phi0)))) kwargs.setdefault('color', planecolor) plt.plot(-np.sort(phi), theta[np.argsort(phi)], **kwargs) elif coord_system.upper() == 'EQ': if 'SGP' in plane: phi, theta = coord.vec2ang( coord.gal2eq( coord.sgal2gal(coord.ang2vec(phi0, np.zeros_like(phi0))))) kwargs.setdefault('color', planecolor) plt.plot(-np.sort(phi), theta[np.argsort(phi)], **kwargs) if 'GAL' in plane: phi, theta = coord.vec2ang( coord.gal2eq(coord.ang2vec(phi0, np.zeros_like(phi0)))) kwargs.setdefault('color', '0.5') plt.plot(-np.sort(phi), theta[np.argsort(phi)], **kwargs) else: raise Exception( "plane type not understood, use GP or SGP or list!") else: raise Exception("coord system not understood, use eq or gal!")
def test_06_rotate(self): v1 = coord.rand_vec(stat) rot_axis = np.hstack(coord.rand_vec(1)) angle = 0.25 v2 = coord.rotate(v1, rot_axis, angle) angles = coord.angle(v1, v2) self.assertTrue((angles > 0).all() & (angles <= angle).all()) # rotate back v3 = coord.rotate(v2, rot_axis, -angle) v4 = coord.rotate(v2, rot_axis, 2 * np.pi - angle) self.assertTrue(np.allclose(v1, v3)) self.assertTrue(np.allclose(v3, v4)) # when rotating around z-axis and vectors have z=0: all angles have to be 0.25 rot_axis = np.array([0, 0, 1]) v1 = coord.ang2vec(coord.rand_phi(stat), np.zeros(stat)) v2 = coord.rotate(v1, rot_axis, angle) angles = coord.angle(v1, v2) self.assertTrue((angles > angle - 1e-3).all() & (angles < angle + 1e-3).all()) # when rotating around z-axis all angles correspond to longitude shift angles = 2 * np.pi * np.random.random(stat) v1 = coord.rand_vec(stat) lon1, lat1 = coord.vec2ang(v1) v2 = np.array( [coord.rotate(vi, rot_axis, ai) for vi, ai in zip(v1.T, angles)]).T lon2, lat2 = coord.vec2ang(v2) self.assertTrue(np.allclose(lat1, lat2)) lon_diff = lon1 - lon2 lon_diff[lon_diff < 0] += 2 * np.pi self.assertTrue(np.allclose(lon_diff, angles))
def plot_tissot(vec_c, r, res=1e-2, **kwargs): """ Plot a circle onto skymap. :param vec_c: vector pointing to the center of the circle :param r: radius of the circle :param res: resolution of the circle approximation (in radian) :param kwargs: additional named keyword arguments passed to plt.plot() """ lon, lat = coord.vec2ang(vec_c) vec_ref = coord.rotate(vec_c, coord.sph_unit_vectors(lon, lat)[2], r) psis = np.arange(0, 2 * np.pi, res) lons, lats = coord.vec2ang(coord.rotate(vec_ref, vec_c, psis)) plt.plot(-lons, lats, **kwargs)
def sensitivity_2pt(self, set_idx=None, niso=1000, bins=180, **kwargs): """ Function to calculate the sensitivity by the 2pt-auto-correlation over a scrambling of the right ascension coordinates. :param set_idx: If set, only this set number will be evaluated :param niso: Number of isotropic sets to calculate :param bins: Number of angular bins, 180 correspond to 1 degree binning (np.linspace(0, np.pi, bins+1). :param kwargs: additional named arguments passed to obs.two_pt_auto() :return: pvalues in the shape (self.nsets, bins) """ kwargs.setdefault('cumulative', True) vec_crs = self.get('vecs') _, dec = coord.vec2ang(coord.gal2eq(np.reshape(vec_crs, (3, -1)))) # calculate auto correlation for isotropic scrambled data _ac_iso = np.zeros((niso, bins)) for i in range(niso): _vecs = coord.ang2vec(coord.rand_phi(self.ncrs), np.random.choice(dec, size=self.ncrs)) _ac_iso[i] = obs.two_pt_auto(_vecs, bins, **kwargs) # calculate p-value by comparing the true sets with the isotropic ones set_idx = np.arange(self.nsets) if set_idx is None else [set_idx] pvals = np.zeros((len(set_idx), bins)) for i, idx in enumerate(set_idx): _ac_crs = obs.two_pt_auto(vec_crs[:, idx], bins, **kwargs) pvals[i] = np.sum(_ac_iso >= _ac_crs[np.newaxis], axis=0) / float(niso) return pvals
def test_20_exposure_issue(self): sim = ObservedBound(nside=4, nsets=nsets, ncrs=ncrs) sim.apply_exposure(a0=-35.25, zmax=60) sim.arrival_setup(0.) crs = sim.get_data(convert_all=True) _, dec = coord.vec2ang(coord.gal2eq(crs['vecs'].reshape(3, -1))) self.assertTrue(np.sum(coord.exposure_equatorial(dec, a0=-35.25, zmax=60) <= 0) == 0)
def check_problematic_pixel(nside, ipix, a0, zmax, deviation=0.5, coord_system='gal'): """ Checks input pixel for exposure deviation within the corner points from more than certain threshold (default: 0.5). :param nside: nside of the healpy pixelization :param ipix: pixel number(s) :param a0: latitude of detector (-90, 90) in degrees (default: Auger) :param zmax: maximum acceptance zenith angle (0, 90) degrees :param deviation: maximum deviation between exposure values in pixel corners :param coord_system: choose between different coordinate systems - gal, eq, sgal, ecl """ npix = hp.nside2npix(nside) v = np.swapaxes(hp.boundaries(nside, np.arange(npix), step=1), 0, 1).reshape(3, -1) if coord_system != 'eq': v = getattr(coord, '%s2eq' % coord_system)(v) # exposure values of corner points exposure = coord.exposure_equatorial(coord.vec2ang(v)[1], a0, zmax).reshape((npix, 4)) # check for maximal deviation of corner points _min, _max = np.min(exposure, axis=-1), np.max(exposure, axis=-1) mask = _max > 0 eps = np.min(_min[_min > 0]) / 2. _min[_min < eps] = eps mask = mask * (_max / _min > (1 + deviation)) return mask[ipix]
def test_05_ang2vec(self): phi = coord.rand_phi(stat) theta = coord.rand_theta(stat) vec = coord.ang2vec(phi, theta) self.assertTrue(np.allclose(np.sum(vec**2, axis=0), np.ones(stat))) phi2, theta2 = coord.vec2ang(vec) self.assertTrue(np.allclose(phi, phi2)) self.assertTrue(np.allclose(theta, theta2))
def test_09_exposure(self): sim = ObservedBound(self.nside, self.nsets, self.ncrs) sim.apply_exposure() sim.arrival_setup(0.) crs = sim.get_data(convert_all=True) vecs_eq = coord.gal2eq(coord.ang2vec(np.hstack(crs['lon']), np.hstack(crs['lat']))) lon_eq, lat_eq = coord.vec2ang(vecs_eq) self.assertTrue(np.abs(np.mean(lon_eq)) < 0.05) self.assertTrue((np.mean(lat_eq) < -0.5) & (np.mean(lat_eq) > - 0.55))
def plot_thrust(self, n, t, **kwargs): """ Visualize the thrust observables in the ROI. :param n: Thrust axis as given by astrotools.obs.thrust()[1] :param t: Thrust values as returned by astrotools.obs.thrust()[0] :param kwargs: Keywords passed to matplotlib.pyplot.plot() for axis visualization """ kwargs.setdefault('c', 'red') linestyle_may = kwargs.pop('linestyle', 'solid') alpha_may = kwargs.pop('alpha', 0.5) lon, lat = coord.vec2ang(n[0]) # fill thrust array (unit vector phi runs in negative lon direction) e_phi = coord.sph_unit_vectors(lon, lat)[1] sign = np.sign(e_phi[2] - n[1][2]) phi_major = sign * coord.angle(e_phi, n[1])[0] phi_minor = sign * coord.angle(e_phi, n[2])[0] if np.abs(phi_major - phi_minor) < 0.99 * np.pi / 2.: phi_minor = 2 * np.pi - phi_minor t23_ratio = t[1] / t[2] # mark the principal axes n3 u = np.array(np.cos(phi_minor)) v = -1. * np.array(np.sin(phi_minor)) urot, vrot, x, y = self.m.rotate_vector(u, v, np.rad2deg(lon), np.rad2deg(lat), returnxy=True) _phi = np.arctan2(vrot, urot) s = self.r_roi * (t[1] / 0.15) * self.scale / t23_ratio self.m.plot([x - np.cos(_phi) * s, x + np.cos(_phi) * s], [y - np.sin(_phi) * s, y + np.sin(_phi) * s], linestyle='dashed', alpha=0.5, **kwargs) # mark the principal axes n2 u = np.array(np.cos(phi_major)) v = -1. * np.array(np.sin(phi_major)) urot, vrot, x, y = self.m.rotate_vector(u, v, np.rad2deg(lon), np.rad2deg(lat), returnxy=True) _phi = np.arctan2(vrot, urot) s = self.r_roi * (t[1] / 0.15) * self.scale self.m.plot([x - np.cos(_phi) * s, x + np.cos(_phi) * s], [y - np.sin(_phi) * s, y + np.sin(_phi) * s], linestyle=linestyle_may, alpha=alpha_may, **kwargs) # mark the center point self.m.plot((x), (y), 'o', color=kwargs.pop('c'), markersize=10)
def test_15_exposure(self): nsets = 100 sim = ObservedBound(self.nside, nsets, self.ncrs) sim.apply_exposure(a0=-35.25, zmax=60) sim.arrival_setup(0.2) crs = sim.get_data(convert_all=True) lon, lat = np.hstack(crs['lon']), np.hstack(crs['lat']) ra, dec = coord.vec2ang(coord.gal2eq(coord.ang2vec(lon, lat))) exp = coord.exposure_equatorial(dec, a0=-35.25, zmax=60) self.assertTrue((exp > 0).all())
def vec2ang(v): """ Substitutes healpy.vec2ang() to use our angle convention. :param v: vector(s) of shape (3, n) :return: tuple consisting of - phi [range (pi, -pi), 0 points in x-direction, pi/2 in y-direction], - theta [range (pi/2, -pi/2), pi/2 points in z-direction] """ return coord.vec2ang(v)
def pix2ang(nside, ipix, nest=False): """ Convert HEALpixel ipix to spherical angles (astrotools definition) Substitutes hp.pix2ang :param nside: nside of the healpy pixelization :param ipix: pixel number(s) :param nest: set True in case you work with healpy's nested scheme :return: angles (phi, theta) in astrotools definition """ v = pix2vec(nside, ipix, nest=nest) phi, theta = coord.vec2ang(v) return phi, theta
def test_06_scrambling(self): n = 5 vecs = coord.rand_exposure_vec(a0=-45, zmax=45, n=stat, coord_system='gal') ra, dec = coord.vec2ang(coord.gal2eq(vecs)) vecs_new = coord.equatorial_scrambling(vecs, n, coord_system='gal') self.assertTrue(vecs_new.shape == (3, n, stat)) for i in range(n): ra_s, dec_s = coord.vec2ang(coord.gal2eq(vecs_new[:, i])) self.assertTrue(np.allclose(dec, dec_s)) self.assertTrue(not np.allclose(ra, ra_s)) vecs = coord.rand_exposure_vec(a0=-80, zmax=60, n=stat, coord_system='eq') ra, dec = coord.vec2ang(vecs) vecs_new = coord.equatorial_scrambling(vecs, n, coord_system='eq') for i in range(n): ra_s, dec_s = coord.vec2ang(vecs_new[:, i]) self.assertTrue(np.allclose(dec, dec_s)) self.assertTrue(not np.allclose(ra, ra_s))
def rand_exposure_vec_in_pix(nside, ipix, a0=-35.25, zmax=60, coord_system='gal', deviation=0.5, nest=False): """ Draw vectors from a distribution within a HEALpixel that follow the exposure distribution within the pixel. It is much slower than rand_vec_in_pix() and should therefore only be used for problematic pixels (close to zero exposure). :param nside: nside of the healpy pixelization :param ipix: pixel number(s) :param a0: latitude of detector (-90, 90) in degrees (default: Auger) :param zmax: maximum acceptance zenith angle (0, 90) degrees :param coord_system: choose between different coordinate systems - gal, eq, sgal, ecl :param deviation: maximum relative deviation between exposure values in pixel corners :param nest: set True in case you work with healpy's nested scheme :return: vectors containing events from the pixel(s) specified in ipix """ ipix = np.atleast_1d(ipix) vecs = np.zeros((3, ipix.size)) mask = check_problematic_pixel(nside, ipix, a0, zmax, deviation) vecs[:, ~mask] = rand_vec_in_pix(nside, ipix[~mask], nest) if not nest: ipix = hp.ring2nest(nside, ipix=ipix) for pix in np.unique(ipix[mask]): n = np.sum(ipix == pix) # increase resolution of healpy schemes cooresponding to number of crs per pixel n_up = max(3, int(np.ceil(np.log10(10 * n) / np.log10(4)))) pix_new = pix * 4**n_up + np.arange(4**n_up) v = pix2vec(nside=nside * 2**n_up, ipix=pix_new, nest=True) if coord_system != 'eq': v = getattr(coord, '%s2eq' % coord_system)(v) p = coord.exposure_equatorial(coord.vec2ang(v)[1], a0, zmax) pixel = np.random.choice(pix_new, size=n, replace=False, p=p / np.sum(p)) vecs[:, ipix == pix] = pix2vec(nside=nside * 2**n_up, ipix=pixel, nest=True) return np.array(vecs)
def test_02_rand_exposure_vec(self): a0 = -45 vecs = coord.rand_exposure_vec(a0=-45, zmax=45, n=stat, coord_system='eq') phi, theta = coord.vec2ang(vecs) self.assertTrue( abs(np.sum(phi >= 0) - np.sum(phi < 0)) < 3 * np.sqrt(stat)) self.assertTrue((theta < 0).all()) self.assertTrue( np.sum(theta < -np.deg2rad(a0)) > np.sum(theta > -np.deg2rad(a0))) # auger exposure vecs = coord.rand_exposure_vec(n=stat, coord_system='gal') ra = coord.get_right_ascension(vecs, coord_system='gal') dec = coord.get_declination(vecs, coord_system='gal') self.assertTrue( abs(np.sum(ra >= 0) - np.sum(ra < 0)) < 3 * np.sqrt(stat)) self.assertTrue(np.sum(dec > 0) < np.sum(dec < 0)) exposure = coord.get_exposure(vecs, coord_system='gal') self.assertTrue(np.all(exposure > 0))
def test_07_sph_unit_vector(self): lon1, lat1 = 0, 0 e_r, e_phi, e_theta = coord.sph_unit_vectors(lon1, lat1) self.assertTrue(np.allclose(e_r, np.array([1, 0, 0]))) self.assertTrue(np.allclose(e_phi, np.array([0, 1, 0]))) self.assertTrue(np.allclose(e_theta, np.array([0, 0, 1]))) vecs2 = coord.rand_vec(10) lon2, lat2 = coord.vec2ang(vecs2) e_r, e_phi, e_theta = coord.sph_unit_vectors(lon2, lat2) # check that vecs are aligned with e_r and orthogonal to e_phi, e_theta self.assertTrue(np.allclose(np.sum(vecs2 * e_r, axis=0), np.ones(10))) self.assertTrue( np.allclose(np.sum(vecs2 * e_phi, axis=0), np.zeros(10))) self.assertTrue( np.allclose(np.sum(vecs2 * e_theta, axis=0), np.zeros(10))) # check if all unit vectors are normed self.assertTrue(np.allclose(np.sum(e_r**2, axis=0), np.ones(10))) self.assertTrue(np.allclose(np.sum(e_phi**2, axis=0), np.ones(10))) self.assertTrue(np.allclose(np.sum(e_theta**2, axis=0), np.ones(10))) # check for right-handed e_theta_cross = np.cross(e_r, e_phi, axis=0) self.assertTrue(np.allclose(e_theta, e_theta_cross))
def sensitivity_2pt(self, niso=1000, bins=180, **kwargs): """ Function to calculate the sensitivity by the 2pt-auto-correlation over a scrambling of the right ascension coordinates. :param niso: Number of isotropic sets to calculate. :param bins: Number of angular bins, 180 correspond to 1 degree binning (np.linspace(0, np.pi, bins+1). :param kwargs: additional named arguments passed to obs.two_pt_auto() :return: pvalues in the shape (bins) """ kwargs.setdefault('cumulative', True) vec_crs = self.get('vecs') _, dec = coord.vec2ang(coord.gal2eq(vec_crs)) # calculate auto correlation for isotropic scrambled data _ac_iso = np.zeros((niso, bins)) for i in range(niso): _vecs = coord.ang2vec(coord.rand_phi(self.ncrs), dec) _ac_iso[i] = obs.two_pt_auto(_vecs, bins, **kwargs) # calculate p-value by comparing the true sets with the isotropic ones _ac_crs = obs.two_pt_auto(vec_crs, bins, **kwargs) pvals = np.sum(_ac_iso >= _ac_crs[np.newaxis], axis=0) / float(niso) return pvals
def test_21_convert_all(self): sim = ObservedBound(nside=4, nsets=nsets, ncrs=ncrs) sim.arrival_setup(0.) crs = sim.get_data(convert_all=False) keys = crs.keys() self.assertTrue('vecs' not in keys and 'lon' not in keys and 'lat' not in keys) vecs = crs['vecs'] # automatic from pixel center sim.convert_pixel('angles') # converts pixel to lon / lat crs = sim.get_data(convert_all=False) keys = crs.keys() self.assertTrue('vecs' not in keys and 'lon' in keys and 'lat' in keys) _lon, _lat = coord.vec2ang(vecs) self.assertTrue(np.mean(abs(crs['lon'] - _lon) < 0.5)) self.assertTrue(np.mean(abs(crs['lat'] - _lat) < 0.5)) self.assertTrue(np.mean(abs(crs['lon'] - _lon) > 0)) self.assertTrue(np.mean(abs(crs['lat'] - _lat) > 0)) sim = ObservedBound(nside=4, nsets=nsets, ncrs=ncrs) sim.apply_exposure(a0=-35.25, zmax=60) sim.arrival_setup(0.) crs = sim.get_data(convert_all=True) keys = crs.keys() self.assertTrue('vecs' in keys and 'lon' in keys and 'lat' in keys)
def scatter(v, c=None, cblabel='log$_{10}$(Energy / eV)', opath=None, fig=None, **kwargs): """ Scatter plot of events with arrival directions x,y,z and colorcoded energies. :param v: array of shape (3, n) pointing into directions of the events :param c: quantity that is supposed to occur in colorbar, e.g. energy of the cosmic rays :param cblabel: colorbar label :param opath: if not None, saves the figure to the given opath (no returns) :param fig: figure to plot in, creates new figure if None :param kwargs: additional named keyword arguments - figsize: figure size as input for plt.figure() - cmap: colormap - cbar: if True includes a colobar - cticks: sets ticks of colormap - mask_alpha: alpha value for maskcolor - fontsize: scale the general fontsize - dark_grid: if True paints a dark grid (useful for bright maps) - gridcolor: Color of the grid. - gridalpha: Transparency value of the gridcolor. - tickcolor: Color of the ticks. - tickalpha: Transparency of the longitude ticks. - plane: plots 'SGP' or 'GP' or both (list) into plot - planecolor: color of plane - coord_system: default galactic ('gal') / equatorial ('eq') :return: figure, axis of the scatter plot """ lons, lats = coord.vec2ang(v) fontsize = kwargs.pop('fontsize', 26) kwargs.setdefault('s', 8) if 'marker' not in kwargs: kwargs.setdefault('lw', 0) cbar = kwargs.pop('cbar', True) and isinstance(c, (list, tuple, np.ndarray)) if cbar: vmin = kwargs.pop( 'vmin', smart_round(np.min(c[np.isfinite(c)]), upper_border=False)) vmax = kwargs.pop( 'vmax', smart_round(np.max(c[np.isfinite(c)]), upper_border=True)) step = smart_round((vmax - vmin) / 5., order=1) cticks = kwargs.pop( 'cticks', np.round(np.arange(vmin, vmax, step), int(np.round(-np.log10(step), 0)))) clabels = kwargs.pop('clabels', cticks) # read keyword arguments for the grid dark_grid = kwargs.pop('dark_grid', True) gridcolor = kwargs.pop('gridcolor', 'lightgray' if dark_grid is None else 'black') gridalpha = kwargs.pop('gridalpha', 0.5 if dark_grid is None else 0.4) tickcolor = kwargs.pop('tickcolor', 'lightgray' if dark_grid is None else 'black') tickalpha = kwargs.pop('tickalpha', 0.5 if dark_grid is None else 1) planecolor = kwargs.pop('planecolor', 'darkgray') plane = kwargs.pop('plane', None) coord_system = kwargs.pop('coord_system', 'gal') if coord_system == 'eq': lons, lats = coord.vec2ang(coord.gal2eq(coord.ang2vec(lons, lats))) # mimic astronomy convention: positive longitudes evolving to the left with respect to GC lons = -lons # plot the events fig = plt.figure( figsize=kwargs.pop('figsize', [12, 6])) if fig is None else fig ax = fig.add_axes([0.1, 0.1, 0.85, 0.9], projection="hammer") events = ax.scatter(lons, lats, c=c, **kwargs) if cbar: cbar = plt.colorbar(events, orientation='horizontal', shrink=0.85, pad=0.05, aspect=30, ticks=cticks) cbar.set_label(cblabel, fontsize=fontsize) events.set_clim(vmin, vmax) cbar.ax.tick_params(labelsize=fontsize - 4) cbar.set_ticklabels(clabels) cbar.draw_all() # Setup the grid plt.xticks(fontsize=fontsize) plt.yticks(fontsize=fontsize) plot_grid(gridcolor=gridcolor, gridalpha=gridalpha, tickalpha=tickalpha, tickcolor=tickcolor, fontsize=fontsize) if plane is not None: plot_plane(planecolor, coord_system, plane) if opath is not None: plt.savefig(opath, bbox_inches='tight') plt.clf() return fig, ax
def test_04_vec2ang(self): v = coord.rand_vec(stat) phi, theta = coord.vec2ang(v) self.assertTrue((phi >= -np.pi).all() and (phi <= np.pi).all() and (theta >= -np.pi).all() and (theta <= np.pi).all())