Source code for pynbody.plot.stars

"""

stars
=====

"""

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import warnings

from ..analysis import profile, angmom, halo
from .. import filt, units, config, array
from .sph import image
from .. import units as _units

from ..sph import render_spherical_image
from ..sph import Kernel2D

import logging
logger = logging.getLogger('pynbody.plot.stars')


def bytscl(arr, mini=0, maxi=10000):
	X = (arr - mini) / (maxi - mini)
	X[X > 1] = 1
	X[X < 0] = 0
	return X


def nw_scale_rgb(r, g, b, scales=[4, 3.2, 3.4]):
	return r * scales[0], g * scales[1], b * scales[2]


def nw_arcsinh_fit(r, g, b, nonlinearity=3):
	radius = r + g + b
	val = np.arcsinh(radius * nonlinearity) / nonlinearity / radius
	return r * val, g * val, b * val


def combine(r, g, b, magnitude_range, brightest_mag=None, mollview=False):
	# flip sign so that brightest pixels have biggest value
	r = -r
	g = -g
	b = -b

	if brightest_mag is None:
		brightest_mag = []

		# find something close to the maximum that is not quite the maximum
		for x in r, g, b:
			if mollview:
				x_tmp = x.flatten()[x.flatten()<0]
				ordered = np.sort(x_tmp.data)
			else:   
				ordered = np.sort(x.flatten())
			brightest_mag.append(ordered[-len(ordered) // 5000])

		brightest_mag = max(brightest_mag)
	else:
		brightest_mag = -brightest_mag

	rgbim = np.zeros((r.shape[0], r.shape[1], 3))
	rgbim[:, :, 0] = bytscl(r, brightest_mag - magnitude_range, brightest_mag)
	rgbim[:, :, 1] = bytscl(g, brightest_mag - magnitude_range, brightest_mag)
	rgbim[:, :, 2] = bytscl(b, brightest_mag - magnitude_range, brightest_mag)
	return rgbim, -brightest_mag

def convert_to_mag_arcsec2(image, mollview=False):
	if not mollview:
		assert image.units=="pc^-2"
	pc2_to_sqarcsec = 2.3504430539466191e-09
	return -2.5*np.log10(image*pc2_to_sqarcsec)

[docs]def render(sim, filename=None, r_band='i', g_band='v', b_band='u', r_scale=0.5, g_scale=1.0, b_scale=1.0, dynamic_range=2.0, mag_range=None, width=50, resolution=500, starsize=None, plot=True, axes=None, ret_im=False, clear=True, ret_range=False, with_dust=False, z_range=50.0): ''' Make a 3-color image of stars. The colors are based on magnitudes found using stellar Marigo stellar population code. If with_dust is True, a simple dust screening is applied. Returns: If ret_im=True, an NxNx3 array representing an RGB image **Optional keyword arguments:** *filename*: string (default: None) Filename to be written to (if a filename is specified) *r_band*: string (default: 'i') Determines which Johnston filter will go into the image red channel *g_band*: string (default: 'v') Determines which Johnston filter will go into the image green channel *b_band*: string (default: 'b') Determines which Johnston filter will go into the image blue channel *r_scale*: float (default: 0.5) The scaling of the red channel before channels are combined *g_scale*: float (default: 1.0) The scaling of the green channel before channels are combined *b_scale*: float (default: 1.0) The scaling of the blue channel before channels are combined *width*: float in kpc (default:50) Sets the size of the image field in kpc *resolution*: integer (default: 500) Sets the number of pixels on the side of the image *starsize*: float in kpc (default: None) If not None, sets the maximum size of stars in the image *ret_im*: bool (default: False) if True, the NxNx3 image array is returned *ret_range*: bool (default: False) if True, the range of the image in mag arcsec^-2 is returned. *plot*: bool (default: True) if True, the image is plotted *axes*: matplotlib axes object (deault: None) if not None, the axes object to plot to *dynamic_range*: float (default: 2.0) The number of dex in luminosity over which the image brightness ranges *mag_range*: float, float (default: None) If provided, the brightest and faintest surface brightnesses in the range, in mag arcsec^-2. Takes precedence over dynamic_range. *with_dust*: bool, (default: False) If True, the image is rendered with a simple dust screening model based on Calzetti's law. *z_range*: float, (default: 50.0) If with_dust is True this parameter specifies the z range over which the column density will be calculated. The default value is 50 kpc. ''' if isinstance(width, str) or issubclass(width.__class__, _units.UnitBase): if isinstance(width, str): width = _units.Unit(width) width = width.in_units(sim['pos'].units, **sim.conversion_context()) if starsize is not None: smf = filt.HighPass('smooth', str(starsize) + ' kpc') sim.s[smf]['smooth'] = array.SimArray(starsize, 'kpc', sim=sim) r = image(sim.s, qty=r_band + '_lum_den', width=width, log=False, units="pc^-2", clear=False, noplot=True, resolution=resolution) * r_scale g = image(sim.s, qty=g_band + '_lum_den', width=width, log=False, units="pc^-2", clear=False, noplot=True, resolution=resolution) * g_scale b = image(sim.s, qty=b_band + '_lum_den', width=width, log=False, units="pc^-2", clear=False, noplot=True, resolution=resolution) * b_scale # convert all channels to mag arcsec^-2 r=convert_to_mag_arcsec2(r) g=convert_to_mag_arcsec2(g) b=convert_to_mag_arcsec2(b) if with_dust is True: # render image with a simple dust absorption correction based on Calzetti's law using the gas content. try: import extinction except ImportError: warnings.warn( "Could not load extinction package. If you want to use this feature, " "plaese install the extinction package from here: http://extinction.readthedocs.io/en/latest/" "or <via pip install extinction> or <conda install -c conda-forge extinction>", RuntimeWarning) return warm = filt.HighPass('temp',3e4) cool = filt.LowPass('temp',3e4) positive = filt.BandPass('z',-z_range,z_range) #LowPass('z',0) column_den_warm = image(sim.g[positive][warm], qty='rho', width=width, log=False, units="kg cm^-2", clear=False, noplot=True,z_camera=z_range) column_den_cool = image(sim.g[positive][cool], qty='rho', width=width, log=False, units="kg cm^-2", clear=False, noplot=True,z_camera=z_range) mh = 1.67e-27 # units kg cool_fac = 0.25 # fudge factor to make dust absorption not too strong # get the column density of gas col_den = np.divide(column_den_warm,mh)+np.divide(column_den_cool*cool_fac,mh) # get absorption coefficient a_v = 0.5*col_den*2e-21 #get the central wavelength for the given band wavelength_avail = {'u':3571,'b':4378,'v':5466,'r':6695,'i':8565,'j':12101, 'h':16300,'k':21900,'U':3571,'B':4378,'V':5466,'R':6695,'I':8565,'J':12101, 'H':16300,'K':21900} #in Angstrom # effective wavelength taken from http://svo2.cab.inta-csic.es/svo/theory/fps3/index.php?mode=browse&gname=Generic&gname2=Johnson # and from https://en.wikipedia.org/wiki/Photometric_system for h, k lr,lg,lb = wavelength_avail[r_band],wavelength_avail[g_band],wavelength_avail[b_band] #in Angstrom wave = np.array([lb, lg, lr]) ext_r = np.empty_like(r) ext_g = np.empty_like(g) ext_b = np.empty_like(b) for i in range(len(a_v)): for j in range(len(a_v[0])): ext = extinction.calzetti00(wave.astype(np.float), a_v[i][j].astype(np.float), 3.1, unit='aa', out=None) ext_r[i][j] = ext[2] ext_g[i][j] = ext[1] ext_b[i][j] = ext[0] r = r+ext_r g = g+ext_g b = b+ext_b #r,g,b = nw_scale_rgb(r,g,b) #r,g,b = nw_arcsinh_fit(r,g,b) if mag_range is None: rgbim, mag_max = combine(r, g, b, dynamic_range*2.5) mag_min = mag_max + 2.5*dynamic_range else: mag_max, mag_min = mag_range rgbim, mag_max = combine(r, g, b, mag_min - mag_max, mag_max) if plot: if clear: plt.clf() if axes is None: axes = plt.gca() if axes: axes.imshow( rgbim[::-1, :], extent=(-width / 2, width / 2, -width / 2, width / 2)) axes.set_xlabel('x [' + str(sim.s['x'].units) + ']') axes.set_ylabel('y [' + str(sim.s['y'].units) + ']') plt.draw() if filename: plt.savefig(filename) if ret_im: return rgbim if ret_range: return mag_max, mag_min
[docs]def mollview(map=None,fig=None,plot=False,filenme=None, rot=None,coord=None,unit='', xsize=800,title='Mollweide view',nest=False, min=None,max=None,flip='astro', remove_dip=False,remove_mono=False, gal_cut=0, format='%g',format2='%g', cbar=True,cmap=None, notext=False, norm=None,hold=False,margins=None,sub=None, return_projected_map=False): """Plot an healpix map (given as an array) in Mollweide projection. Requires the healpy package. This function is taken from the Healpy package and slightly modified. Parameters ---------- map : float, array-like or None An array containing the map, supports masked maps, see the `ma` function. If None, will display a blank map, useful for overplotting. fig : figure object or None, optional The figure to use. Default: create a new figure plot : bool (default: False) if True the image is plotted filename : string (default: None) Filename to be written to (if a filename is specified) rot : scalar or sequence, optional Describe the rotation to apply. In the form (lon, lat, psi) (unit: degrees) : the point at longitude *lon* and latitude *lat* will be at the center. An additional rotation of angle *psi* around this direction is applied. coord : sequence of character, optional Either one of 'G', 'E' or 'C' to describe the coordinate system of the map, or a sequence of 2 of these to rotate the map from the first to the second coordinate system. unit : str, optional A text describing the unit of the data. Default: '' xsize : int, optional The size of the image. Default: 800 title : str, optional The title of the plot. Default: 'Mollweide view' nest : bool, optional If True, ordering scheme is NESTED. Default: False (RING) min : float, optional The minimum range value max : float, optional The maximum range value flip : {'astro', 'geo'}, optional Defines the convention of projection : 'astro' (default, east towards left, west towards right) or 'geo' (east towards right, west towards left) remove_dip : bool, optional If :const:`True`, remove the dipole+monopole remove_mono : bool, optional If :const:`True`, remove the monopole gal_cut : float, scalar, optional Symmetric galactic cut for the dipole/monopole fit. Removes points in latitude range [-gal_cut, +gal_cut] format : str, optional The format of the scale label. Default: '%g' format2 : str, optional Format of the pixel value under mouse. Default: '%g' cbar : bool, optional Display the colorbar. Default: True notext : bool, optional If True, no text is printed around the map norm : {'hist', 'log', None} Color normalization, hist= histogram equalized color mapping, log= logarithmic color mapping, default: None (linear color mapping) hold : bool, optional If True, replace the current Axes by a MollweideAxes. use this if you want to have multiple maps on the same figure. Default: False sub : int, scalar or sequence, optional Use only a zone of the current figure (same syntax as subplot). Default: None margins : None or sequence, optional Either None, or a sequence (left,bottom,right,top) giving the margins on left,bottom,right and top of the axes. Values are relative to figure (0-1). Default: None return_projected_map : bool if True returns the projected map in a 2d numpy array See Also -------- gnomview, cartview, orthview, azeqview """ try: from healpy import projaxes as PA from healpy import pixelfunc except ImportError: warnings.warn( "Could not load healpy package. If you want to use this feature, " "plaese install the healpy package from here: http://healpy.readthedocs.io/en/latest/" "or via pip or conda.", RuntimeWarning) return # Create the figure if not (hold or sub): if fig == None: f=plt.figure(figsize=(8.5,5.4)) extent = (0.02,0.05,0.96,0.9) else: f=fig extent = (0.02,0.05,0.96,0.9) elif hold: f=plt.gcf() left,bottom,right,top = np.array(f.gca().get_position()).ravel() extent = (left,bottom,right-left,top-bottom) f.delaxes(f.gca()) else: # using subplot syntax f=plt.gcf() if hasattr(sub,'__len__'): nrows, ncols, idx = sub else: nrows, ncols, idx = sub//100, (sub%100)//10, (sub%10) if idx < 1 or idx > ncols*nrows: raise ValueError('Wrong values for sub: %d, %d, %d'%(nrows, ncols, idx)) c,r = (idx-1)%ncols,(idx-1)//ncols if not margins: margins = (0.01,0.0,0.0,0.02) extent = (c*1./ncols+margins[0], 1.-(r+1)*1./nrows+margins[1], 1./ncols-margins[2]-margins[0], 1./nrows-margins[3]-margins[1]) extent = (extent[0]+margins[0], extent[1]+margins[1], extent[2]-margins[2]-margins[0], extent[3]-margins[3]-margins[1]) # Starting to draw : turn interactive off wasinteractive = plt.isinteractive() plt.ioff() try: if map is None: map = np.zeros(12)+np.inf cbar=False map = pixelfunc.ma_to_array(map) ax=PA.HpxMollweideAxes(f,extent,coord=coord,rot=rot, format=format2,flipconv=flip) f.add_axes(ax) if remove_dip: map=pixelfunc.remove_dipole(map,gal_cut=gal_cut, nest=nest,copy=True, verbose=True) elif remove_mono: map=pixelfunc.remove_monopole(map,gal_cut=gal_cut,nest=nest, copy=True,verbose=True) img = ax.projmap(map,nest=nest,xsize=xsize,coord=coord,vmin=min,vmax=max, cmap=cmap,norm=norm) if cbar: im = ax.get_images()[0] b = im.norm.inverse(np.linspace(0,1,im.cmap.N+1)) v = np.linspace(im.norm.vmin,im.norm.vmax,im.cmap.N) if matplotlib.__version__ >= '0.91.0': cb=f.colorbar(im,ax=ax, orientation='horizontal', shrink=0.5,aspect=25,ticks=PA.BoundaryLocator(), pad=0.05,fraction=0.1,boundaries=b,values=v, format=format) else: # for older matplotlib versions, no ax kwarg cb=f.colorbar(im,orientation='horizontal', shrink=0.5,aspect=25,ticks=PA.BoundaryLocator(), pad=0.05,fraction=0.1,boundaries=b,values=v, format=format) cb.solids.set_rasterized(True) ax.set_title(title) if not notext: ax.text(0.86,0.05,ax.proj.coordsysstr,fontsize=14, fontweight='bold',transform=ax.transAxes) if cbar: cb.ax.text(0.5,-1.0,unit,fontsize=14, transform=cb.ax.transAxes,ha='center',va='center') f.sca(ax) finally: if plot: plt.draw() if wasinteractive: plt.ion() #plt.show() if return_projected_map: return img
[docs]def render_mollweide(sim, filename=None, r_band='i', g_band='v', b_band='u', r_scale=0.5, g_scale=1.0, b_scale=1.0, dynamic_range=2.0, mag_range=None, width=25, nside=128, starsize=None, plot=True, axes=None, ret_im=False, clear=True, ret_range=False): ''' Make a 3-color all-sky image of stars in a mollweide projection. Adapted from the function pynbody.plot.stars.render The colors are based on magnitudes found using stellar Marigo stellar population code. However there is no radiative transfer to account for dust. Returns: If ret_im=True, an NxNx3 array representing an RGB image **Optional keyword arguments:** *filename*: string (default: None) Filename to be written to (if a filename is specified) *r_band*: string (default: 'i') Determines which Johnston filter will go into the image red channel *g_band*: string (default: 'v') Determines which Johnston filter will go into the image green channel *b_band*: string (default: 'b') Determines which Johnston filter will go into the image blue channel *r_scale*: float (default: 0.5) The scaling of the red channel before channels are combined *g_scale*: float (default: 1.0) The scaling of the green channel before channels are combined *b_scale*: float (default: 1.0) The scaling of the blue channel before channels are combined *width*: float in kpc (default:50) Sets the size of the image field in kpc *starsize*: float in kpc (default: None) If not None, sets the maximum size of stars in the image *ret_im*: bool (default: False) if True, the NxNx3 image array is returned *ret_range*: bool (default: False) if True, the range of the image in mag arcsec^-2 is returned. *plot*: bool (default: True) if True, the image is plotted *axes*: matplotlib axes object (deault: None) if not None, the axes object to plot to *dynamic_range*: float (default: 2.0) The number of dex in luminosity over which the image brightness ranges *mag_range*: float, float (default: None) If provided, the brightest and faintest surface brightnesses in the range, in mag arcsec^-2. Takes precedence over dynamic_range. ''' if isinstance(width, str) or issubclass(width.__class__, _units.UnitBase): if isinstance(width, str): width = _units.Unit(width) width = width.in_units(sim['pos'].units, **sim.conversion_context()) if starsize is not None: smf = filt.HighPass('smooth', str(starsize) + ' kpc') sim.s[smf]['smooth'] = array.SimArray(starsize, 'kpc', sim=sim) r = render_spherical_image(sim.s, qty=r_band + '_lum_den', nside=nside, distance=width, kernel=Kernel2D(),kstep=0.5, denoise=None, out_units="pc^-2", threaded=False)# * r_scale r = mollview(r,return_projected_map=True) * r_scale f=plt.gcf() g = render_spherical_image(sim.s, qty=g_band + '_lum_den', nside=nside, distance=width, kernel=Kernel2D(),kstep=0.5, denoise=None, out_units="pc^-2", threaded=False)# * g_scale g = mollview(g,return_projected_map=True,fig=f) * g_scale f=plt.gcf() b = render_spherical_image(sim.s, qty=b_band + '_lum_den', nside=nside, distance=width, kernel=Kernel2D(),kstep=0.5, denoise=None, out_units="pc^-2", threaded=False)# * b_scale b = mollview(b,return_projected_map=True,fig=f) * b_scale # convert all channels to mag arcsec^-2 r=convert_to_mag_arcsec2(r, mollview=True) g=convert_to_mag_arcsec2(g, mollview=True) b=convert_to_mag_arcsec2(b, mollview=True) if mag_range is None: rgbim, mag_max = combine(r, g, b, dynamic_range*2.5, mollview=True) mag_min = mag_max + 2.5*dynamic_range else: mag_max, mag_min = mag_range rgbim, mag_max = combine(r, g, b, mag_min - mag_max, mag_max, mollview=True) if plot: if clear: plt.clf() if axes is None: axes = plt.gca() if axes: axes.imshow( rgbim[::-1, :])#, extent=(-width / 2, width / 2, -width / 2, width / 2) axes.axis('off') plt.draw() if filename: plt.savefig(filename) if ret_im: return rgbim if ret_range: return mag_max, mag_min
[docs]def sfh(sim, filename=None, massform=True, clear=False, legend=False, subplot=False, trange=False, bins=100, **kwargs): ''' star formation history **Optional keyword arguments:** *trange*: list, array, or tuple size(t_range) must be 2. Specifies the time range. *bins*: int number of bins to use for the SFH *massform*: bool decides whether to use original star mass (massform) or final star mass *subplot*: subplot object where to plot SFH *legend*: boolean whether to draw a legend or not *clear*: boolean if False (default), plot on the current axes. Otherwise, clear the figure first. By default, sfh will use the formation mass of the star. In tipsy, this will be taken from the starlog file. Set massform=False if you want the final (observed) star formation history **Usage:** >>> import pynbody.plot as pp >>> pp.sfh(s,linestyle='dashed',color='k') ''' import matplotlib.pyplot as pyplot if subplot: plt = subplot else: plt = pyplot if "nbins" in kwargs: bins = kwargs['nbins'] if 'nbins' in kwargs: bins = kwargs['nbins'] del kwargs['nbins'] if ((len(sim.g)>0) | (len(sim.d)>0)): simstars = sim.star else: simstars = sim if trange: assert len(trange) == 2 else: trange = [simstars['tform'].in_units( "Gyr").min(), simstars['tform'].in_units("Gyr").max()] binnorm = 1e-9 * bins / (trange[1] - trange[0]) trangefilt = filt.And(filt.HighPass('tform', str(trange[0]) + ' Gyr'), filt.LowPass('tform', str(trange[1]) + ' Gyr')) tforms = simstars[trangefilt]['tform'].in_units('Gyr') if massform: try: weight = simstars[trangefilt][ 'massform'].in_units('Msol') * binnorm except (KeyError, units.UnitsException): warnings.warn( "Could not load massform array -- falling back to current stellar masses", RuntimeWarning) weight = simstars[trangefilt]['mass'].in_units('Msol') * binnorm else: weight = simstars[trangefilt]['mass'].in_units('Msol') * binnorm if clear: plt.clf() sfhist, thebins, patches = plt.hist(tforms, weights=weight, bins=bins, histtype='step', **kwargs) if not subplot: # don't set the limits #plt.ylim(0.0, 1.2 * np.max(sfhist)) plt.xlabel('Time [Gyr]', fontsize='large') plt.ylabel('SFR [M$_\odot$ yr$^{-1}$]', fontsize='large') else: plt.set_ylim(0.0, 1.2 * np.max(sfhist)) # Make both axes have the same start and end point. if subplot: x0, x1 = plt.get_xlim() else: x0, x1 = plt.gca().get_xlim() # add a z axis on top if it has not been already done by an earlier plot: from pynbody.analysis import pkdgrav_cosmo as cosmo c = cosmo.Cosmology(sim=sim) old_axis = pyplot.gca() pz = plt.twiny() labelzs = np.arange(5, int(sim.properties['z']) - 1, -1) times = [13.7 * c.Exp2Time(1.0 / (1 + z)) / c.Exp2Time(1) for z in labelzs] pz.set_xticks(times) pz.set_xticklabels([str(x) for x in labelzs]) pz.set_xlim(x0, x1) pz.set_xlabel('$z$') pyplot.sca(old_axis) if legend: plt.legend(loc=1) if filename: logger.info("Saving %s", filename) plt.savefig(filename) return array.SimArray(sfhist, "Msol yr**-1"), array.SimArray(thebins, "Gyr")
[docs]def schmidtlaw(sim, center=True, filename=None, pretime='50 Myr', diskheight='3 kpc', rmax='20 kpc', compare=True, radial=True, clear=True, legend=True, bins=10, **kwargs): '''Schmidt Law Plots star formation surface density vs. gas surface density including the observed relationships. Currently, only plots densities found in radial annuli. **Usage:** >>> import pynbody.plot as pp >>> pp.schmidtlaw(h[1]) **Optional keyword arguments:** *center*: bool center and align the input simulation faceon. *filename*: string Name of output file *pretime* (default='50 Myr'): age of stars to consider for SFR *diskheight* (default='3 kpc'): height of gas and stars above and below disk considered for SF and gas densities. *rmax* (default='20 kpc'): radius of disk considered *compare* (default=True): whether to include Kennicutt (1998) and Bigiel+ (2008) for comparison *radial* (default=True): should bins be annuli or a rectangular grid? *bins* (default=10): How many radial bins should there be? *legend*: boolean whether to draw a legend or not ''' if not radial: raise NotImplementedError("Sorry, only radial Schmidt law currently supported") if center: angmom.faceon(sim) if isinstance(pretime, str): pretime = units.Unit(pretime) # select stuff diskgas = sim.gas[filt.Disc(rmax, diskheight)] diskstars = sim.star[filt.Disc(rmax, diskheight)] youngstars = np.where(diskstars['tform'].in_units("Myr") > sim.properties['time'].in_units( "Myr", **sim.conversion_context()) - pretime.in_units('Myr'))[0] # calculate surface densities if radial: ps = profile.Profile(diskstars[youngstars], nbins=bins) pg = profile.Profile(diskgas, nbins=bins) else: # make bins 2 kpc nbins = rmax * 2 / binsize pg, x, y = np.histogram2d(diskgas['x'], diskgas['y'], bins=nbins, weights=diskgas['mass'], range=[(-rmax, rmax), (-rmax, rmax)]) ps, x, y = np.histogram2d(diskstars[youngstars]['x'], diskstars[youngstars]['y'], weights=diskstars['mass'], bins=nbins, range=[(-rmax, rmax), (-rmax, rmax)]) if clear: plt.clf() plt.loglog(pg['density'].in_units('Msol pc^-2'), ps['density'].in_units('Msol kpc^-2') / pretime / 1e6, "+", **kwargs) if compare: xsigma = np.logspace(np.log10(pg['density'].in_units('Msol pc^-2')).min(), np.log10( pg['density'].in_units('Msol pc^-2')).max(), 100) ysigma = 2.5e-4 * xsigma ** 1.5 # Kennicutt (1998) xbigiel = np.logspace(1, 2, 10) ybigiel = 10. ** (-2.1) * xbigiel ** 1.0 # Bigiel et al (2007) plt.loglog(xsigma, ysigma, label='Kennicutt (1998)') plt.loglog( xbigiel, ybigiel, linestyle="dashed", label='Bigiel et al (2007)') plt.xlabel('$\Sigma_{gas}$ [M$_\odot$ pc$^{-2}$]') plt.ylabel('$\Sigma_{SFR}$ [M$_\odot$ yr$^{-1}$ kpc$^{-2}$]') if legend: plt.legend(loc=2) if (filename): logger.info("Saving %s", filename) plt.savefig(filename)
[docs]def oneschmidtlawpoint(sim, center=True, pretime='50 Myr', diskheight='3 kpc', rmax='20 kpc', **kwargs): '''One Schmidt Law Point Determines values for star formation surface density and gas surface density for the entire galaxy based on the half mass cold gas radius. **Usage:** import pynbody.plot as pp pp.oneschmidtlawpoint(h[1]) *pretime* (default='50 Myr'): age of stars to consider for SFR *diskheight* (default='3 kpc'): height of gas and stars above and below disk considered for SF and gas densities. *rmax* (default='20 kpc'): radius of disk considered ''' if center: angmom.faceon(sim) cg = h[1].gas[filt.LowPass('temp', 1e5)] cgd = cg[filt.Disc('30 kpc', '3 kpc')] cgs = np.sort(cgd['rxy'].in_units('kpc')) rhgas = cgs[len(cgs) / 2] instars = h[1].star[filt.Disc(str(rhgas) + ' kpc', '3 kpc')] minstars = np.sum( instars[filt.LowPass('age', '100 Myr')]['mass'].in_units('Msol')) ingasm = np.sum( cg[filt.Disc(str(rhgas) + ' kpc', '3 kpc')]['mass'].in_units('Msol')) rpc = rhgas * 1000.0 rkpc = rhgas xsigma = ingasm / (np.pi * rpc * rpc) ysigma = minstars / (np.pi * rkpc * rkpc * 1e8) return xsigma, ysigma
[docs]def satlf(sim, band='v', filename=None, MWcompare=True, Trentham=True, clear=True, legend=True, label='Simulation', **kwargs): ''' satellite luminosity function **Options:** *band* ('v'): which Johnson band to use. available filters: U, B, V, R, I, J, H, K *filename* (None): name of file to which to save output *MWcompare* (True): whether to plot comparison lines to MW *Trentham* (True): whether to plot comparison lines to Trentham + Tully (2009) combined with Koposov et al (2007) By default, satlf will use the formation mass of the star. In tipsy, this will be taken from the starlog file. **Usage:** >>> import pynbody.plot as pp >>> h = s.halos() >>> pp.satlf(h[1],linestyle='dashed',color='k') ''' from ..analysis import luminosity as lum import os halomags = [] # try : for haloid in sim.properties['children']: if (sim._halo_catalogue.contains(haloid)): halo = sim._halo_catalogue[haloid] try: halo.properties[band + '_mag'] = lum.halo_mag(halo, band=band) if np.isfinite(halo.properties[band + '_mag']): halomags.append(halo.properties[band + '_mag']) except IndexError: pass # no stars in satellite # except KeyError: #raise KeyError, str(sim)+' properties have no children key as a halo type would' if clear: plt.clf() plt.semilogy(sorted(halomags), np.arange(len(halomags)) + 1, label=label, **kwargs) plt.xlabel('M$_{' + band + '}$') plt.ylabel('Cumulative LF') if MWcompare: # compare with observations of MW tolfile = os.path.join(os.path.dirname(__file__), "tollerud2008mw") if os.path.exists(tolfile): tolmags = [float(q) for q in file(tolfile).readlines()] else: raise IOError(tolfile + " not found") plt.semilogy(sorted(tolmags), np.arange(len(tolmags)), label='Milky Way') if Trentham: halomags = np.array(halomags) halomags = halomags[np.asarray(np.where(np.isfinite(halomags)))] xmag = np.linspace(halomags.min(), halomags.max(), 100) # Trentham + Tully (2009) equation 6 # number of dwarfs between -11>M_R>-17 is well correlated with mass logNd = 0.91 * np.log10(sim.properties['mass']) - 10.2 # set Nd from each equal to combine Trentham + Tully with Koposov coeff = 10.0 ** logNd / (10 ** -0.6 - 10 ** -1.2) # print 'Koposov coefficient:'+str(coeff) # Analytic expression for MW from Koposov #import pdb; pdb.set_trace() yn = coeff * 10 ** ((xmag + 5.0) / 10.0) # Koposov et al (2007) plt.semilogy(xmag, yn, linestyle="dashed", label='Trentham & Tully (2009)') if legend: plt.legend(loc=2) if (filename): logger.info("Saving %s", filename) plt.savefig(filename)
[docs]def sbprofile(sim, band='v', diskheight='3 kpc', rmax='20 kpc', binning='equaln', center=True, clear=True, filename=None, axes=False, fit_exp=False, print_ylabel=True, fit_sersic=False, **kwargs): ''' surface brightness profile **Usage:** >>> import pynbody.plot as pp >>> h = s.halos() >>> pp.sbprofile(h[1],exp_fit=3,linestyle='dashed',color='k') **Options:** *band* ('v'): which Johnson band to use. available filters: U, B, V, R, I, J, H, K *fit_exp*(False): Fits straight exponential line outside radius specified. *fit_sersic*(False): Fits Sersic profile outside radius specified. *diskheight('3 kpc')* *rmax('20 kpc')*: Size of disk to be profiled *binning('equaln')*: How show bin sizes be determined? based on pynbody.analysis.profile *center(True)*: Automatically align face on and center? *axes(False)*: In which axes (subplot) should it be plotted? *filename* (None): name of file to which to save output **needs a description of all keywords** By default, sbprof will use the formation mass of the star. In tipsy, this will be taken from the starlog file. ''' if center: logger.info("Centering...") angmom.faceon(sim) logger.info("Selecting disk stars") diskstars = sim.star[filt.Disc(rmax, diskheight)] logger.info("Creating profile") ps = profile.Profile(diskstars, type=binning) logger.info("Plotting") r = ps['rbins'].in_units('kpc') if axes: plt = axes else: import matplotlib.pyplot as plt if clear: plt.clf() plt.plot(r, ps['sb,' + band], linewidth=2, **kwargs) if axes: plt.set_ylim(max(ps['sb,' + band]), min(ps['sb,' + band])) else: plt.ylim(max(ps['sb,' + band]), min(ps['sb,' + band])) if fit_exp: exp_inds = np.where(r.in_units('kpc') > fit_exp) expfit = np.polyfit(np.array(r[exp_inds]), np.array(ps['sb,' + band][exp_inds]), 1) # 1.0857 is how many magnitudes a 1/e decrease is print(("h: ", 1.0857 / expfit[0], " u_0:", expfit[1])) fit = np.poly1d(expfit) if 'label' in kwargs: del kwargs['label'] if 'linestyle' in kwargs: del kwargs['linestyle'] plt.plot(r, fit(r), linestyle='dashed', **kwargs) if fit_sersic: sersic_inds = np.where(r.in_units('kpc') < fit_sersic) sersicfit = np.polyfit(np.log10(np.array(r[sersic_inds])), np.array(ps['sb,' + band][sersic_inds]), 1) fit = np.poly1d(sersicfit) print(("n: ", sersicfit[0], " other: ", sersicfit[1])) if 'label' in kwargs: del kwargs['label'] if 'linestyle' in kwargs: del kwargs['linestyle'] plt.plot(r, fit(r), linestyle='dashed', **kwargs) #import pdb; pdb.set_trace() if axes: if print_ylabel: plt.set_ylabel(band + '-band Surface brightness [mag as$^{-2}$]') else: plt.xlabel('R [kpc]') plt.ylabel(band + '-band Surface brightness [mag as$^{-2}$]') if filename: logger.info("Saving %s", filename) plt.savefig(filename)
def f(x, alpha, delta, g): return -np.log10(10.0 ** (x * alpha) + 1.0) + delta * (np.log10(1 + np.exp(x))) ** g / (1 + np.exp(10 ** -x))
[docs]def behroozi(xmasses, z, alpha=-1.412, Kravtsov=False): '''Based on Behroozi+ (2013) return what stellar mass corresponds to the halo mass passed in. **Usage** >>> from pynbody.plot.stars import moster >>> xmasses = np.logspace(np.log10(min(totmasshalos)),1+np.log10(max(totmasshalos)),20) >>> ystarmasses, errors = moster(xmasses,halo_catalog._halos[1].properties['z']) >>> plt.fill_between(xmasses,np.array(ystarmasses)/np.array(errors), y2=np.array(ystarmasses)*np.array(errors), facecolor='#BBBBBB',color='#BBBBBB') ''' loghm = np.log10(xmasses) # from Behroozi et al (2013) if Kravtsov: EPS = -1.642 else: EPS = -1.777 EPSpe = 0.133 EPSme = 0.146 EPSanu = -0.006 EPSanupe = 0.113 EPSanume = 0.361 EPSznu = 0 EPSznupe = 0.003 EPSznume = 0.104 EPSa = 0.119 EPSape = 0.061 EPSame = -0.012 M1 = 11.514 M1pe = 0.053 M1me = 0.009 M1a = -1.793 M1ape = 0.315 M1ame = 0.330 M1z = -0.251 M1zpe = 0.012 M1zme = 0.125 if Kravtsov: alpha=-1.779 AL = alpha ALpe = 0.02 ALme = 0.105 ALa = 0.731 ALape = 0.344 ALame = 0.296 if Kravtsov: DEL=4.394 else: DEL = 3.508 DELpe = 0.087 DELme = 0.369 DELa = 2.608 DELape = 2.446 DELame = 1.261 DELz = -0.043 DELzpe = 0.958 DELzme = 0.071 if Kravtsov: G=0.547 else: G = 0.316 Gpe = 0.076 Gme = 0.012 Ga = 1.319 Gape = 0.584 Game = 0.505 Gz = 0.279 Gzpe = 0.256 Gzme = 0.081 a = 1.0 / (z + 1.0) nu = np.exp(-4 * a ** 2) logm1 = M1 + nu * (M1a * (a - 1.0) + M1z * z) logeps = EPS + nu * (EPSanu * (a - 1.0) + EPSznu * z) - EPSa * (a - 1.0) analpha = AL + nu * ALa * (a - 1.0) delta = DEL + nu * DELa * (a - 1.0) g = G + nu * (Ga * (a - 1.0) + z * Gz) x = loghm - logm1 f0 = -np.log10(2.0) + delta * np.log10(2.0) ** g / (1.0 + np.exp(1)) smp = logm1 + logeps + f(x, analpha, delta, g) - f0 if isinstance(smp, np.ndarray): scatter = np.zeros(len(smp)) scatter = 0.218 - 0.023 * (a - 1.0) return 10 ** smp, 10 ** scatter
[docs]def moster(xmasses, z): '''Based on Moster+ (2013) return what stellar mass corresponds to the halo mass passed in. **Usage** >>> from pynbody.plot.stars import moster >>> xmasses = np.logspace(np.log10(min(totmasshalos)),1+np.log10(max(totmasshalos)),20) >>> ystarmasses, errors = moster(xmasses,halo_catalog._halos[1].properties['z']) >>> plt.fill_between(xmasses,np.array(ystarmasses)/np.array(errors), y2=np.array(ystarmasses)*np.array(errors), facecolor='#BBBBBB',color='#BBBBBB') ''' hmp = np.log10(xmasses) # from Moster et al (2013) M10a = 11.590470 M11a = 1.194913 R10a = 0.035113 R11a = -0.024729 B10a = 1.376177 B11a = -0.825820 G10a = 0.608170 G11a = 0.329275 M10e = 0.236067 M11e = 0.353477 R10e = 0.00577173 R11e = 0.00693815 B10e = 0.153 B11e = 0.225 G10e = 0.059 G11e = 0.173 a = 1.0 / (z + 1.0) m1 = M10a + M11a * (1.0 - a) r = R10a + R11a * (1.0 - a) b = B10a + B11a * (1.0 - a) g = G10a + G11a * (1.0 - a) smp = hmp + np.log10(2.0 * r) - np.log10((10.0 ** (hmp - m1)) ** (-b) + (10.0 ** (hmp - m1)) ** (g)) eta = np.exp(np.log(10.) * (hmp - m1)) alpha = eta ** (-b) + eta ** g dmdm10 = (g * eta ** g + b * eta ** (-b)) / alpha dmdm11 = (g * eta ** g + b * eta ** (-b)) / alpha * (1.0 - a) dmdr10 = np.log10(np.exp(1.0)) / r dmdr11 = np.log10(np.exp(1.0)) / r * (1.0 - a) dmdb10 = np.log10(np.exp(1.0)) / alpha * np.log(eta) * eta ** (-b) dmdb11 = np.log10(np.exp(1.0)) / alpha * \ np.log(eta) * eta ** (-b) * (1.0 - a) dmdg10 = -np.log10(np.exp(1.0)) / alpha * np.log(eta) * eta ** g dmdg11 = -np.log10(np.exp(1.0)) / alpha * \ np.log(eta) * eta ** g * (1.0 - a) sigma = np.sqrt(dmdm10 * dmdm10 * M10e * M10e + dmdm11 * dmdm11 * M11e * M11e + dmdr10 * dmdr10 * R10e * R10e + dmdr11 * dmdr11 * R11e * R11e + dmdb10 * dmdb10 * B10e * B10e + dmdb11 * dmdb11 * B11e * B11e + dmdg10 * dmdg10 * G10e * G10e + dmdg11 * dmdg11 * G11e * G11e) return 10 ** smp, 10 ** sigma
[docs]def hudson(xmasses, z): ''' Based on Hudson+ (2014), returns what stellar mass corresponds to the halo mass passed in. This is the only SMHMR function that is not based on abundance matching, but instead uses date from CFHTLenS galaxy lensing data. >>> from pynbody.plot.stars import hudson >>> xmasses = np.logspace(np.log10(min(totmasshalos)),1+np.log10(max(totmasshalos)),20) >>> ystarmasses, errors = hudson(xmasses,halo_catalog._halos[1].properties['z']) >>> plt.fill_between(xmasses,np.array(ystarmasses)/np.array(errors), y2=np.array(ystarmasses)*np.array(errors), facecolor='#BBBBBB',color='#BBBBBB') ''' f05 = 0.0414 f05_err = 0.0024 fz = 0.029 fz_err = 0.009 log10M05 = 12.07 log10M05_err = 0.07 Mz = 0.09 Mz_err = 0.24 beta = 0.69 beta_err = 0.09 gamma = 0.8 f1 = f05 + (z-0.5)*fz f1_err = np.sqrt(f05_err**2+(z-0.5)**2*fz_err**2) M1_exp = (log10M05+(z-0.5)*Mz) M1_exp_err = np.sqrt(log10M05_err**2+(z-0.5)**2*Mz_err**2) M1 = np.power(10, M1_exp) M1_err = np.abs(M1*np.log(10)*M1_exp_err) beta_term = np.power(xmasses/M1, -beta) beta_term_err = np.sqrt((beta/M1*M1_err/M1**2)**2 + (np.log(M1)*beta_err)**2) gamma_term = np.power(xmasses/M1, gamma) gamma_term_err = np.abs(gamma_term*gamma*M1_err/M1**3) fstar_denom = beta_term + gamma_term fstar_denom_err = np.sqrt(beta_term*2+ gamma_term**2) fstar = 2.0*f1/fstar_denom fstar_err = np.sqrt((f1_err/f1)**2 + (fstar_denom_err/fstar_denom)**2) return fstar*xmasses, 2.0/fstar_err
[docs]def subfindguo(halo_catalog, clear=False, compare=True, baryfrac=False, filename=False, **kwargs): '''Stellar Mass vs. Halo Mass Takes a halo catalogue and plots the member stellar masses as a function of halo mass. Usage: >>> import pynbody.plot as pp >>> h = s.halos() >>> pp.guo(h,marker='+',markerfacecolor='k') **Options:** *compare* (True): Should comparison line be plotted? If compare = 'guo', Guo+ (2010) plotted instead of Behroozi+ (2013) *baryfrac* (False): Should line be drawn for cosmic baryon fraction? *filename* (None): name of file to which to save output ''' # if 'marker' not in kwargs : # kwargs['marker']='o' starmasshalos = [] totmasshalos = [] f_b = halo_catalog[0].properties['omegaB0']/halo_catalog[0].properties['omegaM0'] for halo in halo_catalog: for subhalo in halo.sub: subhalo.properties['MassType'].convert_units('Msol') halostarmass = subhalo.properties['MassType'][4] if halostarmass: starmasshalos.append(halostarmass) totmasshalos.append(np.sum(subhalo.properties['MassType'])) if clear: plt.clf() plt.loglog(totmasshalos, starmasshalos, 'o', **kwargs) plt.xlabel('Total Halo Mass') plt.ylabel('Halo Stellar Mass') if compare: xmasses = np.logspace( np.log10(min(totmasshalos)), 1 + np.log10(max(totmasshalos)), 20) if compare == 'guo': # from Sawala et al (2011) + Guo et al (2009) ystarmasses = xmasses*0.129*((xmasses/2.5e11)**-0.926 + (xmasses/2.5e11)**0.261)**-2.44 else : ystarmasses, errors = behroozi(xmasses,halo_catalog._halos[1].properties['z']) plt.fill_between(xmasses,np.array(ystarmasses)/np.array(errors), y2=np.array(ystarmasses)*np.array(errors), facecolor='#BBBBBB',color='#BBBBBB') plt.loglog(xmasses,ystarmasses,label='Behroozi et al (2013)') if baryfrac : xmasses = np.logspace(np.log10(min(totmasshalos)),1+np.log10(max(totmasshalos)),20) ystarmasses = xmasses*f_b plt.loglog(xmasses,ystarmasses,linestyle='dotted',label='f_b = '+'%.2f' % f_b) ystarmasses = xmasses*0.1*f_b plt.loglog(xmasses,ystarmasses,linestyle='dashed',label='0.1 f_b = '+'%.2f' % (0.1*f_b)) plt.axis([0.8*min(totmasshalos),1.2*max(totmasshalos), 0.8*min(starmasshalos),1.2*max(starmasshalos)]) if (filename): logger.info("Saving %s", filename) plt.savefig(filename)
[docs]def guo(halo_catalog, clear=False, compare=True, baryfrac=False, filename=False, **kwargs): '''Stellar Mass vs. Halo Mass Takes a halo catalogue and plots the member stellar masses as a function of halo mass. Usage: >>> import pynbody.plot as pp >>> h = s.halos() >>> pp.guo(h,marker='+',markerfacecolor='k') **Options:** *compare* (True): Should comparison line be plotted? If compare = 'guo', Guo+ (2010) plotted instead of Behroozi+ (2013) *baryfrac* (False): Should line be drawn for cosmic baryon fraction? *filename* (None): name of file to which to save output ''' # if 'marker' not in kwargs : # kwargs['marker']='o' starmasshalos = [] totmasshalos = [] halo_catalog._halos[1]['mass'].convert_units('Msol') for i in np.arange(len(halo_catalog._halos)) + 1: halo = halo_catalog[i] halostarmass = np.sum(halo.star['mass']) if halostarmass: starmasshalos.append(halostarmass) totmasshalos.append(np.sum(halo['mass'])) if clear: plt.clf() plt.loglog(totmasshalos, starmasshalos, 'o', **kwargs) plt.xlabel('Total Halo Mass') plt.ylabel('Halo Stellar Mass') if compare: xmasses = np.logspace( np.log10(min(totmasshalos)), 1 + np.log10(max(totmasshalos)), 20) if compare == 'guo': # from Sawala et al (2011) + Guo et al (2009) ystarmasses = xmasses*0.129*((xmasses/2.5e11)**-0.926 + (xmasses/2.5e11)**0.261)**-2.44 else : ystarmasses, errors = behroozi(xmasses,halo_catalog._halos[1].properties['z']) plt.fill_between(xmasses,np.array(ystarmasses)/np.array(errors), y2=np.array(ystarmasses)*np.array(errors), facecolor='#BBBBBB',color='#BBBBBB') plt.loglog(xmasses,ystarmasses,label='Behroozi et al (2013)') if baryfrac : xmasses = np.logspace(np.log10(min(totmasshalos)),1+np.log10(max(totmasshalos)),20) ystarmasses = xmasses*0.04/0.24 plt.loglog(xmasses,ystarmasses,linestyle='dotted',label='f_b = 0.16') plt.axis([0.8*min(totmasshalos),1.2*max(totmasshalos), 0.8*min(starmasshalos),1.2*max(starmasshalos)]) if (filename): logger.info("Saving %s", filename) plt.savefig(filename)