Exemple #1
0
	def caustic_stack(self,Rdata,Vdata,HaloID,HaloData,stack_num,
				ens_shiftgap=True,edge_int_remove=True,gal_reduce=True,stack_raw=False,est_v_center=False,
				feed_mags=True,G_Mags=None,R_Mags=None,I_Mags=None,clus_z=0):
		"""
		-- Takes an array of individual phase spaces and stacks them, then runs 
		   a caustic technique over the ensemble and/or individual phase spaces.
		-- Returns a dictionary

		-- Relies on a few parameters to be defined outside of the function:
			self.gal_num - Equivalent to Ngal: number of galaxies to take per cluster to then be stacked
			self.line_num - Equivalent to Nclus: number of clusters to stack into one ensemble cluster
			self.scale_data - Scale r data by R200 while stacking, then un-scale by BinR200
			self.run_los - run caustic technique over individual cluster (aka line-of-sight)
			self.avg_meth - method by which averaging of Bin Properties should be, 'mean' or 'median' or 'biweight' etc..
			self.mirror - mirror phase space before solving for caustic?
			Including others... see RunningTheCode.pdf for a list
			* These parameters should be fed to initialization of Stack() class as a dictionary, for ex:
				variables = {'run_los':False, ...... }
				S = Stack(variables)

		"rdata" - should be a 2 dimensional array with individual phase spaces as rows
		"vdata" - should be a 2 dimensional array with individual phase spaces as rows
				ex. rdata[0] => 0th phase space data
		'HaloID' : 1 dimensional array containing Halo Identification Numbers, len(HaloID) == len(rdata)
		'HaloData' : 2 dimensional array containing M200, R200, HVD, Z of Halos, with unique halos as columns
		'stack_num' : number of individual clusters to stack into the one ensemble

		'ens_shiftgap' - do a shiftgapper over final ensemble phase space?
		'gal_reduce' - before caustic run, reduce ensemble phase space to Ngal gals within R200?
		'stack_raw' - don't worry about limiting or building the phase space, just stack the Rdata and Vdata together as is
		'est_v_center' - take median of Vdata for new velocity center

		'feed_gal_mags' - feed magnitudes for individual galaxies, must feed all three mags if True

		-- 'ens' stands for ensemble cluster
		-- 'ind' stands for individual cluster

		-- Uppercase arrays contain data for multiple clusters,
			lowercase arrays contain data for 1 cluster
		"""

		# Define a container for holding stacked data, D
		D = Data()

		# Assign some parameters to Class scope
		self.__dict__.update(ez.create(['stack_raw','feed_mags','gal_reduce','ens_shiftgap','edge_int_remove'],locals()))

		# New Velocity Center
		if est_v_center == True:
			v_offset = astats.biweight_location(vdata[np.where(rdata < 1.0)])
			vdata -= v_offset

		# Unpack HaloData
		if HaloData == None:
			self.fed_halo_data = False
			# Estimate R200
			R200 = []
			HVD = []
			Z = []
			for i in range(stack_num):
				R200.append(np.exp(-1.86)*len(np.where((R_Mags[i] < -19.55) & (Rdata[i] < 1.0) & (np.abs(Vdata[i]) < 3500))[0])**0.51)
				HVD.append(astats.biweight_midvariance(Vdata[i][np.where((Rdata[i] < 1.0)&(np.abs(Vdata[i])<4000))]))
			R200 = np.array(R200)
			HVD = np.array(HVD)
			Z = np.array([0.05]*len(R200))
			if self.avg_meth == 'mean': BinR200 = np.mean(R200); BinHVD = np.mean(HVD); BinZ = np.mean(Z)
			elif self.avg_meth == 'median': BinR200 = np.median(R200); BinHVD = np.median(HVD); BinZ = np.median(Z)
			D.add({'BinR200':BinR200,'BinHVD':BinHVD,'BinZ':BinZ,'R200':R200,'HVD':HVD,'Z':Z})

		else:
			self.fed_halo_data = True
			M200,R200,HVD,Z = HaloData
			if self.avg_meth == 'mean':
				BinM200 = np.mean(M200)
				BinR200 = np.mean(R200)
				BinHVD = np.mean(HVD)
				BinZ = np.mean(Z)
			elif self.avg_meth == 'median':
				BinM200 = np.median(M200)
				BinR200 = np.median(R200)
				BinHVD = np.median(HVD)
				BinZ = np.median(Z)
			# Append to Data
			D.add({'BinM200':BinM200,'BinR200':BinR200,'BinHVD':BinHVD,'BinZ':BinZ})	

		# Create Dummy Variables for Magnitudes if necessary
		if self.feed_mags == False:
			G_Mags,R_Mags,I_Mags = [],[],[]
			for i in range(stack_num):
				G_Mags.append([None]*len(Rdata[i]))
				R_Mags.append([None]*len(Rdata[i]))
				I_Mags.append([None]*len(Rdata[i]))
			G_Mags,R_Mags,I_Mags = np.array(G_Mags),np.array(R_Mags),np.array(I_Mags)

		# Create galaxy identification arrays
		ENS_gal_id,ENS_clus_id,IND_gal_id = [],[],[]
		gal_count = 0
		for i in range(stack_num):
			ENS_gal_id.append(np.arange(gal_count,gal_count+len(Rdata[i])))
			ENS_clus_id.append(np.array([HaloID[i]]*len(Rdata[i]),int))
			IND_gal_id.append(np.arange(len(Rdata[i])))
		ENS_gal_id,ENS_clus_id,ENS_gal_id = np.array(ENS_gal_id),np.array(ENS_clus_id),np.array(IND_gal_id)

		# Iterate through phase spaces
		for self.l in range(stack_num):

			# Limit Phase Space
			if self.stack_raw == False:
				r,v,ens_gal_id,ens_clus_id,ind_gal_id,gmags,rmags,imags,samp_size = self.U.limit_gals(Rdata[self.l],Vdata[self.l],ENS_gal_id[self.l],ENS_clus_id[self.l],IND_gal_id[self.l],G_Mags[self.l],R_Mags[self.l],I_Mags[self.l],R200[self.l])

			# Build Ensemble and LOS Phase Spaces
			if self.stack_raw == False:
				ens_r,ens_v,ens_gal_id,ens_clus_id,ens_gmags,ens_rmags,ens_imags,ind_r,ind_v,ind_gal_id,ind_gmags,ind_rmags,ind_imags = self.U.build(r,v,ens_gal_id,ens_clus_id,ind_gal_id,gmags,rmags,imags,R200[self.l])

			# If Scale data before stack is desired
			if self.scale_data == True:
				ens_r /= R200[self.l]

			# Stack Ensemble Data by extending to Data() container
			names = ['ens_r','ens_v','ens_gmags','ens_rmags','ens_imags','ens_gal_id','ens_clus_id']
			D.extend(ez.create(names,locals()))

			if self.run_los == True:
		
				# Create Data Block
				ind_data = np.vstack([ind_r,ind_v,ind_gal_id,ind_gmags,ind_rmags,ind_imags])

				# Sort by Rmag 
				bright = np.argsort(ind_rmags)
				ind_data = ind_data.T[bright].T
				ind_r,ind_v,ind_gal_id,ind_gmags,ind_rmags,ind_imags = ind_data
	
				# Reduce phase space
				if self.stack_raw == False and self.gal_reduce == True:
					within = np.where(ind_r <= R200[self.l])[0]
					end = within[:self.gal_num + 1][-1]
					ind_data = ind_data.T[:end].T
					ind_r,ind_v,ind_gal_id,ind_gmags,ind_rmags,ind_imags = ind_data
	
				# Calculate individual HVD
				# Pick out gals within r200
				within = np.where(ind_r <= R200[self.l])[0]
				gal_count = len(within)
				if gal_count <= 3:
					'''biweightScale can't take less than 4 elements'''
					# Calculate hvd with numpy std of galaxies within r200 (b/c this is quoted richness)
					ind_hvd = np.std(np.copy(ind_v)[within])
				else:
					# Calculate hvd with astStats biweightScale (see Beers 1990)
					try:
						ind_hvd = astats.biweight_midvariance(np.copy(ind_v)[within])
					# Sometimes divide by zero error in biweight function for low gal_num
					except ZeroDivisionError:
						print 'ZeroDivisionError in biweightfunction'
						print 'ind_v[within]=',ind_v[within]
						ind_hvd = np.std(np.copy(ind_v)[within])

				# If run_los == True, run Caustic Technique on individual cluster
				self.U.print_separation('# Running Caustic for LOS '+str(self.l),type=2)
				self.run_caustic(ind_r,ind_v,R200[self.l],ind_hvd,clus_z=Z[self.l],mirror=self.mirror)
				ind_caumass = np.array([self.C.M200_fbeta])
				ind_caumass_est = np.array([self.C.Mass2.M200_est])
				ind_edgemass = np.array([self.C.M200_edge])
				ind_edgemass_est = np.array([self.C.MassE.M200_est])
				ind_causurf = np.array(self.C.caustic_profile)
				ind_nfwsurf = np.array(self.C.caustic_fit)
				ind_edgesurf = np.array(self.C.caustic_edge)

			# Append Individual Cluster Data
			names = ['ind_r','ind_v','ind_gal_id','ind_gmags','ind_rmags','ind_imags',
				'ind_hvd','ind_caumass','ind_caumass_est','ind_edgemass','ind_edgemass_est',
				'ind_causurf','ind_nfwsurf']
			D.append(ez.create(names,locals()))


		# Turn Ensemble into Arrays
		names = ['ens_r','ens_v','ens_gal_id','ens_clus_id','ens_gmags','ens_rmags','ens_imags','ens_caumass','ens_caumass_est','ens_edgemass','ens_edgemass_est','ens_causurf','ens_nfwsurf','ens_edgesurf']
		D.to_array(names,ravel=True)

		# Re-scale data if scale_data == True:
		if self.scale_data == True:
			D.ens_r *= BinR200

		# Create Ensemble Data Block
		D.ens_data = np.vstack([D.ens_r,D.ens_v,D.ens_gal_id,D.ens_clus_id,D.ens_gmags,D.ens_rmags,D.ens_imags])

		# Shiftgapper for Interloper Treatment
		if self.stack_raw == False and self.ens_shiftgap == True:
			self.D = D
			try:
				D.ens_data = self.C.shiftgapper(D.ens_data.T).T
				D.ens_r,D.ens_v,D.ens_gal_id,D.ens_clus_id,D.ens_gmags,D.ens_rmags,D.ens_imags = D.ens_data
			except UnboundLocalError:	# A couple or no galaxies within RVIR
				print '-'*40
				print 'UnboundLocalError raised on Ensemble Shiftgapper'
				print '-'*40

		D.ens_r,D.ens_v,D.ens_gal_id,D.ens_clus_id,D.ens_gmags,D.ens_rmags,D.ens_imags = D.ens_data

		# Sort by R_Mag
		bright = np.argsort(D.ens_rmags)
		D.ens_data = D.ens_data.T[bright].T
		D.ens_r,D.ens_v,D.ens_gal_id,D.ens_clus_id,D.ens_gmags,D.ens_rmags,D.ens_imags = D.ens_data

		# Reduce System Down to gal_num richness within BinR200
		if self.stack_raw and self.gal_reduce == True:
			within = np.where(D.ens_r <= BinR200)[0]
			print 'len(within)',len(within)
			print 'gal_num,line_num',self.gal_num,self.line_num
			end = within[:self.gal_num*self.line_num + 1][-1]
			D.ens_data = D.ens_data.T[:end].T
			D.ens_r,D.ens_v,D.ens_gal_id,D.ens_clus_id,D.ens_gmags,D.ens_rmags,D.ens_imags = D.ens_data

		# Calculate Ensemble Velocity Dispersion for galaxies within R200
		ens_hvd = astats.biweight_midvariance(np.copy(D.ens_v)[np.where(D.ens_r<=BinR200)])

		# Run Caustic Technique!
		try: self.U.print_separation('# Running Caustic on Ensemble '+str(self.j),type=2)
		except: pass
		try:
			self.run_caustic(D.ens_r,D.ens_v,BinR200,ens_hvd,clus_z=BinZ,mirror=self.mirror,shiftgap=self.ens_shiftgap,edge_int_remove=self.edge_int_remove)
			ens_caumass = np.array([self.C.M200_fbeta])
			ens_caumass_est = np.array([self.C.Mass2.M200_est])
			ens_edgemass = np.array([self.C.M200_edge])
			ens_edgemass_est = np.array([self.C.MassE.M200_est])
			ens_causurf = np.array(self.C.caustic_profile)
			ens_nfwsurf = np.array(self.C.caustic_fit)
			ens_edgesurf = np.array(self.C.caustic_edge)
			ens_caumass500_est = np.array(self.C.Mass2.M500_est)
			ens_edgemass500_est = np.array(self.C.MassE.M500_est)
			ens_r200_est = np.array(self.C.r200_est_fbeta)
			ens_r500_est = np.array(self.C.r500_est_fbeta)
			ens_r200_est_edge = np.array(self.C.r200_est_edge)

		except:
			print ''
			print '-'*45
			print 'CAUSTIC TECHNIQUE FAILED ON THIS CLUSTER'
			print '-'*45
			print ''
			ens_caumass = np.array([0])
			ens_caumass_est = np.array([0])
			ens_edgemass = np.array([0])
			ens_edgemass_est = np.array([0])
			ens_causurf = np.array([0]*len(self.C.x_range))
			ens_nfwsurf = np.array([0]*len(self.C.x_range))
			ens_edgesurf = np.array([0]*len(self.C.x_range))
			ens_caumass500_est = np.array([0])
			ens_edgemass500_est = np.array([0])
			ens_r200_est = np.array([0])
			ens_r500_est = np.array([0])
			ens_r200_est_edge = np.array([0])

		# Other Arrays
		x_range = self.C.x_range

		# Append Data
		names = ['ens_caumass','ens_hvd','ens_caumass_est','ens_edgemass','ens_edgemass_est','ens_causurf','ens_nfwsurf','ens_edgesurf','x_range','ens_caumass500_est','ens_edgemass500_est','ens_r200_est','ens_r500_est','ens_r200_est_edge']
		D.add(ez.create(names,locals()))

		# Turn Individual Data into Arrays
		if self.run_los == True:
			names = ['ind_caumass','ind_caumass_est','ind_edgemass','ind_edgemass_est','ind_hvd']
			D.to_array(names,ravel=True)
			names = ['ind_r','ind_v','ind_gal_id','ind_gmags','ind_rmags','ind_imags','ind_causurf','ind_nfwsurf','ind_edgesurf']
			D.to_array(names)	

		# Output Data
		return D.__dict__
    def stat_calc(self,
                  MASS_EST,
                  MASS_TRUE,
                  HVD_EST,
                  HVD_TRUE,
                  data_set='full',
                  ens=True):
        ''' Does bias and scatter calculations '''
        # Cut data set if necessary
        if data_set == 'cut_low_mass':
            '''Cutting all 'true' mass estimates below 1e14 off'''
            cut = np.where(MASS_TRUE > 1e14)[0]
            MASS_EST = MASS_EST[cut]
            MASS_TRUE = MASS_TRUE[cut]
            HVD_EST = HVD_EST[cut]
            HVD_TRUE = HVD_TRUE[cut]

        # Define a Masked array for sometimes zero terms
        epsilon = 10.0
        use_est = False  # Use MassCalc estimated r200 mass values if true
        maMASS_EST = ma.masked_array(
            MASS_EST, mask=MASS_EST < epsilon)  # Mask essentially zero values
        maHVD_EST = ma.masked_array(HVD_EST, mask=HVD_EST < epsilon)

        # Mass / HVD Fractions
        if ens == True:
            # Ensemble Arrays
            MFRAC = np.log(maMASS_EST / MASS_TRUE)
            VFRAC = np.log(maHVD_EST / HVD_TRUE)
        else:
            # LOS Mass Fraction Arrays: 0th axis is halo number, 1st axis is line of sight number
            MFRAC, VFRAC = [], []
            for a in range(len(MASS_EST)):
                MFRAC.append(ma.log(maMASS_EST[a] / MASS_TRUE[a]))
                VFRAC.append(ma.log(maHVD_EST[a] / HVD_TRUE[a]))
            MFRAC, VFRAC = np.array(MFRAC), np.array(VFRAC)

        if ens == True:
            mbias, mscat = astrostats.biweight_location(
                MFRAC, 6.0), astrostats.biweight_midvariance(MFRAC, 9.0)
            vbias, vscat = astrostats.biweight_location(
                VFRAC, 6.0), astrostats.biweight_midvariance(VFRAC, 9.0)
            return MFRAC, mbias, mscat, VFRAC, vbias, vscat
        else:
            if self.ss:
                # Create vertically averaged (by halo averaged) arrays, with line_num elements
                # biweightLocation takes only arrays with 4 or more elements
                HORZ_MFRAC, HORZ_VFRAC = [], []
                VERT_MFRAC, VERT_VFRAC = [], []
                for a in range(self.line_num):
                    if len(ma.compressed(MFRAC[:, a])) > 4:
                        VERT_MFRAC.append(
                            astrostats.biweight_location(
                                ma.compressed(MFRAC[:, a]), 6.0))
                        VERT_VFRAC.append(
                            astrostats.biweight_location(
                                ma.compressed(VFRAC[:, a]), 6.0))
                    else:
                        VERT_MFRAC.append(np.median(ma.compressed(MFRAC[:,
                                                                        a])))
                        VERT_VFRAC.append(np.median(ma.compressed(VFRAC[:,
                                                                        a])))
                VERT_MFRAC, VERT_VFRAC = np.array(VERT_MFRAC), np.array(
                    VERT_VFRAC)
                # Create horizontally averaged (by line of sight) arrays, with halo_num elements
                for a in self.halo_range:
                    if len(ma.compressed(MFRAC[a])) > 4:
                        HORZ_MFRAC.append(
                            astrostats.biweight_location(
                                ma.compressed(MFRAC[a]), 6.0))
                        HORZ_VFRAC.append(
                            astrostats.biweight_location(
                                ma.compressed(VFRAC[a]), 6.0))
                    else:
                        HORZ_MFRAC.append(np.median(ma.compressed(MFRAC[a])))
                        HORZ_VFRAC.append(np.median(ma.compressed(VFRAC[a])))
                HORZ_MFRAC, HORZ_VFRAC = np.array(HORZ_MFRAC), np.array(
                    HORZ_VFRAC)
                # Bias and Scatter Calculations
                mbias, mscat = astrostats.biweight_location(
                    VERT_MFRAC,
                    6.0), astrostats.biweight_midvariance(VERT_MFRAC, 9.0)
                vbias, vscat = astrostats.biweight_location(
                    VERT_VFRAC,
                    6.0), astrostats.biweight_midvariance(VERT_VFRAC, 9.0)
            else:
                # Bin stack LOS systems need only one average
                mbias, mscat = astrostats.biweight_location(
                    MFRAC, 6.0), astrostats.biweight_midvariance(MFRAC, 9.0)
                vbias, vscat = astrostats.biweight_location(
                    VFRAC, 6.0), astrostats.biweight_midvariance(VFRAC, 9.0)

            return MFRAC, mbias, mscat, VFRAC, vbias, vscat
	def stat_calc(self,MASS_EST,MASS_TRUE,HVD_EST,HVD_TRUE,data_set='full',ens=True):
		''' Does bias and scatter calculations '''
                # Cut data set if necessary
                if data_set == 'cut_low_mass':
                        '''Cutting all 'true' mass estimates below 1e14 off'''
                        cut = np.where(MASS_TRUE>1e14)[0]
                        MASS_EST = MASS_EST[cut]
                        MASS_TRUE = MASS_TRUE[cut]
                        HVD_EST = HVD_EST[cut]
                        HVD_TRUE = HVD_TRUE[cut]

		# Define a Masked array for sometimes zero terms
		epsilon = 10.0
		use_est = False				# Use MassCalc estimated r200 mass values if true
		maMASS_EST	= ma.masked_array(MASS_EST,mask=MASS_EST<epsilon)		# Mask essentially zero values
		maHVD_EST	= ma.masked_array(HVD_EST,mask=HVD_EST<epsilon)


		# Mass / HVD Fractions
		if ens == True:
			# Ensemble Arrays
			MFRAC = np.log(maMASS_EST/MASS_TRUE)
			VFRAC = np.log(maHVD_EST/HVD_TRUE)
		else:
			# LOS Mass Fraction Arrays: 0th axis is halo number, 1st axis is line of sight number
			MFRAC,VFRAC = [],[]
			for a in range(len(MASS_EST)):
				MFRAC.append( ma.log( maMASS_EST[a]/MASS_TRUE[a] ) )
				VFRAC.append( ma.log( maHVD_EST[a]/HVD_TRUE[a] ) )
			MFRAC,VFRAC = np.array(MFRAC),np.array(VFRAC)

		if ens == True:
			mbias,mscat = astrostats.biweight_location(MFRAC,6.0),astrostats.biweight_midvariance(MFRAC,9.0)
			vbias,vscat = astrostats.biweight_location(VFRAC,6.0),astrostats.biweight_midvariance(VFRAC,9.0)
			return MFRAC,mbias,mscat,VFRAC,vbias,vscat
		else:
			if self.ss:
				# Create vertically averaged (by halo averaged) arrays, with line_num elements
				# biweightLocation takes only arrays with 4 or more elements
				HORZ_MFRAC,HORZ_VFRAC = [],[]
				VERT_MFRAC,VERT_VFRAC = [],[]
				for a in range(self.line_num):
					if len(ma.compressed(MFRAC[:,a])) > 4:
						VERT_MFRAC.append( astrostats.biweight_location( ma.compressed( MFRAC[:,a] ), 6.0 ) )
						VERT_VFRAC.append( astrostats.biweight_location( ma.compressed( VFRAC[:,a] ), 6.0 ) )
					else:
						VERT_MFRAC.append( np.median( ma.compressed( MFRAC[:,a] ) ) )
						VERT_VFRAC.append( np.median( ma.compressed( VFRAC[:,a] ) ) )
				VERT_MFRAC,VERT_VFRAC = np.array(VERT_MFRAC),np.array(VERT_VFRAC)
				# Create horizontally averaged (by line of sight) arrays, with halo_num elements
				for a in self.halo_range:
					if len(ma.compressed(MFRAC[a])) > 4:
						HORZ_MFRAC.append( astrostats.biweight_location( ma.compressed( MFRAC[a] ), 6.0 ) )
						HORZ_VFRAC.append( astrostats.biweight_location( ma.compressed( VFRAC[a] ), 6.0 ) )
					else:
						HORZ_MFRAC.append( np.median( ma.compressed( MFRAC[a] ) ) )
						HORZ_VFRAC.append( np.median( ma.compressed( VFRAC[a] ) ) )
				HORZ_MFRAC,HORZ_VFRAC = np.array(HORZ_MFRAC),np.array(HORZ_VFRAC)
				# Bias and Scatter Calculations
				mbias,mscat = astrostats.biweight_location(VERT_MFRAC,6.0),astrostats.biweight_midvariance(VERT_MFRAC,9.0)
				vbias,vscat = astrostats.biweight_location(VERT_VFRAC,6.0),astrostats.biweight_midvariance(VERT_VFRAC,9.0)
			else:
				# Bin stack LOS systems need only one average
				mbias,mscat = astrostats.biweight_location(MFRAC,6.0),astrostats.biweight_midvariance(MFRAC,9.0)
				vbias,vscat = astrostats.biweight_location(VFRAC,6.0),astrostats.biweight_midvariance(VFRAC,9.0)

			return MFRAC,mbias,mscat,VFRAC,vbias,vscat
Exemple #4
0
    def richness_est(self,
                     ra,
                     dec,
                     z,
                     pz,
                     gmags,
                     rmags,
                     imags,
                     abs_rmags,
                     haloid,
                     clus_ra,
                     clus_dec,
                     clus_z,
                     clus_rvir=None,
                     gr_slope=None,
                     gr_width=None,
                     gr_inter=None,
                     shiftgap=True,
                     use_specs=False,
                     use_bcg=False,
                     fit_rs=False,
                     spec_list=None,
                     fixed_aperture=False,
                     fixed_vdisp=False,
                     plot_gr=False,
                     plot_sky=False,
                     plot_phase=False,
                     find_pairs=True):
        ''' Richness estimator, magnitudes should be apparent mags except for abs_rmag
			z : spectroscopic redshift
			pz : photometric redshift
			gr_slope : if fed color-mag (g-r vs. r) slope of fit to red sequence
			gr_width : if fed color-mag (g-r vs. r) width of fit to red sequence
			gr_inter : if red color-mag (g-r vs. r) intercept of fit to red sequence
			spec_list : indexing array specifying gals w/ spectroscopic redshifts, preferably fed as boolean numpy array
			fixed_aperture : clus_rvir = 1 Mpc
			fixed_vdisp : clus_vdisp = 1000 km/s (members < clus_vdisp*2)
			use_specs : includes all spectro members in richness regardless of their color, subject to counting twice
			use_bcg : use bcg RS_color as cluster RS_color
			fit_rs : fit a line to the member galaxy RS relationship, as of now this does not work and we take a flat RS relationship
			plot_gr : plot r vs g-r color diagram with RS fits
			plot_sky : plot RA and DEC of gals with signal and background annuli
			plot_phase : plot rdata and vdata phase space
			find_pairs : run c4 cluster pair identifier on phase spaces
			- If at least one quarter of the outer annuli doesn't have any galaxies (perhaps because it has run over the observation edge),
				it will return -99 as a richness
		'''
        # Put Into Class Namespace
        keys = [
            'ra', 'dec', 'z', 'pz', 'gmags', 'rmags', 'imags', 'clus_ra',
            'clus_dec', 'clus_z', 'clus_rvir', 'vel_disp', 'color_data',
            'color_cut', 'RS_color', 'RS_sigma', 'clus_color_cut', 'signal',
            'background', 'richness', 'mems', 'phot', 'spec', 'spec_mems',
            'deg_per_rvir', 'SA_outer', 'SA_inner', 'outer_red_dense',
            'inner_background', 'Nphot_inner', 'Nphot_outer', 'red_inner',
            'red_outer', 'all', 'outer_edge', 'inner_edge', 'shift_cut', 'set',
            'pair', 'Nspec', 'gr_slope', 'gr_inter', 'gr_width', 'obs_tot',
            'obs_back_scaled'
        ]
        self.__dict__.update(ez.create(keys, locals()))

        # Take rough virial radius measurement, this method is BAD...
        if clus_rvir == None:
            clus_rvir = np.exp(-1.86) * len(
                np.where((rmags < -19.55) & (rdata < 1.0)
                         & (np.abs(vdata) < 3500))[0])**0.51
            self.clus_rvir = clus_rvir

        # Set clus_rvir = 1.0 Mpc if fixed aperture == True
        if fixed_aperture == True:
            clus_rvir = 1.0

        # Magnitue Cut at 19.1 Apparent R Mag, then at -19 Absolute R Mag and sort by them
        bright = np.where(rmags < 19.1)[0]
        data = np.vstack([ra, dec, z, pz, gmags, rmags, imags, abs_rmags])
        data = data.T[bright].T
        ra, dec, z, pz, gmags, rmags, imags, abs_rmags = data

        bright = np.where(abs_rmags < -19.5)[0]
        data = np.vstack([ra, dec, z, pz, gmags, rmags, imags, abs_rmags])
        data = data.T[bright].T
        ra, dec, z, pz, gmags, rmags, imags, abs_rmags = data

        sorts = np.argsort(abs_rmags)
        data = np.vstack([ra, dec, z, pz, gmags, rmags, imags, abs_rmags])
        data = data.T[sorts].T
        ra, dec, z, pz, gmags, rmags, imags, abs_rmags = data
        self.__dict__.update(ez.create(keys, locals()))

        # Separate Into Spectro Z and Photo Z DataSets
        # Define the Spectro Z catalogue
        if spec_list == None:
            spec_cut = np.abs(z) > 1e-4
        else:
            if type(spec_list) == list: spec_list = np.array(spec_list)
            if spec_list.dtype != 'bool':
                spec_cut = np.array([False] * len(ra))
                spec_cut[spec_list] = True
        all = {
            'ra': ra,
            'dec': dec,
            'z': z,
            'pz': pz,
            'gmags': gmags,
            'rmags': rmags,
            'imags': imags,
            'abs_rmags': abs_rmags
        }
        spec = {
            'ra': ra[spec_cut],
            'dec': dec[spec_cut],
            'z': z[spec_cut],
            'pz': pz[spec_cut],
            'gmags': gmags[spec_cut],
            'rmags': rmags[spec_cut],
            'imags': imags[spec_cut],
            'abs_rmags': abs_rmags[spec_cut]
        }
        phot = {
            'ra': ra[~spec_cut],
            'dec': dec[~spec_cut],
            'z': z[~spec_cut],
            'pz': pz[~spec_cut],
            'gmags': gmags[~spec_cut],
            'rmags': rmags[~spec_cut],
            'imags': imags[~spec_cut],
            'abs_rmags': abs_rmags[~spec_cut]
        }

        # Project Spectra into radius and velocity
        ang_d, lum_d = C.zdistance(clus_z, self.H0)
        angles = C.findangle(spec['ra'], spec['dec'], clus_ra, clus_dec)
        rdata = angles * ang_d
        vdata = self.c * (spec['z'] - clus_z) / (1 + clus_z)
        spec.update({'rdata': rdata, 'vdata': vdata})
        self.__dict__.update(ez.create(keys, locals()))

        # Take Hard Phasespace Limits
        limit = np.where((rdata < 5) & (np.abs(vdata) < 5000))[0]
        clus_data = np.vstack([
            rdata, vdata, spec['ra'], spec['dec'], spec['z'], spec['pz'],
            spec['gmags'], spec['rmags'], spec['imags'], spec['abs_rmags']
        ])
        clus_data = clus_data.T[limit].T
        rdata, vdata, spec['ra'], spec['dec'], spec['z'], spec['pz'], spec[
            'gmags'], spec['rmags'], spec['imags'], spec[
                'abs_rmags'] = clus_data
        spec.update({'rdata': rdata, 'vdata': vdata})
        self.__dict__.update(ez.create(keys, locals()))

        # Shiftgapper for Interlopers
        if shiftgap == True:
            len_before = np.where((rdata < clus_rvir * 1.5)
                                  & (np.abs(vdata) < 4000))[0].size
            clus_data = np.vstack([
                rdata, vdata, spec['ra'], spec['dec'], spec['z'], spec['pz'],
                spec['gmags'], spec['rmags'], spec['imags'], spec['abs_rmags']
            ])
            clus_data = C.shiftgapper(clus_data.T).T
            sorts = np.argsort(clus_data[-1])
            clus_data = clus_data.T[sorts].T
            rdata, vdata, spec['ra'], spec['dec'], spec['z'], spec['pz'], spec[
                'gmags'], spec['rmags'], spec['imags'], spec[
                    'abs_rmags'] = clus_data
            shift_cut = len_before - np.where((rdata < clus_rvir)
                                              & (np.abs(vdata) < 4000))[0].size

        # Measure Velocity Dispersion of all galaxies within 1 * r_vir and np.abs(vdata) < 4000 km/s
        spec.update({'rdata': rdata, 'vdata': vdata})
        self.__dict__.update(ez.create(keys, locals()))
        vel_disp = astats.biweight_midvariance(
            vdata[np.where((rdata < clus_rvir * 1) & (np.abs(vdata) < 4000))])
        if fixed_vdisp == True: vel_disp = 1000

        # Run Cluster Pair Finder
        if find_pairs == True:
            pair, d1_chi, d2_chi, d3_chi, s_chi, double1, double2, double3, single, v_range, bins = pair_find(
                rdata, vdata)

        # Calculate Nspec, Nspec = # of galaxies within RVIR
        try:
            Nspec = np.where(rdata < clus_rvir)[0].size
        except:
            Nspec = 0
        self.__dict__.update(ez.create(keys, locals()))

        # Get Members from Specta, get their red sequence color
        mems = np.where((spec['rdata'] < clus_rvir)
                        & (np.abs(spec['vdata']) < 2 * vel_disp))[0]
        color_data = spec['gmags'] - spec['rmags']
        color_cut = np.where((color_data[mems] < 1.2)
                             & (color_data[mems] > 0.65))[0]
        RS_color = astats.biweight_location(color_data[mems][color_cut])
        RS_shift = color_data[mems][color_cut] - RS_color
        RS_sigma = astats.biweight_midvariance(
            RS_shift[np.where(np.abs(RS_shift) < .15)])

        if fit_rs == True:
            clf = linear_model.LinearRegression()
            set = np.where(
                np.abs(color_data[mems] - RS_color) < 3 * RS_sigma)[0]
            clf.fit(spec['rmags'][mems][set].reshape(set.size, 1),
                    color_data[mems][set])

        if use_bcg == True:
            bright = np.argsort(all['abs_rmags'][mems])[0]
            RS_color = color_data[mems][bright]
            RS_shift = color_data[mems][color_cut] - RS_color
            RS_sigma = astats.biweight_midvariance(
                RS_shift[np.where(np.abs(RS_shift) < .15)])

        # spec_mems is # of members that are within 2 sigma of cluster color
        clus_color_cut = np.where(
            np.abs(color_data[mems] - RS_color) < RS_sigma * 2)[0]
        spec_mems = len(clus_color_cut)
        self.__dict__.update(ez.create(keys, locals()))

        # If fed gr fit
        if gr_slope != None:

            def RS_color(r_mag):
                return r_mag * gr_slope + gr_inter

            RS_sigma = gr_width / 2
            clus_color_cut = np.where(
                np.abs(color_data[mems] -
                       RS_color(spec['rmags'])[mems]) < RS_sigma * 2)[0]
            spec_mems = len(clus_color_cut)
            self.__dict__.update(ez.create(keys, locals()))

        # Get Rdata from PhotoZ & SpecZ Data Set
        angles = C.findangle(all['ra'], all['dec'], clus_ra, clus_dec)
        all['rdata'] = angles * ang_d

        # Get deg per RVIR proper
        deg_per_rvir = R.Cosmo.arcsec_per_kpc_proper(
            clus_z).value * 1e3 * clus_rvir / 3600

        # Get Area of Virial Circle out to 1 rvir, and Area of outer annuli, which is annuli from 4rvir < R < 6rvir, or dependent on rvir
        if clus_rvir < 2.5:
            outer_edge = 6.0
            inner_edge = 4.0
        elif clus_rvir >= 2.5 and clus_rvir < 3:
            outer_edge = 5.0
            inner_edge = 3.5
        else:
            outer_edge = 3.0
            inner_edge = 2.0
        SA_inner = np.pi * deg_per_rvir**2
        SA_outer = np.pi * ((outer_edge * deg_per_rvir)**2 -
                            (inner_edge * deg_per_rvir)**2)

        # Get Number of Cluster Color Galaxies from Photo Data Set in Inner Circle and Outer Annuli
        RS_color_sig = 2.0
        if gr_slope == None:
            red_inner = np.where(((all['gmags'] - all['rmags']) < RS_color +
                                  RS_color_sig * RS_sigma)
                                 & ((all['gmags'] - all['rmags']) > RS_color -
                                    RS_color_sig * RS_sigma)
                                 & (all['rdata'] < clus_rvir))[0]
            red_outer = np.where((
                (all['gmags'] - all['rmags']) < RS_color + 1.5 * RS_sigma) & (
                    (all['gmags'] - all['rmags']) > RS_color - 1.5 * RS_sigma)
                                 & (all['rdata'] < outer_edge * clus_rvir)
                                 & (all['rdata'] > inner_edge * clus_rvir))[0]
        else:
            red_inner = np.where((
                (all['gmags'] - all['rmags']) < RS_color(all['rmags']) +
                RS_color_sig * RS_sigma) & (
                    (all['gmags'] - all['rmags']) > RS_color(all['rmags']) -
                    RS_color_sig * RS_sigma) & (all['rdata'] < clus_rvir))[0]
            red_outer = np.where((
                (all['gmags'] -
                 all['rmags']) < RS_color(all['rmags']) + 1.5 * RS_sigma) & (
                     (all['gmags'] - all['rmags']) > RS_color(all['rmags']) -
                     1.5 * RS_sigma) & (all['rdata'] < outer_edge * clus_rvir)
                                 & (all['rdata'] > inner_edge * clus_rvir))[0]

        Nphot_inner = len(red_inner)
        Nphot_outer = len(red_outer)

        self.__dict__.update(ez.create(keys, locals()))

        # Get Solid Angle Density of Outer Red Galaxies
        outer_red_dense = Nphot_outer / SA_outer
        inner_background = int(np.ceil(outer_red_dense * SA_inner))

        # Define obs_tot and obs_back_scaled, where obs_back_scaled is obs_back already scaled to inner aperture (aka. inner_background)
        obs_tot = np.copy(Nphot_inner)
        obs_back_scaled = np.copy(inner_background)

        # If inner_background is less than Nphot_inner, then Nphot_inner -= inner_background, otherwise Nphot_inner = 0
        if inner_background < Nphot_inner:
            Nphot_inner -= inner_background
        else:
            Nphot_inner = 0

        # Richness = spec_mems + Nphot_inner or just Nphot_inner
        if use_specs == True:
            richness = spec_mems + Nphot_inner
        else:
            richness = Nphot_inner
        self.__dict__.update(ez.create(keys, locals()))

        # Plot
        if plot_gr == True:
            fig, ax = mp.subplots()
            ax.plot(rmags, gmags - rmags, 'ko', alpha=.8)
            ax.plot(spec['rmags'][mems], color_data[mems], 'co')
            if gr_slope == None:
                ax.axhline(RS_color, color='r')
                ax.axhline(RS_color + RS_sigma, color='b')
                ax.axhline(RS_color - RS_sigma, color='b')
            else:
                xdata = np.arange(spec['rmags'][mems].min(),
                                  spec['rmags'][mems].max(), .1)
                ax.plot(xdata, RS_color(xdata), color='r')
                ax.plot(xdata, RS_color(xdata) + RS_sigma, color='b')
                ax.plot(xdata, RS_color(xdata) - RS_sigma, color='b')
            ax.set_xlim(13, 19)
            ax.set_ylim(0, 1.3)
            ax.set_xlabel('Apparent R Mag', fontsize=16)
            ax.set_ylabel('App G Mag - App R Mag', fontsize=16)
            ax.set_title('Color-Mag Diagram, Cluster ' + str(haloid))
            fig.savefig('colormag_' + str(haloid) + '.png',
                        bbox_inches='tight')
            mp.close(fig)

        if plot_sky == True:
            fig, ax = mp.subplots()
            ax.plot(all['ra'], all['dec'], 'ko')
            ax.plot(all['ra'][red_inner], all['dec'][red_inner], 'ro')
            ax.plot(all['ra'][red_outer], all['dec'][red_outer], 'yo')
            ax.plot(clus_ra, clus_dec, 'co', markersize=9)
            ax.set_xlabel('RA', fontsize=15)
            ax.set_ylabel('Dec.', fontsize=15)
            ax.set_title('Richness Annuli for Halo ' + str(haloid))
            fig.savefig('skyplot_' + str(haloid) + '.png', bbox_inches='tight')
            mp.close(fig)

        if plot_phase == True:
            fig, ax = mp.subplots()
            ax.plot(spec['rdata'], spec['vdata'], 'ko')
            ax.plot(spec['rdata'][mems], spec['vdata'][mems], 'co')
            bcg = np.where(
                spec['abs_rmags'] == spec['abs_rmags'][mems].min())[0][0]
            ax.plot(spec['rdata'][bcg], spec['vdata'][bcg], 'ro')
            ax.set_xlim(0, 5)
            ax.set_ylim(-5000, 5000)
            ax.set_xlabel('Radius (Mpc)', fontsize=15)
            ax.set_ylabel('Velocity (km/s)', fontsize=15)
            ax.set_title('phasespace haloid ' + str(haloid))
            fig.savefig('phasespace_' + str(haloid) + '.png',
                        bbox_inches='tight')
            mp.close(fig)

# Check to make sure galaxies exist (somewhat) uniformely in outer annuli (aka. cluster isn't on edge of observation strip)
# First, project all galaxies into polar coordinates centered on cluster center
        x = (all['ra'] - clus_ra) / np.cos(
            clus_dec * np.pi /
            180)  # x coordinate in arcsec (of dec) centered on cluster center
        y = (all['dec'] - clus_dec)
        all['radius'] = np.sqrt(x**2 +
                                y**2) / deg_per_rvir  #radius scaled by RVIR
        all['theta'] = np.arctan(y / x)
        # Add corrections to arctan function
        all['theta'][np.where((x < 0) & (y > 0))] += np.pi  # Quadrant II
        all['theta'][np.where((x < 0) & (y < 0))] += np.pi  # Quadrant III
        all['theta'][np.where((x > 0) & (y < 0))] += 2 * np.pi  # Quadrant IV
        # Then break outer annuli into 4 sections and check if at least 1 galaxy exists in each section
        sizes1 = np.array([
            np.where((np.abs(all['theta'] - i) <= np.pi / 2)
                     & (all['radius'] > inner_edge)
                     & (all['radius'] < 15))[0].size
            for i in np.linspace(0, 2 * np.pi, 4)
        ])
        # Do it again but shift theta by np.pi/8 this time
        sizes2 = np.array([
            np.where((np.abs(all['theta'] - i) <= np.pi / 2)
                     & (all['radius'] > inner_edge)
                     & (all['radius'] < 15))[0].size
            for i in np.linspace(np.pi / 8, 17 * np.pi / 8, 4)
        ])
        if 0 in sizes1 or 0 in sizes2:
            p = False
            if p == True:
                mp.plot(all['radius'], all['theta'], 'ko')
                mp.xlabel('radius')
                mp.ylabel('theta')
                mp.savefig('rth_' + str(haloid) + '.png')
                mp.close()
            print 'sizes1=', sizes1
            print 'sizes2=', sizes2
            return -99

        return richness