@@ -63,7 +63,8 @@ class MockSurvey(object):
63
63
"""
64
64
def __init__ (self , minMass , areaDeg2 , zMin , zMax , H0 , Om0 , Ob0 , sigma8 , ns , zStep = 0.01 ,
65
65
enableDrawSample = False , delta = 500 , rhoType = 'critical' ,
66
- transferFunction = 'boltzmann_camb' , massFunction = 'Tinker08' ):
66
+ transferFunction = 'boltzmann_camb' , massFunction = 'Tinker08' ,
67
+ c_m_relation = 'Bhattacharya13' ):
67
68
"""Create a MockSurvey object, for performing calculations of cluster counts or generating mock
68
69
catalogs. The Tinker et al. (2008) halo mass function is used (hardcoded at present, but in
69
70
principle this can easily be swapped for any halo mass function supported by CCL).
@@ -89,7 +90,9 @@ def __init__(self, minMass, areaDeg2, zMin, zMax, H0, Om0, Ob0, sigma8, ns, zSte
89
90
'boltzmann_camb').
90
91
massFunction (:obj:`str`): Name of the mass function to use, currently either 'Tinker08' or
91
92
'Tinker10'. Mass function calculations are done by CCL.
92
-
93
+ c_m_relation ('obj':`str`): Name of the concentration -- mass relation to assume, as understood by
94
+ CCL (this may be used internally for conversion between mass definitions, as needed).
95
+
93
96
"""
94
97
95
98
if areaDeg2 == 0 :
@@ -104,11 +107,8 @@ def __init__(self, minMass, areaDeg2, zMin, zMax, H0, Om0, Ob0, sigma8, ns, zSte
104
107
105
108
self .delta = delta
106
109
self .rhoType = rhoType
107
- if self .delta == 200 :
108
- c_m_relation = 'Bhattacharya13'
109
- else :
110
- c_m_relation = None
111
- self .mdef = ccl .halos .MassDef (self .delta , self .rhoType , c_m_relation = c_m_relation )
110
+ self .c_m_relation = c_m_relation
111
+ self .mdef = ccl .halos .MassDef (self .delta , self .rhoType )
112
112
self .transferFunction = transferFunction
113
113
self .massFuncName = massFunction
114
114
@@ -129,7 +129,10 @@ def __init__(self, minMass, areaDeg2, zMin, zMax, H0, Om0, Ob0, sigma8, ns, zSte
129
129
self .log10M .max ()+ (self .log10M [1 ]- self .log10M [0 ])/ 2 , len (self .log10M )+ 1 )
130
130
131
131
# Below is needed for Q calc when not using M500c definition (for now at least)
132
- self ._M500cDef = ccl .halos .MassDef (500 , "critical" )
132
+ if self .delta != 500 and self .rhoType != 'critical' :
133
+ self ._M500cDef = ccl .halos .MassDef (500 , "critical" )
134
+ self ._transToM500c = ccl .halos .mass_translator (mass_in = self .mdef , mass_out = self ._M500cDef ,
135
+ concentration = self .c_m_relation )
133
136
134
137
self .enableDrawSample = enableDrawSample
135
138
self .update (H0 , Om0 , Ob0 , sigma8 , ns )
@@ -161,18 +164,16 @@ def _get_new_cosmo(self, H0, Om0, Ob0, sigma8, ns):
161
164
self .Ob0 = Ob0
162
165
self .sigma8 = sigma8
163
166
self .ns = ns
164
- self .cosmoModel = ccl .Cosmology (Omega_c = Om0 - Ob0 ,
165
- Omega_b = Ob0 ,
166
- h = 0.01 * H0 ,
167
- sigma8 = sigma8 ,
168
- n_s = ns ,
169
- transfer_function = self .transferFunction )
167
+ self .cosmoModel = ccl .Cosmology (Omega_c = Om0 - Ob0 ,
168
+ Omega_b = Ob0 ,
169
+ h = 0.01 * H0 ,
170
+ sigma8 = sigma8 ,
171
+ n_s = ns ,
172
+ transfer_function = self .transferFunction )
170
173
if self .massFuncName == 'Tinker10' :
171
- self .mfunc = ccl .halos .MassFuncTinker10 (self .cosmoModel ,
172
- self .mdef )
174
+ self .mfunc = ccl .halos .MassFuncTinker10 (mass_def = self .mdef )
173
175
elif self .massFuncName == 'Tinker08' :
174
- self .mfunc = ccl .halos .MassFuncTinker08 (self .cosmoModel ,
175
- self .mdef )
176
+ self .mfunc = ccl .halos .MassFuncTinker08 (mass_def = self .mdef )
176
177
177
178
178
179
def update (self , H0 , Om0 , Ob0 , sigma8 , ns ):
@@ -205,11 +206,8 @@ def update(self, H0, Om0, Ob0, sigma8, ns):
205
206
interpLim_minLog10M500c = self .log10M .min ()
206
207
interpLim_maxLog10M500c = self .log10M .max ()
207
208
else :
208
- interpLim_minLog10M500c = np .log10 (self .mdef .translate_mass (self .cosmoModel , self .M .min (),
209
- self .a [k ], self ._M500cDef ))
210
- interpLim_maxLog10M500c = np .log10 (self .mdef .translate_mass (self .cosmoModel , self .M .max (),
211
- self .a [k ], self ._M500cDef ))
212
-
209
+ interpLim_minLog10M500c = np .log10 (self ._transToM500c (self .cosmoModel , self .M .min (), self .a [k ]))
210
+ interpLim_maxLog10M500c = np .log10 (self ._transToM500c (self .cosmoModel , self .M .max (), self .a [k ]))
213
211
zk = self .z [k ]
214
212
interpPoints = 100
215
213
fitM500s = np .power (10 , np .linspace (interpLim_minLog10M500c , interpLim_maxLog10M500c , interpPoints ))
@@ -251,8 +249,7 @@ def _cumulativeNumberDensity(self, z):
251
249
"""
252
250
253
251
h = self .cosmoModel ['h' ]
254
- dndlnM = self .mfunc .get_mass_function (self .cosmoModel ,
255
- self .M , 1 / (1 + z )) / np .log (10 ) #/ h**3
252
+ dndlnM = self .mfunc (self .cosmoModel , self .M , 1 / (1 + z )) / np .log (10 )
256
253
dndM = dndlnM / self .M
257
254
ngtm = integrate .cumtrapz (dndlnM [::- 1 ], np .log (self .M ), initial = 0 )[::- 1 ]
258
255
@@ -293,8 +290,7 @@ def _doClusterCount(self):
293
290
zShellMin = zRange [i ]
294
291
zShellMax = zRange [i + 1 ]
295
292
zShellMid = (zShellMax + zShellMin )/ 2.
296
- dndlnM = self .mfunc .get_mass_function (self .cosmoModel , self .M ,
297
- 1. / (1 + zShellMid )) * norm_mfunc
293
+ dndlnM = self .mfunc (self .cosmoModel , self .M , 1. / (1 + zShellMid )) * norm_mfunc
298
294
dndM = dndlnM / self .M
299
295
n = dndM * np .gradient (self .M )
300
296
numberDensity .append (n )
@@ -362,7 +358,7 @@ def drawSample(self, y0Noise, scalingRelationDict, QFit = None, wcs = None, phot
362
358
tileName = None , SNRLimit = None , makeNames = False , z = None , numDraws = None ,\
363
359
areaDeg2 = None , applySNRCut = False , applyPoissonScatter = True ,\
364
360
applyIntrinsicScatter = True , applyNoiseScatter = True ,\
365
- applyRelativisticCorrection = True , verbose = False ):
361
+ applyRelativisticCorrection = True , verbose = False , biasModel = None ):
366
362
"""Draw a cluster sample from the mass function, generating mock y0~ values (called `fixed_y_c` in
367
363
Nemo catalogs) by applying the given scaling relation parameters, and then (optionally) applying
368
364
a survey selection function.
@@ -555,9 +551,7 @@ def drawSample(self, y0Noise, scalingRelationDict, QFit = None, wcs = None, phot
555
551
if self .delta == 500 and self .rhoType == "critical" :
556
552
log10M500cs [mask ]= log10Ms [mask ]
557
553
else :
558
- log10M500cs [mask ]= np .log10 (self .mdef .translate_mass (self .cosmoModel , np .power (10 , log10Ms [mask ]),
559
- 1 / (1 + zk ), self ._M500cDef ))
560
-
554
+ log10M500cs [mask ]= np .log10 (self ._transToM500c (self .cosmoModel , np .power (10 , log10Ms [mask ]), 1 / (1 + zk )))
561
555
theta500s = interpolate .splev (log10M500cs [mask ], self .theta500Splines [k ], ext = 3 )
562
556
if QFit is not None :
563
557
Qs [mask ]= QFit .getQ (theta500s , z = zk , tileName = tileName )
@@ -609,8 +603,15 @@ def drawSample(self, y0Noise, scalingRelationDict, QFit = None, wcs = None, phot
609
603
tab .add_column (atpy .Column (true_y0s / 1e-4 , 'true_fixed_y_c' ))
610
604
tab .add_column (atpy .Column (measured_y0s / 1e-4 , 'fixed_y_c' ))
611
605
tab .add_column (atpy .Column (y0Noise / 1e-4 , 'fixed_err_y_c' ))
606
+ tab ['true_fixed_SNR' ]= tab ['true_fixed_y_c' ]/ tab ['fixed_err_y_c' ] # True truth, but pre-intrinsic and measurement scatter
607
+ # tab['true_fixed_SNR']=(scattered_y0s/1e-4)/tab['fixed_err_y_c'] # With intrinsic scatter, no measurement scatter
608
+ # tab['true_fixed_SNR']=tab['fixed_y_c']/tab['fixed_err_y_c'] # Like forced photometry case on a real map at true location
609
+ # Apply optimization bias first, then it'll feed through to SNR automatically
610
+ if biasModel is not None :
611
+ corrFactors = biasModel ['func' ](tab ['true_fixed_SNR' ], biasModel ['params' ][0 ], biasModel ['params' ][1 ], biasModel ['params' ][2 ])
612
+ tab ['fixed_y_c' ]= tab ['fixed_y_c' ]* corrFactors
613
+ tab ['fixed_SNR' ]= tab ['fixed_y_c' ]/ tab ['fixed_err_y_c' ]
612
614
613
- tab .add_column (atpy .Column (measured_y0s / y0Noise , 'fixed_SNR' ))
614
615
tab .add_column (atpy .Column (zs , 'redshift' ))
615
616
tab .add_column (atpy .Column (zErrs , 'redshiftErr' ))
616
617
if photFilterLabel is not None and tileName is not None :
@@ -619,7 +620,7 @@ def drawSample(self, y0Noise, scalingRelationDict, QFit = None, wcs = None, phot
619
620
620
621
# Apply selection?
621
622
if applySNRCut == True :
622
- selMask = measured_y0s > y0Noise * SNRLimit
623
+ selMask = tab [ 'fixed_SNR' ] > tab [ 'fixed_err_y_c' ] * SNRLimit
623
624
tab = tab [selMask ]
624
625
t1 = time .time ()
625
626
0 commit comments