def model_photo(self, tt_arr, zred=0.1, filters=None, bands=None): ''' very simple wrapper for a fsps model with minimal overhead. Generates photometry in specified photometric bands :param tt_arr: array of free parameters :param zred: redshift (default: 0.1) :param filters: speclite.filters filter object. Either filters or bands has to be specified. (default: None) :param bands: (optional) photometric bands to generate the photometry. Either bands or filters has to be specified. (default: None) :return outphoto: array of photometric fluxes in nanomaggies in the specified bands ''' if filters is None: if bands is not None: bands_list = self._get_bands(bands) # get list of bands filters = specFilter.load_filters(*tuple(bands_list)) else: raise ValueError("specify either filters or bands") w, spec = self.model(tt_arr, zred=zred) # get spectra maggies = filters.get_ab_maggies(spec * 1e-17*U.erg/U.s/U.cm**2/U.Angstrom, wavelength=w.flatten()*U.Angstrom) # maggies return np.array(list(maggies[0])) * 1e9
def __init__(self, nproc=1): from speclite import filters from scipy.spatial import KDTree self.nproc = nproc self.bgs_meta = read_basis_templates(objtype='BGS', onlymeta=True) self.elg_meta = read_basis_templates(objtype='ELG', onlymeta=True) self.lrg_meta = read_basis_templates(objtype='LRG', onlymeta=True) self.qso_meta = read_basis_templates(objtype='QSO', onlymeta=True) self.wd_da_meta = read_basis_templates(objtype='WD', subtype='DA', onlymeta=True) self.wd_db_meta = read_basis_templates(objtype='WD', subtype='DB', onlymeta=True) self.decamwise = filters.load_filters('decam2014-g', 'decam2014-r', 'decam2014-z', 'wise2010-W1', 'wise2010-W2') # Read all the stellar spectra and synthesize DECaLS/WISE fluxes. self.star_phot() #self.elg_phot() #self.elg_kcorr = read_basis_templates(objtype='ELG', onlykcorr=True) self.bgs_tree = KDTree(self._bgs()) self.elg_tree = KDTree(self._elg()) #self.lrg_tree = KDTree(self._lrg()) #self.qso_tree = KDTree(self._qso()) self.star_tree = KDTree(self._star()) self.wd_da_tree = KDTree(self._wd_da()) self.wd_db_tree = KDTree(self._wd_db())
def _get_decam_filters(): global decam_filters if decam_filters is None: log = get_logger() log.info("read decam filters") decam_filters = filters.load_filters("decam2014-g", "decam2014-r", "decam2014-z") return decam_filters
def __init__(self, library_file): """ holds all the observatories/instruments/filters :param library_file: """ # get the filter file with open(library_file) as f: self._library = yaml.load(f, Loader=yaml.SafeLoader) self._instruments = [] # create attributes which are lib.observatory.instrument # and the instrument attributes are speclite FilterResponse objects with warnings.catch_warnings(): warnings.simplefilter("ignore") print("Loading optical filters") for observatory, value in self._library.items(): # create a node for the observatory this_node = ObservatoryNode(value) # attach it to the object setattr(self, observatory, this_node) # now get the instruments for instrument, value2 in value.items(): # update the instruments self._instruments.append(instrument) # create the filter response via speclite filter_path = os.path.join( get_speclite_filter_path(), observatory, instrument ) filters_to_load = [ "%s-%s.ecsv" % (filter_path, filter) for filter in value2 ] this_filter = spec_filter.load_filters(*filters_to_load) # attach the filters to the observatory setattr(this_node, instrument, this_filter) self._instruments.sort()
def __init__(self, library_file): """ holds all the observatories/instruments/filters :param library_file: """ # get the filter file with open(library_file) as f: self._library = yaml.safe_load(f) self._instruments = [] # create attributes which are lib.observatory.instrument # and the instrument attributes are speclite FilterResponse objects with warnings.catch_warnings(): warnings.simplefilter("ignore") print('Loading optical filters') for observatory, value in self._library.iteritems(): # create a node for the observatory this_node = ObservatoryNode(value) # attach it to the object setattr(self, observatory, this_node) # now get the instruments for instrument, value2 in value.iteritems(): # update the instruments self._instruments.append(instrument) # create the filter response via speclite filter_path = os.path.join(get_speclite_filter_path(), observatory, instrument) filters_to_load = ["%s-%s.ecsv" % (filter_path, filter) for filter in value2] this_filter = spec_filter.load_filters(*filters_to_load) # attach the filters to the observatory setattr(this_node, instrument, this_filter) self._instruments.sort()
def ellipse_sbprofile(ellipsefit, band=('g', 'r', 'z'), refband='r', minerr=0.02, redshift=None, pixscale=0.262): """Convert ellipse-fitting results to a magnitude, color, and surface brightness profiles. """ if redshift: from astropy.cosmology import WMAP9 as cosmo smascale = pixscale / cosmo.arcsec_per_kpc_proper(redshift).value # [kpc/pixel] smaunit = 'kpc' else: smascale = 1.0 smaunit = 'pixels' indx = np.ones(len(ellipsefit[refband]), dtype=bool) sbprofile = dict() sbprofile['smaunit'] = smaunit sbprofile['sma'] = ellipsefit['r'].sma[indx] * smascale with np.errstate(invalid='ignore'): for filt in band: #area = ellipsefit[filt].sarea[indx] * pixscale**2 sbprofile['mu_{}'.format(filt)] = 22.5 - 2.5 * np.log10(ellipsefit[filt].intens[indx]) #sbprofile[filt] = 22.5 - 2.5 * np.log10(ellipsefit[filt].intens[indx]) sbprofile['mu_{}_err'.format(filt)] = ellipsefit[filt].int_err[indx] / \ ellipsefit[filt].intens[indx] / np.log(10) #sbprofile['mu_{}'.format(filt)] = sbprofile[filt] + 2.5 * np.log10(area) # Just for the plot use a minimum uncertainty #sbprofile['{}_err'.format(filt)][sbprofile['{}_err'.format(filt)] < minerr] = minerr sbprofile['gr'] = sbprofile['mu_g'] - sbprofile['mu_r'] sbprofile['rz'] = sbprofile['mu_r'] - sbprofile['mu_z'] sbprofile['gr_err'] = np.sqrt(sbprofile['mu_g_err']**2 + sbprofile['mu_r_err']**2) sbprofile['rz_err'] = np.sqrt(sbprofile['mu_r_err']**2 + sbprofile['mu_z_err']**2) # Just for the plot use a minimum uncertainty sbprofile['gr_err'][sbprofile['gr_err'] < minerr] = minerr sbprofile['rz_err'][sbprofile['rz_err'] < minerr] = minerr # Add the effective wavelength of each bandpass, although this needs to take # into account the DECaLS vs BASS/MzLS filter curves. from speclite import filters filt = filters.load_filters('decam2014-g', 'decam2014-r', 'decam2014-z', 'wise2010-W1', 'wise2010-W2') for ii, band in enumerate(('g', 'r', 'z', 'W1', 'W2')): sbprofile.update({'{}_wave_eff'.format(band): filt.effective_wavelengths[ii].value}) return sbprofile
def test_filter_set(): sf = spec_filters.load_filters('bessell-*') fs1 = FilterSet(sf) #sf = spec_filters.load_filter('bessell-r') #fs2 = FilterSet(sf) with pytest.raises(NotASpeclikeFilter): fs2 = FilterSet('a')
def test_filter_set(): sf = spec_filters.load_filters("bessell-*") fs1 = FilterSet(sf) # sf = spec_filters.load_filter('bessell-r') # fs2 = FilterSet(sf) with pytest.raises(NotASpeclikeFilter): fs2 = FilterSet("a")
def Photo_DESI(wave, spectra): ''' generate photometry by convolving the input spectrum with DECAM and WISE bandpasses: g, r, z, W1, W2, W3, W4 filters. :param wave: wavelength of input spectra in Angstroms. 2D array Nspec x Nwave. :param fluxes: fluxes of input spectra. This should be noiseless source spectra. 2D array Nspec x Nwave. In units of 10e-17 erg/s/cm2/A ''' wave = np.atleast_2d(wave) assert wave.shape[1] == spectra.shape[1] n_spec = spectra.shape[0] # number of spectra if wave.shape[0] == 1: wave = np.tile(wave, (n_spec, 1)) from astropy import units as U # load DECAM g, r, z and WISE W1-4 filter_response = specFilter.load_filters('decam2014-g', 'decam2014-r', 'decam2014-z', 'wise2010-W1', 'wise2010-W2', 'wise2010-W3', 'wise2010-W4') # apply filters fluxes = np.zeros((n_spec, 7)) # photometric flux in nanomaggies for i in range(n_spec): spectrum = spectra[i] # apply filters flux = np.array( filter_response.get_ab_maggies( np.atleast_2d(spectrum) * 1e-17 * U.erg / U.s / U.cm**2 / U.Angstrom, wave[i, :] * U.Angstrom)) # convert to nanomaggies fluxes[i, :] = 1e9 * np.array([ flux[0][0], flux[0][1], flux[0][2], flux[0][3], flux[0][4], flux[0][5], flux[0][6] ]) # calculate magnitudes (not advised due to NaNs) mags = 22.5 - 2.5 * np.log10(fluxes) return fluxes, mags
def __init__(self, lam, flam, family='sdss2010-*', axis=0, redshift=None): self.filters = filters.load_filters(family) if redshift is not None: self.filters = filters.FilterSequence( [f_.create_shifted(band_shift=redshift) for f_ in self.filters]) else: pass # spectral dimension has to be the final one nl0 = flam.shape[axis] flam, self.lam = self.filters.pad_spectrum( spectrum=np.moveaxis(flam, axis, -1), wavelength=lam, method='zero') self.flam = np.moveaxis(flam, -1, axis) if self.flam.shape[axis] > nl0: warn('spectrum has been padded, bad mags possible', Spec2PhotWarning) self.ABmags = self.filters.get_ab_magnitudes( spectrum=self.flam, wavelength=self.lam, axis=axis).as_array()
def __init__(self, lam, flam, family='sdss2010-*', axis=0, redshift=None): self.filters = filters.load_filters(family) if redshift is not None: self.filters = filters.FilterSequence([ f_.create_shifted(band_shift=redshift) for f_ in self.filters ]) else: pass # spectral dimension has to be the final one nl0 = flam.shape[axis] flam, self.lam = self.filters.pad_spectrum(spectrum=np.moveaxis( flam, axis, -1), wavelength=lam, method='zero') self.flam = np.moveaxis(flam, -1, axis) if self.flam.shape[axis] > nl0: warn('spectrum has been padded, bad mags possible', Spec2PhotWarning) self.ABmags = self.filters.get_ab_magnitudes(spectrum=self.flam, wavelength=self.lam, axis=axis).as_array()
def PCA_stellar_mass(dapall, plateifu=None, filename=None, vec_file=None, vec_data=None, pca_data_dir=None, goodfrac_channel=2, goodfrac_thresh=.0001, use_mask=True): """ Return absolute AB Magnitude in filter provided Parameters ---------- dapall: 'Table', 'dict' DAPALL file data plateifu: 'str', list, optional, must be keyword plate-ifu of galaxy desired filename: 'str', optional, must be keyword pca data file to read in, ignores plateifu if provided vec_file: 'str', optional, must be keyword pca data file containing eigenvectors vec_data: 'tuple', optional, must be keyword eigenvector data (mean_spec, evec_spec, lam_spec) """ if filename is None: if plateifu is None: plateifu = dapall["plateifu"] else: filename = os.path.join(pca_data_dir, plateifu, "{}_res.fits".format(plateifu)) filter_obs = filters.load_filters("sdss2010-i") i_mag_abs = PCA_mag(dapall, filter_obs, plateifu=plateifu, filename=filename, vec_file=vec_file, vec_data=vec_data, pca_data_dir=pca_data_dir) sun_i = absmag_sun_band["i"] * u.ABmag i_sol_lum = 10**(-0.4 * (i_mag_abs - sun_i).value) # Load PCA Data for Galaxy if filename is None: filename = os.path.join(pca_data_dir, plateifu, "{}_res.fits".format(plateifu)) with fits.open(filename) as pca_data: MLi = pca_data["MLi"].data mask = pca_data["MASK"].data.astype(bool) goodfrac = pca_data["GOODFRAC"].data[goodfrac_channel] m_star = i_sol_lum * 10**MLi mask = mask | (goodfrac < goodfrac_thresh) mask_shaped = np.zeros_like(m_star, dtype=bool) if mask_shaped.shape[0] == 3: mask_shaped[0, :, :] = mask mask_shaped[1, :, :] = mask mask_shaped[1, :, :] = mask else: mask_shaped = mask m_star_masked = np.ma.masked_array(m_star * u.solMass, mask=mask_shaped) return m_star_masked
def PCA_mag(dapall, filter_obs, plateifu=None, filename=None, vec_file=None, vec_data=None, pca_data_dir=None): """ Return absolute AB Magnitude in filter provided Parameters ---------- dapall: 'Table', 'dict' DAPALL file data filter_obs: 'str', 'speclite.filters.FilterSequence' observational filter to use plateifu: 'str', list, optional, must be keyword plate-ifu of galaxy desired filename: 'str', optional, must be keyword pca data file to read in, ignores plateifu if provided vec_file: 'str', optional, must be keyword pca data file containing eigenvectors vec_data: 'tuple', optional, must be keyword eigenvector data (mean_spec, evec_spec, lam_spec) """ # Check filter status # CHECK PLATEIFU if plateifu is None: plateifu = dapall["plateifu"] if filter_obs.__class__ is not filters.FilterSequence: if filter_obs in ["GALEX-NUV", "GALEX-FUV"]: wav, resp = np.loadtxt( "{}/data/GALEX_GALEX.NUV.dat".format(directory)).T galex_nuv = filters.FilterResponse(wavelength=wav * u.Angstrom, response=resp, meta=dict(group_name='GALEX', band_name='NUV')) wav, resp = np.loadtxt( "{}/data/GALEX_GALEX.FUV.dat".format(directory)).T galex_fuv = filters.FilterResponse(wavelength=wav * u.Angstrom, response=resp, meta=dict(group_name='GALEX', band_name='FUV')) try: filter_obs = filters.load_filters(filter_obs) except ValueError: logging.warnings("Invalid filter, using default of 'sdss2010-i'") filter_obs = filters.load_filters("sdss2010-i") if filename is None: if plateifu is None: raise ValueError("No input file or plateifu provided") else: filename = os.path.join(pca_data_dir, plateifu, "{}_res.fits".format(plateifu)) spectrum, wlen = PCA_Spectrum(plateifu=plateifu, filename=filename, vec_file=vec_file, vec_data=vec_data, pca_data_dir=pca_data_dir) mag = filter_obs.get_ab_magnitudes( spectrum, wlen, axis=0)[filter_obs.names[0]].data * u.ABmag mag_abs = mag - WMAP9.distmod(dapall["nsa_zdist"]) return mag_abs
def get_spectra(lyafile, nqso=None, wave=None, templateid=None, normfilter='sdss2010-g', seed=None, rand=None, qso=None, add_dlas=False, debug=False, nocolorcuts=True): """Generate a QSO spectrum which includes Lyman-alpha absorption. Args: lyafile (str): name of the Lyman-alpha spectrum file to read. nqso (int, optional): number of spectra to generate (starting from the first spectrum; if more flexibility is needed use TEMPLATEID). wave (numpy.ndarray, optional): desired output wavelength vector. templateid (int numpy.ndarray, optional): indices of the spectra (0-indexed) to read from LYAFILE (default is to read everything). If provided together with NQSO, TEMPLATEID wins. normfilter (str, optional): normalization filter seed (int, optional): Seed for random number generator. rand (numpy.RandomState, optional): RandomState object used for the random number generation. If provided together with SEED, this optional input superseeds the numpy.RandomState object instantiated by SEED. qso (desisim.templates.QSO, optional): object with which to generate individual spectra/templates. add_dlas (bool): Inject damped Lya systems into the Lya forest These are done according to the current best estimates for the incidence dN/dz (Prochaska et al. 2008, ApJ, 675, 1002) Set in calc_lz These are *not* inserted according to overdensity along the sightline nocolorcuts (bool, optional): Do not apply the fiducial rzW1W2 color-cuts cuts (default True). Returns (flux, wave, meta, dla_meta) where: * flux (numpy.ndarray): Array [nmodel, npix] of observed-frame spectra (erg/s/cm2/A). * wave (numpy.ndarray): Observed-frame [npix] wavelength array (Angstrom). * meta (astropy.Table): Table of meta-data [nmodel] for each output spectrum with columns defined in desisim.io.empty_metatable *plus* RA, DEC. * objmeta (astropy.Table): Table of additional object-specific meta-data [nmodel] for each output spectrum with columns defined in desisim.io.empty_metatable. * dla_meta (astropy.Table): Table of meta-data [ndla] for the DLAs injected into the spectra. Only returned if add_dlas=True Note: `dla_meta` is only included if add_dlas=True. """ from scipy.interpolate import interp1d import fitsio from speclite.filters import load_filters from desisim.templates import QSO from desisim.io import empty_metatable h = fitsio.FITS(lyafile) if templateid is None: if nqso is None: nqso = len(h) - 1 templateid = np.arange(nqso) else: templateid = np.asarray(templateid) nqso = len(np.atleast_1d(templateid)) if rand is None: rand = np.random.RandomState(seed) templateseed = rand.randint(2**32, size=nqso) #heads = [head.read_header() for head in h[templateid + 1]] heads = [] for indx in templateid: heads.append(h[indx + 1].read_header()) zqso = np.array([head['ZQSO'] for head in heads]) ra = np.array([head['RA'] for head in heads]) dec = np.array([head['DEC'] for head in heads]) mag_g = np.array([head['MAG_G'] for head in heads]) # Hard-coded filtername! Should match MAG_G! normfilt = load_filters(normfilter) if qso is None: qso = QSO(normfilter_south=normfilter, wave=wave) wave = qso.wave flux = np.zeros([nqso, len(wave)], dtype='f4') meta, objmeta = empty_metatable(objtype='QSO', nmodel=nqso) meta['TEMPLATEID'][:] = templateid meta['REDSHIFT'][:] = zqso meta['MAG'][:] = mag_g meta['MAGFILTER'][:] = normfilter meta['SEED'][:] = templateseed meta['RA'] = ra meta['DEC'] = dec # Lists for DLA meta data if add_dlas: dla_NHI, dla_z, dla_id = [], [], [] # Loop on quasars for ii, indx in enumerate(templateid): flux1, _, meta1, objmeta1 = qso.make_templates( nmodel=1, redshift=np.atleast_1d(zqso[ii]), mag=np.atleast_1d(mag_g[ii]), seed=templateseed[ii], nocolorcuts=nocolorcuts, lyaforest=False) flux1 *= 1e-17 for col in meta1.colnames: meta[col][ii] = meta1[col][0] for col in objmeta1.colnames: objmeta[col][ii] = objmeta1[col][0] # read lambda and forest transmission data = h[indx + 1].read() la = data['LAMBDA'][:] tr = data['FLUX'][:] if len(tr): # Interpolate the transmission at the spectral wavelengths, if # outside the forest, the transmission is 1. itr = interp1d(la, tr, bounds_error=False, fill_value=1.0) flux1 *= itr(wave) # Inject a DLA? if add_dlas: if np.min(wave / lambda_RF_LYA - 1) < zqso[ii]: # Any forest? dlas, dla_model = insert_dlas(wave, zqso[ii], seed=templateseed[ii]) ndla = len(dlas) if ndla > 0: flux1 *= dla_model # Meta dla_z += [idla['z'] for idla in dlas] dla_NHI += [idla['N'] for idla in dlas] dla_id += [indx] * ndla padflux, padwave = normfilt.pad_spectrum(flux1, wave, method='edge') normmaggies = np.array( normfilt.get_ab_maggies(padflux, padwave, mask_invalid=True)[normfilter]) factor = 10**(-0.4 * mag_g[ii]) / normmaggies flux1 *= factor for key in ('FLUX_G', 'FLUX_R', 'FLUX_Z', 'FLUX_W1', 'FLUX_W2'): meta[key][ii] *= factor flux[ii, :] = flux1[:] h.close() # Finish if add_dlas: ndla = len(dla_id) if ndla > 0: from astropy.table import Table dla_meta = Table() dla_meta['NHI'] = dla_NHI # log NHI values dla_meta['z'] = dla_z dla_meta['ID'] = dla_id else: dla_meta = None return flux * 1e17, wave, meta, objmeta, dla_meta else: return flux * 1e17, wave, meta, objmeta
def get_spectra(lyafile, nqso=None, wave=None, templateid=None, normfilter='sdss2010-g', seed=None, rand=None, qso=None, nocolorcuts=False): '''Generate a QSO spectrum which includes Lyman-alpha absorption. Args: lyafile (str): name of the Lyman-alpha spectrum file to read. nqso (int, optional): number of spectra to generate (starting from the first spectrum; if more flexibility is needed use TEMPLATEID). wave (numpy.ndarray, optional): desired output wavelength vector. templateid (int numpy.ndarray, optional): indices of the spectra (0-indexed) to read from LYAFILE (default is to read everything). If provided together with NQSO, TEMPLATEID wins. normfilter (str, optional): normalization filter seed (int, optional): Seed for random number generator. rand (numpy.RandomState, optional): RandomState object used for the random number generation. If provided together with SEED, this optional input superseeds the numpy.RandomState object instantiated by SEED. qso (desisim.templates.QSO, optional): object with which to generate individual spectra/templates. nocolorcuts (bool, optional): Do not apply the fiducial rzW1W2 color-cuts cuts (default False). Returns: flux (numpy.ndarray): Array [nmodel, npix] of observed-frame spectra (erg/s/cm2/A). wave (numpy.ndarray): Observed-frame [npix] wavelength array (Angstrom). meta (astropy.Table): Table of meta-data [nmodel] for each output spectrum with columns defined in desisim.io.empty_metatable *plus* RA, DEC. ''' import numpy as np from scipy.interpolate import interp1d import fitsio from speclite.filters import load_filters from desisim.templates import QSO from desisim.io import empty_metatable h = fitsio.FITS(lyafile) if templateid is None: if nqso is None: nqso = len(h) - 1 templateid = np.arange(nqso) else: templateid = np.asarray(templateid) nqso = len(templateid) if rand is None: rand = np.random.RandomState(seed) templateseed = rand.randint(2**32, size=nqso) #heads = [head.read_header() for head in h[templateid + 1]] heads = [] for indx in templateid: heads.append(h[indx + 1].read_header()) zqso = np.array([head['ZQSO'] for head in heads]) ra = np.array([head['RA'] for head in heads]) dec = np.array([head['DEC'] for head in heads]) mag_g = np.array([head['MAG_G'] for head in heads]) # Hard-coded filtername! normfilt = load_filters(normfilter) if qso is None: qso = QSO(normfilter=normfilter, wave=wave) wave = qso.wave flux = np.zeros([nqso, len(wave)], dtype='f4') meta = empty_metatable(objtype='QSO', nmodel=nqso) meta['TEMPLATEID'] = templateid meta['REDSHIFT'] = zqso meta['MAG'] = mag_g meta['SEED'] = templateseed meta['RA'] = ra meta['DEC'] = dec for ii, indx in enumerate(templateid): flux1, _, meta1 = qso.make_templates(nmodel=1, redshift=np.atleast_1d(zqso[ii]), mag=np.atleast_1d(mag_g[ii]), seed=templateseed[ii], nocolorcuts=nocolorcuts) flux1 *= 1e-17 for col in meta1.colnames: meta[col][ii] = meta1[col][0] # read lambda and forest transmission data = h[indx + 1].read() la = data['LAMBDA'][:] tr = data['FLUX'][:] if len(tr): # Interpolate the transmission at the spectral wavelengths, if # outside the forest, the transmission is 1. itr = interp1d(la, tr, bounds_error=False, fill_value=1.0) flux1 *= itr(wave) padflux, padwave = normfilt.pad_spectrum(flux1, wave, method='edge') normmaggies = np.array( normfilt.get_ab_maggies(padflux, padwave, mask_invalid=True)[normfilter]) factor = 10**(-0.4 * mag_g[ii]) / normmaggies flux1 *= factor for key in ('FLUX_G', 'FLUX_R', 'FLUX_Z', 'FLUX_W1', 'FLUX_W2'): meta[key][ii] *= factor flux[ii, :] = flux1[:] h.close() return flux, wave, meta
def mag_ab(wavelength, spectrum, filters, *, redshift=None, coefficients=None, distmod=None, interpolate=1000): r'''Compute AB magnitudes from spectra and filters. This function takes *emission* spectra and observation filters and computes bandpass AB magnitudes [1]_. The filter specification in the `filters` argument is passed unchanged to `speclite.filters.load_filters`. See there for the syntax, and the list of supported values. The emission spectra can optionally be redshifted. If the `redshift` parameter is given, the output array will have corresponding axes. If the `interpolate` parameter is not `False`, at most that number of redshifted spectra are computed, and the remainder is interpolated from the results. The spectra can optionally be combined. If the `coefficients` parameter is given, its shape must match `spectra`, and the corresponding axes are contracted using a product sum. If the spectra are redshifted, the coefficients array can contain axes for each redshift. By default, absolute magnitudes are returned. To compute apparent magnitudes instead, provide the `distmod` argument with the distance modulus for each redshift. The distance modulus is applied after redshifts and coefficients and should match the shape of the `redshift` array. Parameters ---------- wavelength : (nw,) `~astropy.units.Quantity` or array_like Wavelength array of the spectrum. spectrum : ([ns,] nw,) `~astropy.units.Quantity` or array_like Emission spectrum. Can be multidimensional for computing with multiple spectra of the same wavelengths. The last axis is the wavelength axis. filters : str or list of str Filter specification, loaded filters are array_like of shape (nf,). redshift : (nz,) array_like, optional Optional array of redshifts. Can be multidimensional. coefficients : ([nz,] [ns,]) array_like Optional coefficients for combining spectra. Axes must be compatible with all redshift and spectrum dimensions. distmod : (nz,) array_like, optional Optional distance modulus for each redshift. Shape must be compatible with redshift dimensions. interpolate : int or `False`, optional Maximum number of redshifts to compute explicitly. Default is `1000`. Returns ------- mag_ab : ([nz,] [ns,] nf,) array_like The AB magnitude of each redshift (if given), each spectrum (if not combined), and each filter. Warnings -------- The :mod:`speclite` package must be installed to use this function. References ---------- .. [1] M. R. Blanton et al., 2003, AJ, 125, 2348 ''' from speclite.filters import load_filters # load the filters if np.ndim(filters) == 0: filters = (filters, ) filters = load_filters(*filters) if np.shape(filters) == (1, ): filters = filters[0] # number of dimensions for each input nd_s = len(np.shape(spectrum)[:-1]) # last axis is spectral axis nd_f = len(np.shape(filters)) nd_z = len(np.shape(redshift)) # check if interpolation is necessary if interpolate and np.size(redshift) <= interpolate: interpolate = False # if interpolating, split the redshift range into `interpolate` bits if interpolate: redshift_ = np.linspace(np.min(redshift), np.max(redshift), interpolate) else: redshift_ = redshift if redshift is not None else 0 # working array shape m_shape = np.shape(redshift_) + np.shape(spectrum)[:-1] + np.shape(filters) # compute AB maggies for every redshift, spectrum, and filter m = np.empty(m_shape) for i, z in np.ndenumerate(redshift_): for j, f in np.ndenumerate(filters): # create a shifted filter for redshift fs = f.create_shifted(z) m[i + (..., ) + j] = fs.get_ab_maggies(spectrum, wavelength) # if interpolating, compute the full set of redshifts if interpolate: # diy interpolation keeps memory use to a minimum dm = np.diff(m, axis=0, append=m[-1:]) u, n = np.modf( np.interp(redshift, redshift_, np.arange(redshift_.size))) n = n.astype(int) u = u.reshape(u.shape + (1, ) * (nd_s + nd_f)) m = np.ascontiguousarray(m[n]) m += u * dm[n] del (dm, n, u) # combine spectra if asked to if coefficients is not None: # contraction over spectrum axes (`nd_z` to `nd_z+nd_s`) if nd_z == 0: m = np.matmul(coefficients, m) else: c = np.reshape(coefficients, np.shape(coefficients) + (1, ) * nd_f) m = np.sum(m * c, axis=tuple(range(nd_z, nd_z + nd_s))) # no spectrum axes left nd_s = 0 # convert maggies to magnitudes np.log10(m, out=m) m *= -2.5 # apply the redshift K-correction if necessary if redshift is not None: kcorr = -2.5 * np.log10(1 + redshift) m += np.reshape(kcorr, kcorr.shape + (1, ) * (nd_s + nd_f)) # add distance modulus if given if distmod is not None: m += np.reshape(distmod, np.shape(distmod) + (1, ) * (nd_s + nd_f)) # done return m
# [$10^{-17} erg/cm^{2}/s/\AA/arcsec^2$] return twi_wave, Isky if __name__ == '__main__': import pylab as pl start = time.time() # specsim. config = specsim.config.load_config('desi') simulator = specsim.simulator.Simulator('desi') simulator.simulate() rfilter = filters.load_filters('decam2014-r') gfa_files = glob.glob( '/global/cfs/cdirs/desi/users/ameisner/GFA/conditions/offline_all_guide_ccds_SV1-thru_*.fits' ) gfas = Table() for x in gfa_files: # gfas = resource_filename('bgs-cmxsv', 'dat/offline_all_guide_ccds_SV1-thru_20210111.fits') x = Table.read(x) gfas = vstack((gfas, x)) # gfas = gfas[gfas['N_SOURCES_FOR_PSF'] > 2] gfas = gfas[gfas['CONTRAST'] > 2]
def Lgal_noisePhoto(lib='bc03', dust=False, overwrite=False, validate=False): ''' generate photometry from noiseless spectral_challenge Lgal spectra generated by Rita then add realistic noise from Legacy survey. ''' fsource = f_nonoise(4, lib=lib) fsource = os.path.basename(fsource).rsplit('.', 1)[0].rsplit('_BGS_template_')[1] if dust: str_dust = 'dust' else: str_dust = 'nodust' fphot = os.path.join(UT.dat_dir(), 'spectral_challenge', 'bgs', 'mag.%s.%s.legacy_noise.dat' % (fsource, str_dust)) if os.path.isfile(fphot) and not overwrite: _mags = np.loadtxt(fphot, unpack=True, skiprows=1, usecols=[-7, -6, -5, -4, -3, -2, -1]) mags = _mags.T else: galids = np.unique(testGalIDs()) # IDs of unique spectral_challenge spectra mags = np.zeros((len(galids), 7)) for i, gid in enumerate(galids): # read ins ource spectra spec_source = Lgal_nonoiseSpectra(gid, lib=lib) if dust: flux = spec_source['flux_dust_nonoise'] else: flux = spec_source['flux_nodust_nonoise'] # apply filters filter_response = specFilter.load_filters('decam2014-g', 'decam2014-r', 'decam2014-z', 'wise2010-W1', 'wise2010-W2', 'wise2010-W3', 'wise2010-W4') mags_i = filter_response.get_ab_magnitudes(flux*U.Watt/U.m**2/U.Angstrom, spec_source['wave']*U.Angstrom) mags[i,:] = np.array([mags_i[0][0], mags_i[0][1], mags_i[0][2], mags_i[0][3], mags_i[0][4], mags_i[0][5], mags_i[0][6]]) # this is because mags_i is an astropy table. ugh fluxs = fUT.mag2flux(mags, method='log') # add in noise from legacy cats = Cats.GamaLegacy() gleg = cats.Read('g15') for ib, band in enumerate(['g', 'r', 'z', 'w1', 'w2', 'w3', 'w4']): _flux = gleg['legacy-photo']['flux_%s' % band] _ivar = gleg['legacy-photo']['flux_ivar_%s' % band] if ib == 0: gleg_fluxs = np.zeros((len(_flux), 7)) gleg_ivars = np.zeros((len(_flux), 7)) gleg_fluxs[:,ib] = _flux gleg_ivars[:,ib] = _ivar tree = KDTree(gleg_fluxs[:,:5]) dist, indx = tree.query(fluxs[:,:5]) ivars = np.zeros_like(mags) ivars = gleg_ivars[indx,:] # now lets apply noise to thse mags sig = ivars**-0.5 fluxs += sig * np.random.randn(sig.shape[0], sig.shape[1]) # noisy flux noisy_mags = fUT.flux2mag(fluxs) hdr = 'galid, flux_g, flux_r, flux_z, flux_w1, flux_w2, flux_w3, flux_w4, flux_ivar_g, flux_ivar_r, flux_ivar_z, flux_ivar_w1, flux_ivar_w2, flux_ivar_w3, flux_ivar_w4, mag_g, mag_r, mag_z, mag_w1, mag_w2, mag_w3, mag_w4' fmt = ['%.5e' for i in range(22)] fmt[0] = '%i' np.savetxt(fphot, np.vstack([galids, fluxs.T, ivars.T, noisy_mags.T]).T, header=hdr, fmt=fmt) if validate: # compare the Lgal nonoise photometry to photometry from GAMA-Legacy cats = Cats.GamaLegacy() gleg = cats.Read('g15') # histogram of magnitudes fig = plt.figure(figsize=(20,4)) for i_b, band in enumerate(['g', 'r', 'z', 'w1', 'w2']): sub = fig.add_subplot(1,5,i_b+1) sub.hist(fUT.flux2mag(gleg['legacy-photo']['flux_%s' % band]), color='k', range=(14,25), density=True, label='Legacy') sub.hist(mags[:,i_b], color='C1', range=(14, 25), density=True, alpha=0.5, label='LGal\nSpectral\nChallenge') sub.set_xlabel('%s magnitude' % band, fontsize=20) sub.set_xlim(14, 25) sub.legend(loc='upper right', fontsize=15) fig.savefig(os.path.join(UT.fig_dir(), os.path.basename(fphot).replace('dat', 'hist.png')), bbox_inches='tight') plt.close() # g-r vs r-z fig = plt.figure(figsize=(6,6)) sub = fig.add_subplot(111) g_r = fUT.flux2mag(gleg['legacy-photo']['flux_g']) - fUT.flux2mag(gleg['legacy-photo']['flux_r']) r_z = fUT.flux2mag(gleg['legacy-photo']['flux_r']) - fUT.flux2mag(gleg['legacy-photo']['flux_z']) sub.scatter(g_r, r_z, c='k', s=1, label='Legacy') g_r = mags[:,0] - mags[:,1] r_z = mags[:,1] - mags[:,2] sub.scatter(g_r, r_z, c='C1', s=4, zorder=10, label='LGal\nSpectral Challenge') sub.set_xlabel('$g - r$', fontsize=20) sub.set_xlim(-1., 3.) sub.set_ylabel('$r - z$', fontsize=20) sub.set_ylim(-1., 3.) sub.legend(loc='upper right', handletextpad=0.2, markerscale=3, fontsize=15) fig.savefig(os.path.join(UT.fig_dir(), os.path.basename(fphot).replace('dat', 'color.png')), bbox_inches='tight') plt.close() return mags
def get_spectra(lyafile, nqso=None, wave=None, templateid=None, normfilter='sdss2010-g', seed=None, rand=None, qso=None, add_dlas=False, debug=False, nocolorcuts=True): """Generate a QSO spectrum which includes Lyman-alpha absorption. Args: lyafile (str): name of the Lyman-alpha spectrum file to read. nqso (int, optional): number of spectra to generate (starting from the first spectrum; if more flexibility is needed use TEMPLATEID). wave (numpy.ndarray, optional): desired output wavelength vector. templateid (int numpy.ndarray, optional): indices of the spectra (0-indexed) to read from LYAFILE (default is to read everything). If provided together with NQSO, TEMPLATEID wins. normfilter (str, optional): normalization filter seed (int, optional): Seed for random number generator. rand (numpy.RandomState, optional): RandomState object used for the random number generation. If provided together with SEED, this optional input superseeds the numpy.RandomState object instantiated by SEED. qso (desisim.templates.QSO, optional): object with which to generate individual spectra/templates. add_dlas (bool): Inject damped Lya systems into the Lya forest These are done according to the current best estimates for the incidence dN/dz (Prochaska et al. 2008, ApJ, 675, 1002) Set in calc_lz These are *not* inserted according to overdensity along the sightline nocolorcuts (bool, optional): Do not apply the fiducial rzW1W2 color-cuts cuts (default True). Returns (flux, wave, meta, dla_meta) where: * flux (numpy.ndarray): Array [nmodel, npix] of observed-frame spectra (erg/s/cm2/A). * wave (numpy.ndarray): Observed-frame [npix] wavelength array (Angstrom). * meta (astropy.Table): Table of meta-data [nmodel] for each output spectrum with columns defined in desisim.io.empty_metatable *plus* RA, DEC. * objmeta (astropy.Table): Table of additional object-specific meta-data [nmodel] for each output spectrum with columns defined in desisim.io.empty_metatable. * dla_meta (astropy.Table): Table of meta-data [ndla] for the DLAs injected into the spectra. Only returned if add_dlas=True Note: `dla_meta` is only included if add_dlas=True. """ from scipy.interpolate import interp1d import fitsio from speclite.filters import load_filters from desisim.templates import QSO from desisim.io import empty_metatable h = fitsio.FITS(lyafile) if templateid is None: if nqso is None: nqso = len(h)-1 templateid = np.arange(nqso) else: templateid = np.asarray(templateid) nqso = len(np.atleast_1d(templateid)) if rand is None: rand = np.random.RandomState(seed) templateseed = rand.randint(2**32, size=nqso) #heads = [head.read_header() for head in h[templateid + 1]] heads = [] for indx in templateid: heads.append(h[indx + 1].read_header()) zqso = np.array([head['ZQSO'] for head in heads]) ra = np.array([head['RA'] for head in heads]) dec = np.array([head['DEC'] for head in heads]) mag_g = np.array([head['MAG_G'] for head in heads]) # Hard-coded filtername! Should match MAG_G! normfilt = load_filters(normfilter) if qso is None: qso = QSO(normfilter_south=normfilter, wave=wave) wave = qso.wave flux = np.zeros([nqso, len(wave)], dtype='f4') meta, objmeta = empty_metatable(objtype='QSO', nmodel=nqso) meta['TEMPLATEID'][:] = templateid meta['REDSHIFT'][:] = zqso meta['MAG'][:] = mag_g meta['MAGFILTER'][:] = normfilter meta['SEED'][:] = templateseed meta['RA'] = ra meta['DEC'] = dec # Lists for DLA meta data if add_dlas: dla_NHI, dla_z, dla_id = [], [], [] # Loop on quasars for ii, indx in enumerate(templateid): flux1, _, meta1, objmeta1 = qso.make_templates(nmodel=1, redshift=np.atleast_1d(zqso[ii]), mag=np.atleast_1d(mag_g[ii]), seed=templateseed[ii], nocolorcuts=nocolorcuts, lyaforest=False) flux1 *= 1e-17 for col in meta1.colnames: meta[col][ii] = meta1[col][0] for col in objmeta1.colnames: objmeta[col][ii] = objmeta1[col][0] # read lambda and forest transmission data = h[indx + 1].read() la = data['LAMBDA'][:] tr = data['FLUX'][:] if len(tr): # Interpolate the transmission at the spectral wavelengths, if # outside the forest, the transmission is 1. itr = interp1d(la, tr, bounds_error=False, fill_value=1.0) flux1 *= itr(wave) # Inject a DLA? if add_dlas: if np.min(wave/lambda_RF_LYA - 1) < zqso[ii]: # Any forest? dlas, dla_model = insert_dlas(wave, zqso[ii], seed=templateseed[ii]) ndla = len(dlas) if ndla > 0: flux1 *= dla_model # Meta dla_z += [idla['z'] for idla in dlas] dla_NHI += [idla['N'] for idla in dlas] dla_id += [indx]*ndla padflux, padwave = normfilt.pad_spectrum(flux1, wave, method='edge') normmaggies = np.array(normfilt.get_ab_maggies(padflux, padwave, mask_invalid=True)[normfilter]) factor = 10**(-0.4 * mag_g[ii]) / normmaggies flux1 *= factor for key in ('FLUX_G', 'FLUX_R', 'FLUX_Z', 'FLUX_W1', 'FLUX_W2'): meta[key][ii] *= factor flux[ii, :] = flux1[:] h.close() # Finish if add_dlas: ndla = len(dla_id) if ndla > 0: from astropy.table import Table dla_meta = Table() dla_meta['NHI'] = dla_NHI # log NHI values dla_meta['z'] = dla_z dla_meta['ID'] = dla_id else: dla_meta = None return flux*1e17, wave, meta, objmeta, dla_meta else: return flux*1e17, wave, meta, objmeta
def main(args=None): log = get_logger() if isinstance(args, (list, tuple, type(None))): args = parse(args) if args.outfile is not None and len(args.infile)>1 : log.error("Cannot specify single output file with multiple inputs, use --outdir option instead") return 1 if not os.path.isdir(args.outdir) : log.info("Creating dir {}".format(args.outdir)) os.makedirs(args.outdir) if args.mags: log.warning('--mags is deprecated; please use --bbflux instead') args.bbflux = True exptime = args.exptime if exptime is None : exptime = 1000. # sec if args.eboss: exptime = 1000. # sec (added here in case we change the default) #- Generate obsconditions with args.program, then override as needed obsconditions = reference_conditions[args.program.upper()] if args.airmass is not None: obsconditions['AIRMASS'] = args.airmass if args.seeing is not None: obsconditions['SEEING'] = args.seeing if exptime is not None: obsconditions['EXPTIME'] = exptime if args.moonfrac is not None: obsconditions['MOONFRAC'] = args.moonfrac if args.moonalt is not None: obsconditions['MOONALT'] = args.moonalt if args.moonsep is not None: obsconditions['MOONSEP'] = args.moonsep if args.no_simqso: log.info("Load QSO model") model=QSO() else: log.info("Load SIMQSO model") #lya_simqso_model.py is located in $DESISIM/py/desisim/scripts/. #Uses a different emmision lines model than the default SIMQSO. #We will update this soon to match with the one used in select_mock_targets. model=SIMQSO(nproc=1,sqmodel='lya_simqso_model') decam_and_wise_filters = None bassmzls_and_wise_filters = None if args.target_selection or args.bbflux : log.info("Load DeCAM and WISE filters for target selection sim.") # ToDo @moustakas -- load north/south filters decam_and_wise_filters = filters.load_filters('decam2014-g', 'decam2014-r', 'decam2014-z', 'wise2010-W1', 'wise2010-W2') bassmzls_and_wise_filters = filters.load_filters('BASS-g', 'BASS-r', 'MzLS-z', 'wise2010-W1', 'wise2010-W2') footprint_healpix_weight = None footprint_healpix_nside = None if args.desi_footprint : if not 'DESIMODEL' in os.environ : log.error("To apply DESI footprint, I need the DESIMODEL variable to find the file $DESIMODEL/data/footprint/desi-healpix-weights.fits") sys.exit(1) footprint_filename=os.path.join(os.environ['DESIMODEL'],'data','footprint','desi-healpix-weights.fits') if not os.path.isfile(footprint_filename): log.error("Cannot find $DESIMODEL/data/footprint/desi-healpix-weights.fits") sys.exit(1) pixmap=pyfits.open(footprint_filename)[0].data footprint_healpix_nside=256 # same resolution as original map so we don't loose anything footprint_healpix_weight = load_pixweight(footprint_healpix_nside, pixmap=pixmap) if args.gamma_kms_zfit and not args.zbest: log.info("Setting --zbest to true as required by --gamma_kms_zfit") args.zbest = True if args.extinction: sfdmap= SFDMap() else: sfdmap=None if args.balprob: bal=BAL() else: bal=None if args.eboss: eboss = { 'footprint':FootprintEBOSS(), 'redshift':RedshiftDistributionEBOSS() } else: eboss = None if args.nproc > 1: func_args = [ {"ifilename":filename , \ "args":args, "model":model , \ "obsconditions":obsconditions , \ "decam_and_wise_filters": decam_and_wise_filters , \ "bassmzls_and_wise_filters": bassmzls_and_wise_filters , \ "footprint_healpix_weight": footprint_healpix_weight , \ "footprint_healpix_nside": footprint_healpix_nside , \ "bal":bal,"sfdmap":sfdmap,"eboss":eboss \ } for i,filename in enumerate(args.infile) ] pool = multiprocessing.Pool(args.nproc) pool.map(_func, func_args) else: for i,ifilename in enumerate(args.infile) : simulate_one_healpix(ifilename,args,model,obsconditions, decam_and_wise_filters,bassmzls_and_wise_filters, footprint_healpix_weight,footprint_healpix_nside, bal=bal,sfdmap=sfdmap,eboss=eboss)
def MCMC_photo(self, photo_obs, photo_ivar_obs, zred, bands='desi', nwalkers=100, burnin=100, niter=1000, writeout=None, silent=True): ''' infer the posterior distribution of the free parameters given observed photometric flux, and inverse variance using MCMC. The function outputs a dictionary with the median theta of the posterior as well as the 1sigma and 2sigma errors on the parameters (see below). :param photo_obs: array of the observed photometric flux __in units of nanomaggies__ :param photo_ivar_obs: array of the observed photometric flux **inverse variance**. Not uncertainty! :param zred: float specifying the redshift of the observations :param bands: specify the photometric bands. Some pre-programmed bands available. e.g. if bands == 'desi' then bands_list = ['decam2014-g', 'decam2014-r', 'decam2014-z','wise2010-W1', 'wise2010-W2', 'wise2010-W3', 'wise2010-W4']. (default: 'desi') :param nwalkers: (optional) number of walkers. (default: 100) :param burnin: (optional) int specifying the burnin. (default: 100) :param nwalkers: (optional) int specifying the number of iterations. (default: 1000) :param writeout: (optional) string specifying the output file. If specified, everything in the output dictionary is written out as well as the entire MCMC chain. (default: None) :param silent: (optional) If False, a bunch of messages will be shown :return output: dictionary that with keys: - output['redshift'] : redshift - output['theta_med'] : parameter value of the median posterior - output['theta_1sig_plus'] : 1sigma above median - output['theta_2sig_plus'] : 2sigma above median - output['theta_1sig_minus'] : 1sigma below median - output['theta_2sig_minus'] : 2sigma below median - output['wavelength_model'] : wavelength of best-fit model - output['flux_model'] : flux of best-fit model - output['wavelength_data'] : wavelength of observations - output['flux_data'] : flux of observations - output['flux_ivar_data'] = inverse variance of the observed flux. ''' # get photometric bands bands_list = self._get_bands(bands) assert len(bands_list) == len(photo_obs) # get filters filters = specFilter.load_filters(*tuple(bands_list)) # posterior function args and kwargs lnpost_args = ( photo_obs, # nanomaggies photo_ivar_obs, # 1/nanomaggies^2 zred ) lnpost_kwargs = { 'filters': filters, 'prior_shape': 'flat' # shape of prior (hardcoded) } # run emcee and get MCMC chains chain = self._emcee(self._lnPost_photo, lnpost_args, lnpost_kwargs, nwalkers=nwalkers, burnin=burnin, niter=niter, silent=silent) # get quanitles of the posterior lowlow, low, med, high, highhigh = np.percentile(chain, [2.5, 16, 50, 84, 97.5], axis=0) output = {} output['redshift'] = zred output['theta_med'] = med output['theta_1sig_plus'] = high output['theta_2sig_plus'] = highhigh output['theta_1sig_minus'] = low output['theta_2sig_minus'] = lowlow flux_model = self.model_photo(med, zred=zred, filters=filters) output['flux_model'] = flux_model output['flux_data'] = photo_obs output['flux_ivar_data'] = photo_ivar_obs # save prior and MCMC chain output['priors'] = self.priors output['mcmc_chain'] = chain if writeout is not None: fh5 = h5py.File(writeout, 'w') for k in output.keys(): fh5.create_dataset(k, data=output[k]) fh5.close() return output
def Lgal_nonoisePhoto(lib='bc03', dust=False, overwrite=False, validate=False): ''' generate noiseless photometry from noiseless spectral_challenge Lgal spectra generated by Rita. Super simple code that convolves the spectra with the bandpass decam g, r, z, W1, W2, W3, W4 filtes. ''' fsource = f_nonoise(4, lib=lib) fsource = os.path.basename(fsource).rsplit('.', 1)[0].rsplit('_BGS_template_')[1] if dust: str_dust = 'dust' else: str_dust = 'nodust' fphot = os.path.join(UT.dat_dir(), 'spectral_challenge', 'bgs','photo.%s.%s.nonoise.dat' % (fsource, str_dust)) if os.path.isfile(fphot) and not overwrite: _mags = np.loadtxt(fphot, unpack=True, skiprows=1, usecols=range(1,8)) mags = _mags.T else: galids = np.unique(testGalIDs()) # IDs of spectral_challenge galaxies fluxes = np.zeros((len(galids), 8)) fluxes[:,0] = galids for i, gid in enumerate(galids): # read ins ource spectra spec_source = Lgal_nonoiseSpectra(gid, lib=lib) if dust: flux = spec_source['flux_dust_nonoise'] else: flux = spec_source['flux_nodust_nonoise'] # apply filters filter_response = specFilter.load_filters('decam2014-g', 'decam2014-r', 'decam2014-z','wise2010-W1', 'wise2010-W2', 'wise2010-W3', 'wise2010-W4') fluxes_i = filter_response.get_ab_maggies(np.atleast_2d(flux)*U.Watt/U.m**2/U.Angstrom, spec_source['wave']*U.Angstrom) fluxes[i,1:] = 1e9 * np.array([fluxes_i[0][0], fluxes_i[0][1], fluxes_i[0][2], fluxes_i[0][3], fluxes_i[0][4], fluxes_i[0][5], fluxes_i[0][6]]) # nanomaggies fmt = ['%.5e' for i in range(8)] fmt[0] = '%i' np.savetxt(fphot, fluxes, header='galid, g, r, z, W1, W2, W3, W4 fluxes in nanomaggies', fmt=fmt) mags = fUT.flux2mag(fluxes[:,1:], method='log') if validate: # compare the Lgal nonoise photometry to photometry from GAMA-Legacy cats = Cats.GamaLegacy() gleg = cats.Read('g15') fig = plt.figure(figsize=(20,4)) for i_b, band in enumerate(['g', 'r', 'z', 'w1', 'w2']): sub = fig.add_subplot(1,5,i_b+1) sub.hist(fUT.flux2mag(gleg['legacy-photo']['flux_%s' % band]), color='k', range=(14,25), density=True, label='Legacy') sub.hist(mags[:,i_b], color='C1', range=(14, 25), density=True, alpha=0.5, label='LGal\nSpectral\nChallenge') sub.set_xlabel('%s magnitude' % band, fontsize=20) sub.set_xlim(14, 25) sub.legend(loc='upper right', fontsize=15) fig.savefig(os.path.join(UT.fig_dir(), os.path.basename(fphot).replace('dat', 'hist.png')), bbox_inches='tight') plt.close() # g-r vs r-z fig = plt.figure(figsize=(6,6)) sub = fig.add_subplot(111) g_r = fUT.flux2mag(gleg['legacy-photo']['flux_g']) - fUT.flux2mag(gleg['legacy-photo']['flux_r']) r_z = fUT.flux2mag(gleg['legacy-photo']['flux_r']) - fUT.flux2mag(gleg['legacy-photo']['flux_z']) sub.scatter(g_r, r_z, c='k', s=1, label='Legacy') g_r = mags[:,0] - mags[:,1] r_z = mags[:,1] - mags[:,2] sub.scatter(g_r, r_z, c='C1', s=4, zorder=10, label='LGal\nSpectral Challenge') sub.set_xlabel('$g - r$', fontsize=20) sub.set_xlim(-1., 3.) sub.set_ylabel('$r - z$', fontsize=20) sub.set_ylim(-1., 3.) sub.legend(loc='upper right', handletextpad=0.2, markerscale=3, fontsize=15) fig.savefig(os.path.join(UT.fig_dir(), os.path.basename(fphot).replace('dat', 'color.png')), bbox_inches='tight') plt.close() return mags
def main(args=None): log = get_logger() if isinstance(args, (list, tuple, type(None))): args = parse(args) if args.outfile is not None and len(args.infile)>1 : log.error("Cannot specify single output file with multiple inputs, use --outdir option instead") return 1 if not os.path.isdir(args.outdir) : log.info("Creating dir {}".format(args.outdir)) os.makedirs(args.outdir) if args.mags: log.warning('--mags is deprecated; please use --bbflux instead') args.bbflux = True exptime = args.exptime if exptime is None : exptime = 1000. # sec if args.eboss: exptime = 1000. # sec (added here in case we change the default) #- Generate obsconditions with args.program, then override as needed obsconditions = reference_conditions[args.program.upper()] if args.airmass is not None: obsconditions['AIRMASS'] = args.airmass if args.seeing is not None: obsconditions['SEEING'] = args.seeing if exptime is not None: obsconditions['EXPTIME'] = exptime if args.moonfrac is not None: obsconditions['MOONFRAC'] = args.moonfrac if args.moonalt is not None: obsconditions['MOONALT'] = args.moonalt if args.moonsep is not None: obsconditions['MOONSEP'] = args.moonsep if args.no_simqso: log.info("Load QSO model") model=QSO() else: log.info("Load SIMQSO model") #lya_simqso_model.py is located in $DESISIM/py/desisim/scripts/. #Uses a different emmision lines model than the default SIMQSO model=SIMQSO(nproc=1,sqmodel='lya_simqso_model') decam_and_wise_filters = None bassmzls_and_wise_filters = None if args.target_selection or args.bbflux : log.info("Load DeCAM and WISE filters for target selection sim.") # ToDo @moustakas -- load north/south filters decam_and_wise_filters = filters.load_filters('decam2014-g', 'decam2014-r', 'decam2014-z', 'wise2010-W1', 'wise2010-W2') bassmzls_and_wise_filters = filters.load_filters('BASS-g', 'BASS-r', 'MzLS-z', 'wise2010-W1', 'wise2010-W2') footprint_healpix_weight = None footprint_healpix_nside = None if args.desi_footprint : if not 'DESIMODEL' in os.environ : log.error("To apply DESI footprint, I need the DESIMODEL variable to find the file $DESIMODEL/data/footprint/desi-healpix-weights.fits") sys.exit(1) footprint_filename=os.path.join(os.environ['DESIMODEL'],'data','footprint','desi-healpix-weights.fits') if not os.path.isfile(footprint_filename): log.error("Cannot find $DESIMODEL/data/footprint/desi-healpix-weights.fits") sys.exit(1) pixmap=pyfits.open(footprint_filename)[0].data footprint_healpix_nside=256 # same resolution as original map so we don't loose anything footprint_healpix_weight = load_pixweight(footprint_healpix_nside, pixmap=pixmap) if args.gamma_kms_zfit and not args.zbest: log.info("Setting --zbest to true as required by --gamma_kms_zfit") args.zbest = True if args.extinction: sfdmap= SFDMap() else: sfdmap=None if args.balprob: bal=BAL() else: bal=None if args.eboss: eboss = { 'footprint':FootprintEBOSS(), 'redshift':RedshiftDistributionEBOSS() } else: eboss = None if args.nproc > 1: func_args = [ {"ifilename":filename , \ "args":args, "model":model , \ "obsconditions":obsconditions , \ "decam_and_wise_filters": decam_and_wise_filters , \ "bassmzls_and_wise_filters": bassmzls_and_wise_filters , \ "footprint_healpix_weight": footprint_healpix_weight , \ "footprint_healpix_nside": footprint_healpix_nside , \ "bal":bal,"sfdmap":sfdmap,"eboss":eboss \ } for i,filename in enumerate(args.infile) ] pool = multiprocessing.Pool(args.nproc) pool.map(_func, func_args) else: for i,ifilename in enumerate(args.infile) : simulate_one_healpix(ifilename,args,model,obsconditions, decam_and_wise_filters,bassmzls_and_wise_filters, footprint_healpix_weight,footprint_healpix_nside, bal=bal,sfdmap=sfdmap,eboss=eboss)
def main(args=None): log = get_logger() if isinstance(args, (list, tuple, type(None))): args = parse(args) if isinstance(args, (list, tuple, type(None))): args = parse(args) if args.outfile is not None and len(args.infile) > 1: log.error( "Cannot specify single output file with multiple inputs, use --outdir option instead" ) return 1 if not os.path.isdir(args.outdir): log.info("Creating dir {}".format(args.outdir)) os.makedirs(args.outdir) exptime = args.exptime if exptime is None: exptime = 1000. # sec #- Generate obsconditions with args.program, then override as needed obsconditions = reference_conditions[args.program.upper()] if args.airmass is not None: obsconditions['AIRMASS'] = args.airmass if args.seeing is not None: obsconditions['SEEING'] = args.seeing if exptime is not None: obsconditions['EXPTIME'] = exptime if args.moonfrac is not None: obsconditions['MOONFRAC'] = args.moonfrac if args.moonalt is not None: obsconditions['MOONALT'] = args.moonalt if args.moonsep is not None: obsconditions['MOONSEP'] = args.moonsep log.info("Load SIMQSO model") model = SIMQSO(normfilter=args.norm_filter, nproc=1) decam_and_wise_filters = None if args.target_selection or args.mags: log.info("Load DeCAM and WISE filters for target selection sim.") decam_and_wise_filters = filters.load_filters('decam2014-g', 'decam2014-r', 'decam2014-z', 'wise2010-W1', 'wise2010-W2') footprint_healpix_weight = None footprint_healpix_nside = None if args.desi_footprint: if not 'DESIMODEL' in os.environ: log.error( "To apply DESI footprint, I need the DESIMODEL variable to find the file $DESIMODEL/data/footprint/desi-healpix-weights.fits" ) sys.exit(1) footprint_filename = os.path.join(os.environ['DESIMODEL'], 'data', 'footprint', 'desi-healpix-weights.fits') if not os.path.isfile(footprint_filename): log.error( "Cannot find $DESIMODEL/data/footprint/desi-healpix-weights.fits" ) sys.exit(1) pixmap = pyfits.open(footprint_filename)[0].data footprint_healpix_nside = 256 # same resolution as original map so we don't loose anything footprint_healpix_weight = load_pixweight(footprint_healpix_nside, pixmap=pixmap) if args.seed is not None: np.random.seed(args.seed) # seeds for each healpix are themselves random numbers seeds = np.random.randint(2**32, size=len(args.infile)) if args.balprob: bal = BAL() if args.nproc > 1: func_args = [ {"ifilename":filename , \ "args":args, "model":model , \ "obsconditions":obsconditions , \ "decam_and_wise_filters": decam_and_wise_filters , \ "footprint_healpix_weight": footprint_healpix_weight , \ "footprint_healpix_nside": footprint_healpix_nside , \ "seed":seeds[i] } for i,filename in enumerate(args.infile) ] pool = multiprocessing.Pool(args.nproc) pool.map(_func, func_args) else: for i, ifilename in enumerate(args.infile): if args.balprob: simulate_one_healpix(ifilename, args, model, obsconditions, decam_and_wise_filters, footprint_healpix_weight, footprint_healpix_nside, seed=seeds[i], bal=bal) else: simulate_one_healpix(ifilename, args, model, obsconditions, decam_and_wise_filters, footprint_healpix_weight, footprint_healpix_nside, seed=seeds[i])
def get_sky(night, expid, exptime, ftype="model", redux="daily", smoothing=100.0, specsim_darksky=False, nightly_darksky=False): # AR ftype = "data" or "model" # AR redux = "daily" or "blanc" # AR if ftype = "data" : read the sky fibers from frame*fits + apply flat-field # AR if ftype = "model": read the sky model from sky*.fits for the first fiber of each petal (see DJS email from 29Dec2020) # AR those are in electron / angstrom # AR to integrate over the decam-r-band, we need cameras b and r if ftype not in ["data", "model"]: sys.exit("ftype should be 'data' or 'model'") sky = np.zeros(len(fullwave)) reduxdir = dailydir.replace("daily", redux) # AR see AK email [desi-data 5218] if redux == "blanc": specs = ["0", "1", "3", "4", "5", "7", "8", "9"] else: specs = np.arange(10, dtype=int).astype(str) for camera in ["b", "r", "z"]: norm_cam = np.zeros(len(fullwave[cslice[camera]])) sky_cam = np.zeros(len(fullwave[cslice[camera]])) for spec in specs: # AR data if ftype == "data": frfn = os.path.join( reduxdir, "exposures", "{}".format(night), expid, "frame-{}{}-{}.fits".format(camera, spec, expid), ) if not os.path.isfile(frfn): print("Skipping non-existent {}".format(frfn)) continue fr = read_frame(frfn, skip_resolution=True) flfn = os.environ['DESI_SPECTRO_CALIB'] + '/' + CalibFinder( [fr.meta]).data['FIBERFLAT'] # calib_flux [1e-17 erg/s/cm2/Angstrom] = uncalib_flux [electron/Angstrom] / (calibration_model * exptime [s]) # fl = read_fiberflat(flfn) # No exptime correction. # apply_fiberflat(fr, fl) # AR cutting on sky fibers with at least one valid pixel. ii = (fr.fibermap["OBJTYPE"] == "SKY") & (fr.ivar.sum(axis=1) > 0) # AR frame*fits are in e- / angstrom ; adding the N sky fibers # sky_cam += fr.flux[ii, :].sum(axis=0) # nspec += ii.sum() # Ignores fiberflat corrected (fl), as includes e.g. fiberloss. sky_cam += (fr.flux[ii, :] * fr.ivar[ii, :]).sum(axis=0) norm_cam += fr.ivar[ii, :].sum(axis=0) # AR model if ftype == "model": fn = os.path.join( reduxdir, "exposures", "{}".format(night), expid, "sky-{}{}-{}.fits".format(camera, spec, expid), ) if not os.path.isfile(fn): print("Skipping non-existent {}".format(fn)) else: print("Solving for {}".format(fn)) fd = fitsio.FITS(fn) assert np.allclose(fullwave[cslice[camera]], fd["WAVELENGTH"].read()) fd = fitsio.FITS(fn) with fd as hdus: exptime = hdus[0].read_header()['EXPTIME'] flux = hdus['SKY'].read() ivar = hdus['IVAR'].read() mask = hdus['MASK'].read() # Verify that we have the expected wavelengths. # assert np.allclose(detected[camera].wave, hdus['WAVELENGTH'].read()) # Verify that ivar is purely statistical. # assert np.array_equal(ivar, hdus['STATIVAR'].read()) # Verify that the model has no masked pixels. # assert np.all((mask == 0) & (ivar > 0)) # Verify that the sky model is constant. # assert np.array_equal(np.max(ivar, axis=0), np.min(ivar, axis=0)) # assert np.allclose(np.max(flux, axis=0), np.min(flux, axis=0)) # There are actually small variations in flux! # TODO: figure out where these variations come from. # For now, take the median over fibers. if fd["IVAR"][0, :][0].max() > 0: sky_cam += fd["SKY"][0, :][ 0] # AR reading the first fiber only norm_cam += np.ones(len(fullwave[cslice[camera]])) # sky_cam += np.median(flux, axis=0) # norm_cam += np.ones(len(fullwave[cslice[camera]])) fd.close() # AR sky model flux in incident photon / angstrom / s # if nspec > 0: keep = norm_cam > 0 if keep.sum() > 0: # [e/A/s] / throughput. sky[cslice[camera]][keep] = (sky_cam[keep] / norm_cam[keep] / exptime / spec_thru[camera][keep]) else: print("{}-{}-{}: no spectra for {}".format(night, expid, camera, ftype)) # AR sky model flux in erg / angstrom / s (using the photon energy in erg). e_phot_erg = (constants.h.to(units.erg * units.s) * constants.c.to(units.angstrom / units.s) / (fullwave * units.angstrom)) sky *= e_phot_erg.value # AR sky model flux in [erg / angstrom / s / cm**2 / arcsec**2]. sky /= (telap_cm2 * fiber_area_arcsec2) if smoothing > 0.0: sky = scipy.ndimage.gaussian_filter1d(sky, 100.) # AR integrate over the DECam r-band vfilter = filters.load_filters('bessell-V') rfilter = filters.load_filters('decam2014-r') # AR zero-padding spectrum so that it covers the DECam r-band range sky_pad, fullwave_pad = vfilter.pad_spectrum(sky, fullwave, method="zero") vmag = vfilter.get_ab_magnitudes( sky_pad * units.erg / (units.cm**2 * units.s * units.angstrom), fullwave_pad * units.angstrom).as_array()[0][0] # AR zero-padding spectrum so that it covers the DECam r-band range sky_pad, fullwave_pad = rfilter.pad_spectrum(sky, fullwave, method="zero") rmag = rfilter.get_ab_magnitudes( sky_pad * units.erg / (units.cm**2 * units.s * units.angstrom), fullwave_pad * units.angstrom).as_array()[0][0] if specsim_darksky: fd = fitsio.FITS(fn) # Dark Sky at given airmass (cnst. across spectrograph / camera). simulator.atmosphere.airmass = fd['SKY'].read_header()['AIRMASS'] # Force below the horizon: dark sky. simulator.atmosphere.moon.moon_zenith = 120. * u.deg simulator.simulate() # [1e-17 erg / (Angstrom arcsec2 cm2 s)]. sim_darksky = simulator.atmosphere.surface_brightness sim_darksky *= 1.e-17 dsky_pad, dskywave_pad = rfilter.pad_spectrum(sim_darksky.value, config.wavelength.value, method="zero") dsky_rmag = rfilter.get_ab_magnitudes( dsky_pad * units.erg / (units.cm**2 * units.s * units.angstrom), dskywave_pad * units.angstrom).as_array()[0][0] sim_darksky = resample_flux(fullwave, config.wavelength.value, sim_darksky.value) sim_darksky *= u.erg / (u.cm**2 * u.s * u.angstrom * u.arcsec**2) sky = np.clip(sky - sim_darksky.value, a_min=0.0, a_max=None) # AR zero-padding spectrum so that it covers the DECam r-band range sky_pad, fullwave_pad = vfilter.pad_spectrum(sky, fullwave, method="zero") vmag_nodark = vfilter.get_ab_magnitudes( sky_pad * units.erg / (units.cm**2 * units.s * units.angstrom), fullwave_pad * units.angstrom).as_array()[0][0] # AR zero-padding spectrum so that it covers the DECam r-band range sky_pad, fullwave_pad = rfilter.pad_spectrum(sky, fullwave, method="zero") rmag_nodark = rfilter.get_ab_magnitudes( sky_pad * units.erg / (units.cm**2 * units.s * units.angstrom), fullwave_pad * units.angstrom).as_array()[0][0] return fullwave, sky, vmag, rmag, vmag_nodark, rmag_nodark elif nightly_darksky: from pkg_resources import resource_filename if night in nightly_dsky_cache.keys(): return nightly_dsky_cache[night] gfa_info = resource_filename('bgs-cmxsv', 'dat/sv1-exposures.fits') gfa_info = Table.read(gfa_info) bright_cut = 20.50 good_conds = (gfa_info['GFA_TRANSPARENCY_MED'] > 0.95) & (gfa_info['GFA_SKY_MAG_AB_MED'] >= bright_cut) good_conds = gfa_info[good_conds] expids = np.array([ np.int(x.split('/')[-1]) for x in glob.glob( os.path.join(reduxdir, "exposures", "{}/00*".format(night))) ]) isgood = np.isin(good_conds['EXPID'], expids) if np.any(isgood): good_conds = good_conds[isgood] best = good_conds['GFA_SKY_MAG_AB_MED'] == good_conds[ 'GFA_SKY_MAG_AB_MED'].max() print('Nightly Dark GFA r-mag for {}: {}'.format( night, good_conds['GFA_SKY_MAG_AB_MED'].max())) best_expid = good_conds[best]['EXPID'][0] best_expid = '{:08d}'.format(best_expid) darkwave, darksky, dark_vmag, dark_rmag = get_sky( night, best_expid, exptime, ftype="model", redux="daily", smoothing=0.0, specsim_darksky=False, nightly_darksky=False) sky = np.clip(sky - darksky, a_min=0.0, a_max=None) # AR zero-padding spectrum so that it covers the DECam r-band range sky_pad, fullwave_pad = vfilter.pad_spectrum(sky, fullwave, method="zero") vmag_nodark = vfilter.get_ab_magnitudes( sky_pad * units.erg / (units.cm**2 * units.s * units.angstrom), fullwave_pad * units.angstrom).as_array()[0][0] # AR zero-padding spectrum so that it covers the DECam r-band range sky_pad, fullwave_pad = rfilter.pad_spectrum(sky, fullwave, method="zero") rmag_nodark = rfilter.get_ab_magnitudes( sky_pad * units.erg / (units.cm**2 * units.s * units.angstrom), fullwave_pad * units.angstrom).as_array()[0][0] nightly_dsky_cache[ night] = fullwave, sky, vmag, rmag, vmag_nodark, rmag_nodark else: print( 'No nightly darksky available for night: {}. Defaulting to specsim.' .format(night)) nightly_dsky_cache[night] = get_sky(night, expid, exptime, ftype="model", redux="daily", smoothing=0.0, specsim_darksky=True, nightly_darksky=False) return nightly_dsky_cache[night] else: pass return fullwave, sky, vmag, rmag
def MCMC_spectrophoto(self, wave_obs, flux_obs, flux_ivar_obs, photo_obs, photo_ivar_obs, zred, f_fiber_prior=None, mask=None, bands='desi', nwalkers=100, burnin=100, niter=1000, writeout=None, silent=True): ''' infer the posterior distribution of the free parameters given spectroscopy and photometry: observed wavelength, spectra flux, inverse variance flux, photometry, inv. variance photometry using MCMC. The function outputs a dictionary with the median theta of the posterior as well as the 1sigma and 2sigma errors on the parameters (see below). :param wave_obs: array of the observed wavelength :param flux_obs: array of the observed flux __in units of ergs/s/cm^2/Ang__ :param flux_ivar_obs: array of the observed flux **inverse variance**. Not uncertainty! :param photo_obs: array of the observed photometric flux __in units of nanomaggies__ :param photo_ivar_obs: array of the observed photometric flux **inverse variance**. Not uncertainty! :param zred: float specifying the redshift of the observations :param f_fiber_prior: list specifying the range of f_fiber factor proir. (default: None) :param mask: (optional) boolean array specifying where to mask the spectra. If mask == 'emline' the spectra is masked around emission lines at 3728., 4861., 5007., 6564. Angstroms. (default: None) :param nwalkers: (optional) number of walkers. (default: 100) :param burnin: (optional) int specifying the burnin. (default: 100) :param nwalkers: (optional) int specifying the number of iterations. (default: 1000) :param writeout: (optional) string specifying the output file. If specified, everything in the output dictionary is written out as well as the entire MCMC chain. (default: None) :param silent: (optional) If False, a bunch of messages will be shown :return output: dictionary that with keys: - output['redshift'] : redshift - output['theta_med'] : parameter value of the median posterior - output['theta_1sig_plus'] : 1sigma above median - output['theta_2sig_plus'] : 2sigma above median - output['theta_1sig_minus'] : 1sigma below median - output['theta_2sig_minus'] : 2sigma below median - output['wavelength_model'] : wavelength of best-fit model - output['flux_model'] : flux of best-fit model - output['wavelength_data'] : wavelength of observations - output['flux_data'] : flux of observations - output['flux_ivar_data'] = inverse variance of the observed flux. ''' import scipy.optimize as op import emcee self.priors.append(f_fiber_prior) # add f_fiber priors ndim = len(self.priors) # check mask for spectra _mask = self._check_mask(mask, wave_obs, flux_ivar_obs, zred) # get photometric bands bands_list = self._get_bands(bands) assert len(bands_list) == len(photo_obs) # get filters filters = specFilter.load_filters(*tuple(bands_list)) # posterior function args and kwargs lnpost_args = (wave_obs, flux_obs, # 10^-17 ergs/s/cm^2/Ang flux_ivar_obs, # 1/(10^-17 ergs/s/cm^2/Ang)^2 photo_obs, # nanomaggies photo_ivar_obs, # 1/nanomaggies^2 zred) lnpost_kwargs = { 'mask': _mask, # emission line mask 'filters': filters, 'prior_shape': 'flat' # shape of prior (hardcoded) } # run emcee and get MCMC chains chain = self._emcee(self._lnPost_spectrophoto, lnpost_args, lnpost_kwargs, nwalkers=nwalkers, burnin=burnin, niter=niter, silent=silent) # get quanitles of the posterior lowlow, low, med, high, highhigh = np.percentile(chain, [2.5, 16, 50, 84, 97.5], axis=0) output = {} output['redshift'] = zred output['theta_med'] = med output['theta_1sig_plus'] = high output['theta_2sig_plus'] = highhigh output['theta_1sig_minus'] = low output['theta_2sig_minus'] = lowlow w_model, flux_model = self.model(med, zred=zred, wavelength=wave_obs) output['wavelength_model'] = w_model output['flux_model'] = flux_model output['wavelength_data'] = wave_obs output['flux_data'] = flux_obs output['flux_ivar_data'] = flux_ivar_obs # save prior and MCMC chain output['priors'] = self.priors output['mcmc_chain'] = chain if writeout is not None: fh5 = h5py.File(writeout, 'w') for k in output.keys(): fh5.create_dataset(k, data=output[k]) fh5.close() return output
def create_galaxy_counts(cmau_array, mag_bins, z_array, wav, alpha0, alpha1, weight, ab_offset, filter_name, al_inf): r''' Create a simulated distribution of galaxy magnitudes for a particular bandpass by consideration of double Schechter functions (for blue and red galaxies) in a specified range of redshifts, following [1]_. Parameters ---------- cmau_array : numpy.ndarray Array holding the c/m/a/u values that describe the parameterisation of the Schechter functions with wavelength, following Wilson (2022, RNAAS, 6, 60) [1]_. Shape should be `(5, 2, 4)`, with 5 parameters for both blue and red galaxies. mag_bins : numpy.ndarray The apparent magnitudes at which to evaluate the on-sky differential galaxy density. z_array : numpy.ndarray Redshift bins to evaluate Schechter densities in the middle of. wav : float The wavelength, in microns, of the bandpass observations should be simulated in. Should likely be the effective wavelength. alpha0 : list of numpy.ndarray or numpy.ndarray List of arrays of parameters :math:`\alpha_{i, 0}` used to calculate Dirichlet-distributed SED coefficients. Should either be a two-element list of arrays of 5 elements, or an array of shape ``(2, 5)``, with coefficients for blue galaxies before red galaxies. See [2]_ and [3]_ for more details. alpha1 : list of numpy.ndarray or numpy.ndarray :math:`\alpha_{i, 1}` used in the calculation of Dirichlet-distributed SED coefficients. Two-element list or ``(2, 5)`` shape array of blue then red galaxy coefficients. weight : list of numpy.ndarray or numpy.ndarray Corresponding weights for the derivation of Dirichlet `kcorrect` coefficients. Must match shape of ``alpha0`` and ``alpha1``. ab_offset : float Zeropoint offset for differential galaxy count observations in a non-AB magnitude system. Must be in the sense of m_desired = m_AB - offset. filter_name : str ``speclite`` compound filterset-filter name for the response curve of the particular observations. If observations are in a filter system not provided by ``speclite``, response curve can be generated using ``generate_speclite_filters``. al_inf : float The reddening at infinity by which to extinct all galaxy magnitudes. Returns ------- gal_dens : numpy.ndarray Simulated numbers of galaxies per square degree per magnitude in the specified observed bandpass. References ---------- .. [1] Wilson T. J. (2022), RNAAS, 6, 60 .. [2] Herbel J., Kacprzak T., Amara A., et al. (2017), JCAP, 8, 35 .. [3] Blanton M. R., Roweis S. (2007), AJ, 133, 734 ''' cosmology = default_cosmology.get() gal_dens = np.zeros_like(mag_bins) log_wav = np.log10(wav) alpha0_blue, alpha0_red = alpha0 alpha1_blue, alpha1_red = alpha1 weight_blue, weight_red = weight # Currently just set up a very wide absolute magnitude bin range to ensure # we get the dynamic range right. Inefficient but should be reliable... abs_mag_bins = np.linspace(-60, 50, 1100) for i in range(len(z_array) - 1): mini_z_array = z_array[[i, i + 1]] z = 0.5 * np.sum(mini_z_array) phi_model1 = generate_phi(cmau_array, 0, log_wav, z, abs_mag_bins) phi_model2 = generate_phi(cmau_array, 1, log_wav, z, abs_mag_bins) # differential_comoving_volume is "per redshift per steradian" at each # redshift, so we take the average and "integrate" over z. dV_dOmega = np.sum( cosmology.differential_comoving_volume(mini_z_array).to_value( 'Mpc3 / deg2')) / 2 * np.diff(mini_z_array) model_densities = [phi_model1 * dV_dOmega, phi_model2 * dV_dOmega] # Blanton & Roweis (2007) kcorrect templates, via skypy. w = skygal.spectrum.kcorrect.wavelength t = skygal.spectrum.kcorrect.templates # Generate redshifts and coefficients and k-corrections for each # realisation, and then take the median k-correction. for _alpha0, _alpha1, _weight, model_density in zip( [alpha0_blue, alpha0_red], [alpha1_blue, alpha1_red], [weight_blue, weight_red], model_densities): rng = np.random.default_rng() redshift = rng.uniform(z_array[i], z_array[i + 1], 100) spectral_coefficients = skygal.spectrum.dirichlet_coefficients( redshift=redshift, alpha0=_alpha0, alpha1=_alpha1, weight=_weight) kcorr = np.empty_like(redshift) for j in range(len(redshift)): _z = redshift[j] f = load_filters(filter_name)[0] fs = f.create_shifted(_z) non_shift_ab_maggy, shift_ab_maggy = 0, 0 for k in range(len(t)): non_shift_ab_maggy += spectral_coefficients[ j, k] * f.get_ab_maggies(t[k], w) try: shift_ab_maggy += spectral_coefficients[ j, k] * fs.get_ab_maggies(t[k], w) except ValueError: _t, _w = fs.pad_spectrum(t[k], w, method='edge') shift_ab_maggy += spectral_coefficients[ j, k] * fs.get_ab_maggies(_t, _w) # Backwards to Hogg+ astro-ph/0210394, our "shifted" bandpass is the rest-frame # as opposed to the observer frame. kcorr[j] = -2.5 * np.log10( 1 / (1 + _z) * shift_ab_maggy / non_shift_ab_maggy) # e.g. Loveday+2015 for absolute -> apparent magnitude conversion gal_dens += np.interp( mag_bins, abs_mag_bins + cosmology.distmod(z).value + np.percentile(kcorr, 50) - ab_offset + al_inf, model_density) return gal_dens