def itau(temp, dens, line, n_min=5, n_max=1000, other='', verbose=False, value='itau', location=LOCALDIR): """ Gives the integrated optical depth for a given temperature and density. It assumes that the background radiation field dominates the continuum emission. The emission measure is unity. The output units are Hz. :param temp: Electron temperature. Must be a string of the form '8d1'. :type temp: string :param dens: Electron density. :type dens: float :param line: Line to load models for. :type line: string :param n_min: Minimum n value to include in the output. Default 1 :type n_min: int :param n_max: Maximum n value to include in the output. Default 1500, Maximum allowed value 9900 :type n_max: int :param other: String to search for different radiation fields and others. :type other: string :param verbose: Verbose output? :type verbose: bool :param value: ['itau'|'bbnMdn'|'None'] Value to output. itau will output the integrated optical depth. \ bbnMdn will output the :math:`\\beta_{n,n^{\\prime}}b_{n}` times the oscillator strenght :math:`M(\\Delta n)`. \ None will output the :math:`\\beta_{n,n^{\\prime}}b_{n}` values. :type value: string :returns: The principal quantum number and its asociated value. """ t = str2val(temp) d = float(dens) dn = fc.set_dn(line) mdn_ = mdn(dn) bbn = load_betabn(temp, dens, other, line, verbose, location=location) nimin = best_match_indx(n_min, bbn[:, 0]) nimax = best_match_indx(n_max, bbn[:, 0]) n = bbn[nimin:nimax, 0] b = bbn[nimin:nimax, 1] if value == 'itau': i = itau_norad(n, t, b, dn, mdn_) elif value == 'bbnMdn': i = b * dn * mdn_ else: i = b return n, i
def kappa_line(Te, ne, nion, Z, Tr, trans, n_max=1500): """ Computes the line absorption coefficient for CRRLs between levels :math:`n_{i}` and :math:`n_{f}`, :math:`n_{i}>n_{f}`. This can only go up to :math:`n_{\\rm{max}}` 1500 because of the tables used for the Einstein Anm coefficients. :param Te: Electron temperature of the gas. (K) :type Te: float :param ne: Electron density. (:math:`\\mbox{cm}^{-3}`) :type ne: float :param nion: Ion density. (:math:`\\mbox{cm}^{-3}`) :type nion: float :param Z: Electric charge of the atoms being considered. :type Z: int :param Tr: Temperature of the radiation field felt by the gas. This specifies the temperature of the field at 100 MHz. (K) :type Tr: float :param trans: Transition for which to compute the absorption coefficient. :type trans: string :param n_max: Maximum principal quantum number to include in the output. :type n_max: int<1500 :returns: :rtype: array """ cte = np.power(c, 2.) / (16. * np.pi) * np.power( np.power(h, 2) / (2. * np.pi * m_e * k_B), 3. / 2.) bn = load_bn(val2str(Te), ne, other='case_diffuse_{0}'.format(val2str(Tr))) bn = bn[:np.where(bn[:, 0] == n_max)[0]] Anfni = np.loadtxt('{0}/rates/einstein_Anm_{1}.txt'.format( LOCALDIR, trans)) # Cut the Einstein Amn coefficients table to match the bn values i_bn_i = best_match_indx(bn[0, 0], Anfni[:, 1]) i_bn_f = best_match_indx(bn[-1, 0], Anfni[:, 0]) Anfni = Anfni[i_bn_i:i_bn_f + 1] ni = Anfni[:, 0] nf = Anfni[:, 1] omega_ni = 2 * np.power(ni, 2) omega_i = 1. xi_ni = xi(ni, Te, Z) xi_nf = xi(nf, Te, Z) exp_ni = np.exp(xi_ni.value) exp_nf = np.exp(xi_nf.value) #print len(Anfni), len(bn[1:,-1]), len(bn[:-1,-1]), len(omega_ni[:]), len(ni), len(exp_ni), len(exp_nf) kl = cte.value / np.power( Te, 3. / 2.) * ne * nion * Anfni[:, 2] * omega_ni[:] / omega_i * ( bn[1:, -1] * exp_ni - bn[:-1, -1] * exp_nf) return kl
def get_line_mask(freq, reffreq, v0, dv): """ Return a mask with ranges where a line is expected in the given frequency range for \ a line with a given reference frequency at expected velocity v0 and line width dv0. :param freq: Frequency axis where the line is located. :type freq: numpy array or list :param reffreq: Reference frequency for the line. :type reffreq: float :param v0: Velocity of the line. :type v0: float, km/s :param dv: Velocity range to mask. :type dv: float, km/s :returns: Mask centered at the line center and width `dv0` referenced to the input `freq`. """ f0 = vel2freq(reffreq, v0 * 1e3) df0 = dv2df(reffreq * 1e6, dv0 * 1e3) df = abs(freq[0] - freq[1]) f0_indx = utils.best_match_indx(f0, freq, df / 2.0) mindx0 = f0_indx - df0 / df / 1e6 mindxf = f0_indx + df0 / df / 1e6 return [mindx0, mindxf]
def load_bn(te, ne, tr='', ncrit='1.5d3', n_min=5, n_max=1000, verbose=False, location=LOCALDIR): """ Loads the bn values from the CRRL models. :param te: Electron temperature of the model. :type te: string :param ne: Electron density of the model. :type ne: string :param other: Radiation field of the model or any other string with model characteristics. :type other: string :param verbose: Verbose output? :type verbose: bool :returns: The :math:`b_{n}` value for the given model conditions. :rtype: array """ #LOCALDIR = os.path.dirname(os.path.realpath(__file__)) if tr == '-' or tr == '' or tr == 0: model_file = 'Carbon_opt_T_{0}_ne_{1}_ncrit_{2}_vriens_delta_500_vrinc_nmax_9900_dat'.format( te, ne, ncrit) if verbose: print("Loading {0}".format(model_file)) else: model_file = 'Carbon_opt_T_{0}_ne_{1}_ncrit_{2}_{3}_vriens_delta_500_vrinc_nmax_9900_dat'.format( te, ne, ncrit, tr) if verbose: print("Loading {0}".format(model_file)) model_path = glob.glob('{0}/{1}'.format(location, model_file))[0] if verbose: print("Loaded {0}".format(model_path)) bn = np.loadtxt(model_path) nimin = best_match_indx(n_min, bn[:, 0]) nimax = best_match_indx(n_max, bn[:, 0]) bn = bn[nimin:nimax + 1] return bn
def load_bn_h(te, ne, other='', n_min=5, n_max=1000, verbose=False): """ Loads the bn values from the HRRL models. :param te: Electron temperature of the model. :type te: string :param ne: Electron density of the model. :type ne: string :param other: Radiation field of the model or any other string with model characteristics. :type other: string :param verbose: Verbose output? :type verbose: bool :returns: The :math:`b_{n}` value for the given model conditions. :rtype: array """ #LOCALDIR = os.path.dirname(os.path.realpath(__file__)) if other == '-' or other == '': mod_file = 'H_bn2/Hydrogen_opt_T_{1}_ne_{2}_ncrit_8d2_vriens_delta_500_vrinc_nmax_9900_dat'.format( LOCALDIR, te, ne) if verbose: print("Loading {0}".format(mod_file)) mod_file = glob.glob( '{0}/H_bn2/Hydrogen_opt_T_{1}_ne_{2}*_ncrit_8d2_vriens_delta_500_vrinc_nmax_9900_dat' .format(LOCALDIR, te, ne))[0] else: mod_file = 'H_bn2/Hydrogen_opt_T_{1}_ne_{2}_ncrit_8d2_{3}_vriens_delta_500_vrinc_nmax_9900_dat'.format( LOCALDIR, te, ne, other) if verbose: print("Loading {0}".format(mod_file)) mod_file = glob.glob( '{0}/H_bn2/Hydrogen_opt_T_{1}_ne_{2}*_ncrit_8d2_{3}_vriens_delta_500_vrinc_nmax_9900_dat' .format(LOCALDIR, te, ne, other))[0] if verbose: print("Loaded {0}".format(mod_file)) bn = np.loadtxt(mod_file) nimin = best_match_indx(n_min, bn[:, 0]) nimax = best_match_indx(n_max, bn[:, 0]) bn = bn[nimin:nimax + 1] return bn
def mask_cube_(vel, data, vel_rngs, offset=10.): """ """ logger = logging.getLogger(__name__) vel_indx = np.empty(vel_rngs.shape, dtype=int) mdata = deepcopy(data) mdata = np.ma.masked_invalid(mdata) for i, velrng in enumerate(vel_rngs): vel_indx[i][0] = utils.best_match_indx(velrng[0], vel) vel_indx[i][1] = utils.best_match_indx(velrng[1], vel) logger.info('Channels selected to be masked: {0} -- {1}'.format( vel_indx[i][0], vel_indx[i][1] + 1)) mdata[vel_indx[i][0]:vel_indx[i][1] + 1].mask = True return mdata
def mask_cube(vel, data, vel_rngs, offset=10.): """ """ logger = logging.getLogger(__name__) vel_indx = np.empty(vel_rngs.shape, dtype=int) nvel_indx = np.empty(vel_rngs.shape, dtype=int) chns = np.zeros(len(vel_rngs)) nchns = np.zeros(len(vel_rngs)) nvel = [] extend = nvel.extend logger.info("Velocity ranges for masking: ", vel_rngs) for i, velrng in enumerate(vel_rngs): vel_indx[i][0] = utils.best_match_indx(velrng[0], vel) vel_indx[i][1] = utils.best_match_indx(velrng[1], vel) nvel.extend(vel[vel_indx[i][0]:vel_indx[i][1] + 1]) nvel_indx[i][0] = utils.best_match_indx(velrng[0], nvel) nvel_indx[i][1] = utils.best_match_indx(velrng[1], nvel) chns[i] = vel_indx[i][1] - vel_indx[i][0] + 1 nchns[i] = nvel_indx[i][1] - nvel_indx[i][0] + 1 mdata = np.ones(((int(sum(chns)), ) + data.shape[1:])) * offset logger.info('Masked data shape: {0}'.format(mdata.shape)) logger.info('Will select the unmasked data.') for i in range(len(vel_indx)): mdata[nvel_indx[i][0]:nvel_indx[i][1] + 1] = data[vel_indx[i][0]:vel_indx[i][1] + 1] return nvel, mdata
def pressure_broad_coefs(Te): """ Defines the values of the constants :math:`a` and :math:`\\gamma` that go into the collisional broadening formula of Salgado et al. (2017). :param Te: Electron temperature. :type Te: float :returns: The values of :math:`a` and :math:`\\gamma`. :rtype: list """ te = [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000 ] te_indx = utils.best_match_indx(Te, te) a = [ -10.974098, -10.669695, -10.494541, -10.370271, -10.273172, -10.191374, -10.124309, -10.064037, -10.010153, -9.9613006, -9.6200366, -9.4001678, -9.2336349, -9.0848840, -8.9690170, -8.8686695, -8.7802238, -8.7012421, -8.6299908, -8.2718376, -8.0093937, -7.8344941, -7.7083367, -7.6126791, -7.5375720, -7.4770500, -7.4272885, -7.3857095, -7.1811733, -7.1132522 ] gammac = [ 5.4821631, 5.4354009, 5.4071360, 5.3861013, 5.3689105, 5.3535398, 5.3409679, 5.3290318, 5.3180304, 5.3077770, 5.2283700, 5.1700702, 5.1224893, 5.0770049, 5.0408369, 5.0086342, 4.9796105, 4.9532071, 4.9290080, 4.8063682, 4.7057576, 4.6356118, 4.5831746, 4.5421547, 4.5090104, 4.4815675, 4.4584053, 4.4385507, 4.3290786, 4.2814240 ] a_func = interpolate.interp1d(te, a, kind='linear', bounds_error=True, fill_value=0.0) g_func = interpolate.interp1d(te, gammac, kind='linear', bounds_error=True, fill_value=0.0) return [a_func(Te), g_func(Te)]
def lookup_freq(n, line): """ Returns the frequency of a line given the transition number n. :param n: Principal quantum number to look up for. :type n: int :param line: Line for which the frequency is desired. :type line: string :returns: Frequency of line(n). :rtype: float """ qns, freqs = load_ref(line) indx = utils.best_match_indx(n, qns) return freqs[indx]
def get_line_mask2(freq, reffreq, dv): """ Return a mask with ranges where a line is expected in the given frequency range for \ a line with a given reference frequency and line width dv. :param freq: Frequency axis where the line is located. :type freq: numpy array or list :param reffreq: Reference frequency for the line. :type reffreq: float :param dv: Velocity range to mask. :type dv: float, km/s :returns: Mask centered at the line center and width `dv0` referenced to the input `freq`. """ df = dv2df(reffreq, dv * 1e3) df_chan = utils.get_min_sep(freq) f0_indx = utils.best_match_indx(reffreq, np.asarray(freq)) f_mini = int(f0_indx - df / df_chan) if f_mini < 0: f_mini = 0 f_maxi = int(f0_indx + df / df_chan) return [f_mini, f_maxi]
def stack_cubes(cubes, outfits, vmax, vmin, dv, weight_list=None, v_axis=3, overwrite=False, algo='channel'): """ """ logger = logging.getLogger(__name__) cubel = cubes #glob.glob(cubes) logger.debug(cubel) if dv == 0: logger.info('Velocity width not specified.'), logger.info('Will try to determine from input data.') for i, cube in enumerate(cubel): hdu = fits.open(cube) head = hdu[0].header x = crrls.get_axis(head, v_axis) if i == 0: dv = utils.get_min_sep(x) vmax_min = max(x) vmin_max = min(x) else: dv = max(dv, utils.get_min_sep(x)) vmax_min = min(vmax_min, max(x)) vmin_max = max(vmin_max, min(x)) logger.debug('Cube: {0}'.format(cube)) logger.debug('Cube velocity limits: {0} {1} {2}'.format( min(x), max(x), utils.get_min_sep(x))) logger.info('Will use a velocity width of {0}'.format(dv)) # Check velocity ranges to avoid latter crashes. if vmax_min < vmax: logger.info('Requested maximum velocity is larger '\ 'than one of the cubes velocity axis.') #logger.info('Conflicting cube: {0}'.format(cube)) logger.info('v_max={0}, v_max_min={1}'.format(vmax, vmax_min)) logger.info('Will now exit') # sys.exit(1) if vmin_max > vmin: logger.info('Requested minimum velocity is smaller '\ 'than one of the cubes velocity axis.') #logger.info('Conflicting cube: {0}'.format(cube)) logger.info('v_min={0}, v_min_max={1}'.format(vmin, vmin_max)) logger.info('Will now exit') # sys.exit(1) shape = hdu[0].shape if len(shape) > 3: logger.info('Will drop first axis.') s = 1 else: s = 0 nvaxis = np.arange(vmin, vmax + dv, dv) stack = np.ma.zeros((len(nvaxis), ) + shape[s + 1:]) covrg = np.zeros((len(nvaxis), ) + shape[s + 1:]) # Check if there is a weight list if weight_list: try: wl = np.loadtxt(weight_list, dtype=[('fits', np.str_, 256), ('w', np.float64)]) except ValueError: wl = np.loadtxt(weight_list, dtype=[('fits', np.str_, 256), ('w', np.str_, 256)]) weight = np.ma.zeros((len(nvaxis), ) + shape[s + 1:]) logger.info('Output stack will have dimensions: {0}'.format(stack.shape)) for i, cube in enumerate(cubel): logger.info('Adding cube {0}/{1}'.format(i, len(cubel) - 1)) # Load the data hdu = fits.open(cube) head = hdu[0].header data = np.ma.masked_invalid(hdu[0].data) # Check the weight if weight_list: w = wl['w'][np.where(wl['fits'] == cube)] else: w = 1 logger.info('Will use a weight of {0}.'.format(w)) try: aux_weight = np.ones(stack.shape) * w except TypeError: w = np.ma.masked_invalid(fits.open(w[0])[0].data) #aux_weight = np.ones(shape[s+1:])*w if len(data.shape) > 3: logger.info('Will drop first axis.') data = data[0] # Get the cube axes ra = crrls.get_axis(head, 1) de = crrls.get_axis(head, 2) ve = crrls.get_axis(head, v_axis) logger.info('RA axis limits: {0} {1}'.format(min(ra), max(ra))) logger.info('DEC axis limits: {0} {1}'.format(min(de), max(de))) logger.info('VEL axis limits: {0} {1}'.format(min(ve), max(ve))) vmin_idx = utils.best_match_indx(vmin, ve) vmax_idx = utils.best_match_indx(vmax, ve) [vmin_idx, vmax_idx] = sorted([vmin_idx, vmax_idx]) if vmin_idx > 0: vmin_idx -= 1 if vmax_idx < len(ve): vmax_idx += 1 data_ = data[vmin_idx:vmax_idx] ve_ = ve[vmin_idx:vmax_idx] # Check that the axes are in ascending order if ve_[0] > ve_[1]: vs = -1 else: vs = 1 if ra[0] > ra[1]: vr = -1 else: vr = 1 # Interpolate the data interp = RegularGridInterpolator((ve_[::vs], de, ra[::vr]), data_[::vs, :, ::vr]) # Add the data to the stack if 'vector' in algo.lower(): logger.info('Will use one vector to reconstruct the cube') pts = np.array([[nvaxis[k], de[j], ra[i]] \ for k in range(len(nvaxis)) \ for j in range(len(de)) \ for i in range(len(ra))]) aux_weight = np.ones(stack.shape) * w stack += interp(pts).reshape(stack.shape) * aux_weight elif 'channel' in algo.lower(): logger.info('Will reconstruct the cube one channel at a time') for k in range(len(nvaxis)): pts = np.array([[nvaxis[k], de[j], ra[i]] \ for j in range(len(de)) \ for i in range(len(ra[::vr]))]) newshape = shape[s + 1:] # Only spatial dimensions aux_weight = np.ones(newshape) * w aux_cov = np.ones(newshape) try: stack_ = np.ma.masked_invalid( interp(pts).reshape(newshape) * aux_weight) stack_.fill_value = 0. aux_weight[stack_.mask] = 0 aux_cov[stack_.mask] = 0 weight[k] += aux_weight covrg[k] += aux_cov stack[k] += stack_.filled() except ValueError: logger.info('Point outside range: ' \ 'vel={0}, ra={1}..{2}, dec={3}..{4}'.format(pts[0,0], min(pts[:,2]), max(pts[:,2]), min(pts[:,1]), max(pts[:,1]))) else: logger.info('Cube reconstruction algorithm unrecognized.') logger.info('Will exit now.') sys.exit(1) # Divide by the number of input cubes to get the mean stack = stack / weight stack.fill_value = np.nan weight.fill_value = np.nan # Write to a fits file hdulist = fits.PrimaryHDU(stack.filled()) # Copy the header from the first channel image hdulist.header = fits.open(cubel[0])[0].header.copy() hdulist.header['CTYPE3'] = 'VELO' hdulist.header['CRVAL3'] = nvaxis[0] hdulist.header['CDELT3'] = dv hdulist.header['CRPIX3'] = 1 hdulist.header['CUNIT3'] = 'm/s' hdulist.writeto(outfits, overwrite=overwrite) stack_head = hdulist.header.copy() hdulist = fits.PrimaryHDU(weight.filled()) hdulist.header = stack_head hdulist.header['CTYPE3'] = 'VELO' hdulist.header['CRVAL3'] = nvaxis[0] hdulist.header['CDELT3'] = dv hdulist.header['CRPIX3'] = 1 hdulist.header['CUNIT3'] = 'm/s' hdulist.writeto(outfits.split('.fits')[0] + '_weight.fits', overwrite=overwrite) hdulist = fits.PrimaryHDU(covrg) hdulist.header = stack_head hdulist.header['CTYPE3'] = 'VELO' hdulist.header['CRVAL3'] = nvaxis[0] hdulist.header['CDELT3'] = dv hdulist.header['CRPIX3'] = 1 hdulist.header['CUNIT3'] = 'm/s' hdulist.writeto(outfits.split('.fits')[0] + '_coverage.fits', overwrite=overwrite)
def stack_interpol(specs, output, vmax, vmin, dv, x_col, y_col, weight, weight_list=None, weight_list_cols='0,1'): """ """ logger = logging.getLogger(__name__) #specs = glob.glob(spec) # If only one file is passed, it probably contains the list #if len(specs) == 1: #specs = np.genfromtxt(specs[0], dtype=str) # Setup weight list columns, if need be. if weight_list: wlc0,wlc1 = list(map(int, weight_list_cols.split(','))) print(wlc0,wlc1) logger.info('Will use the following files as input: ') logger.info(specs) logger.info('The stack consists of {0} lines'.format(len(specs))) # Determine velocity resolution of stack. if dv == 0: logger.info('Will determine the velocity resolution of the stack.') for i,s in enumerate(specs): data = np.loadtxt(s) x = data[:,x_col] if i == 0: dv = utils.get_min_sep(x) else: dv = max(dv, utils.get_min_sep(x)) logger.info('Spectrum {0} has velocity resolution of: {1}'.format(s, utils.get_min_sep(x))) logger.info('Stack velocity resolution: {0}'.format(dv)) # Setup the grid for the stack. xgrid = np.arange(vmin, vmax, dv) ygrid = np.zeros(len(xgrid)) # the temperatures zgrid = np.zeros(len(xgrid)) # the number of tb points in every stacked channel for i,s in enumerate(specs): logger.info('Working on file: {0}'.format(s)) data = np.loadtxt(s) x = data[:,x_col] y = data[:,y_col] # Sort by velocity o = x.argsort() x = x[o] y = y[o] # Catch NaNs and invalid values: mask_x = np.ma.masked_equal(x, -9999).mask mask_y = np.isnan(y) mask = mask_x | mask_y # Interpolate non masked ranges indepently. my = np.ma.masked_where(mask, y) mx = np.ma.masked_where(mask, x) valid = np.ma.flatnotmasked_contiguous(mx) y_aux = np.zeros(len(xgrid)) if not isinstance(valid, slice): for j,rng in enumerate(valid): #print "slice {0}: {1}".format(i, rng) if len(x[rng]) > 1: interp_y = interpolate.interp1d(x[rng], y[rng], kind='linear', bounds_error=False, fill_value=0.0) y_aux += interp_y(xgrid) elif not np.isnan(x[rng]): #print x[rng] y_aux[utils.best_match_indx(x[rng], xgrid)] += y[rng] else: #print "slice: {0}".format(valid) interp_y = interpolate.interp1d(x[valid], y[valid], kind='linear', bounds_error=False, fill_value=0.0) y_aux += interp_y(xgrid) # Check which channels have data. ychan = [1 if ch != 0 else 0 for ch in y_aux] # Determine the weight. w = None if not weight: w = np.ones(len(xgrid)) elif weight == 'list': wl = np.loadtxt(weight_list, dtype=str) try: w = float(wl[:,wlc1][np.where(wl[:,wlc0] == s)[0][0]]) except IndexError: logger.error("Weight for spectrum {} not found in {} .".format(s, weight_list)) logger.error("Will not include it in the stack.") continue elif weight == 'sigma': w = 1./crrls.get_rms(my) elif weight == 'sigma2': w = 1./np.power(crrls.get_rms(my), 2) logger.info('Will use a weight of: {0}'.format(w)) # Stack! zgrid = zgrid + np.multiply(ychan, w) ygrid = ygrid + y_aux*np.multiply(ychan, w) # Divide by the total weight to preserve optical depth ygrid = np.divide(ygrid, zgrid) logger.info('Saving stack to: {0}'.format(output)) np.savetxt(output, np.c_[xgrid, ygrid, zgrid], header="x axis, " \ "stacked y axis, " \ "y axis weight")