Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1"""Instance Catalog""" 

2from __future__ import print_function 

3from builtins import range 

4import numpy as np 

5from lsst.sims.utils import SpecMap, defaultSpecMap 

6from lsst.sims.catalogs.definitions import InstanceCatalog 

7from lsst.sims.catalogs.decorators import compound, cached 

8from lsst.sims.utils import arcsecFromRadians, _observedFromICRS, altAzPaFromRaDec 

9from lsst.sims.catUtils.mixins import (EBVmixin, PhoSimAstrometryStars, 

10 PhoSimAstrometryGalaxies, 

11 PhoSimAstrometrySSM, 

12 FrozenSNCat) 

13 

14__all__ = ["write_phoSim_header", "PhosimInputBase", 

15 "PhoSimCatalogPoint", "PhoSimCatalogZPoint", "PhoSimCatalogSN", 

16 "PhoSimCatalogSersic2D", "PhoSimCatalogSSM", "PhoSimSpecMap", 

17 "DefaultPhoSimHeaderMap", 'DefaultPhoSimInstanceCatalogCols'] 

18 

19 

20PhoSimSpecMap = SpecMap(fileDict=defaultSpecMap.fileDict, 

21 dirDict={'(^lte)': 'starSED/phoSimMLT'}) 

22 

23 

24# This is a dict of transformations mapping from data in OpSim to header 

25# values expected by PhoSim. The dict is keyed on the names of PhoSim 

26# parameters. The values of the dict are either straight values, in which 

27# case those values are written to the PhoSim InstanceCatalog header, or tuples 

28# containing the name of the OpSim column which should be use to calculate 

29# the PhoSim parameter and any transformation needed to go from OpSim 

30# units to PhoSim units. 

31# 

32# PhoSim header parameters are documented here 

33# https://bitbucket.org/phosim/phosim_release/wiki/Instance%20Catalog 

34# 

35# OpSim columns are documented here 

36# https://www.lsst.org/scientists/simulations/opsim/summary-table-column-descriptions-v335 

37 

38DefaultPhoSimHeaderMap = {'rottelpos': ('rotTelPos', np.degrees), 38 ↛ exitline 38 didn't jump to the function exit

39 'obshistid': ('obsHistID', None), 

40 'moonra': ('moonRA', np.degrees), 

41 'moondec': ('moonDec', np.degrees), 

42 'moonphase': ('moonPhase', None), 

43 'moonalt': ('moonAlt', np.degrees), 

44 'dist2moon': ('dist2Moon', np.degrees), 

45 'sunalt': ('sunAlt', np.degrees), 

46 'seeing': ('rawSeeing', None), 

47 'vistime': ('visitExpTime', lambda x: x + 3.0), 

48 'nsnap': 2, 

49 'seed': ('obsHistID', None), 

50 'opsim_version': 3} 

51 

52# This variable contains all of the columns in a phosim Instance Catalog 

53# Can be used to reconstruct the information encoded in a phosim instance 

54# Catalog 

55DefaultPhoSimInstanceCatalogCols = ('object', 'uniqueID', 'RA', 'DEC'\ 

56 , 'MAG_NORM', 'SED_NAME', 'REDSHIFT'\ 

57 , 'GAMMA1', 'GAMMA2', 'MU', 'DELTA_RA'\ 

58 , 'DELTA_DEC', 'SOURCE_TYPE'\ 

59 , 'source_pars', 'DUST_REST_NAME'\ 

60 , 'dust_pars_1a', 'dust_pars_1b'\ 

61 , 'DUST_LAB_NAME', 'dust_pars_2a'\ 

62 , 'dust_pars_2b') 

63 

64def evaluate_phosim_header(param, phosim_header_map, obs): 

65 """ 

66 Find the value of a PhoSim header parameter given a PhoSimHeaderMap, 

67 and an ObservationMetaData 

68 

69 @param [in] param is the name of the header parameter desired. 

70 

71 @param [in] phosim_header_map is a phoSimHeaderMap dict 

72 

73 @param [in] obs is an ObservationMetaData 

74 

75 @param [out] returns the value of the PhoSim header parameter 

76 """ 

77 

78 if param not in phosim_header_map: 

79 return None 

80 

81 if isinstance(phosim_header_map[param], tuple): 

82 transform = phosim_header_map[param] 

83 if obs.OpsimMetaData is None: 

84 return None 

85 

86 if transform[0] not in obs.OpsimMetaData: 

87 return None 

88 

89 if transform[1] is None: 

90 return obs.OpsimMetaData[transform[0]] 

91 

92 return transform[1](obs.OpsimMetaData[transform[0]]) 

93 

94 return phosim_header_map[param] 

95 

96 

97def write_phoSim_header(obs, file_handle, phosim_header_map): 

98 """ 

99 Write the data contained in an ObservationMetaData as a header in a 

100 PhoSim InstanceCatalog. 

101 

102 obs is the ObservationMetaData 

103 

104 file_handle points to the catalog being written. 

105 """ 

106 

107 if phosim_header_map is None: 

108 raw_opsim_contents = "" 

109 

110 if obs.OpsimMetaData is not None: 

111 sorted_opsim_metadata = list(obs.OpsimMetaData.keys()) 

112 sorted_opsim_metadata.sort() 

113 for tag in sorted_opsim_metadata: 

114 raw_opsim_contents += "%s\n" % tag 

115 

116 raise RuntimeError("You have tried to write a PhoSim InstanceCatalog without specifying " 

117 "a phoSimHeaderMap in your call to write_catalog().\n" 

118 "\n" 

119 "A phoSimHeaderMap is a dict that maps between columns stored in the " 

120 "OpSim database from which an ObservationMetaData was created and " 

121 "header parameters expected by PhoSim. The dict is keyed on the names of PhoSim " 

122 "parameters. The values of the dict are either straight values, in which " 

123 "case those values are written to the PhoSim InstanceCatalog header, or tuples " 

124 "containing the name of the OpSim column which should be use to calculate " 

125 "the PhoSim parameter and any transformation needed to go from OpSim " 

126 "units to PhoSim units (the transform is 'None' if no transform is needed).\n" 

127 "\n" 

128 "To add a phoSimHeaderMap, simple assign it to the 'phoSimHeaderMap' " 

129 "member variable of your catalog with (for instance):\n" 

130 "\n" 

131 "myCat.phoSimHeaderMap = my_phosim_header_map\n" 

132 "\n" 

133 "Before calling write_catalog()\n" 

134 "\n" 

135 "The header parameters expected by PhoSim can be found at\n" 

136 "\n" 

137 "https://bitbucket.org/phosim/phosim_release/wiki/Instance%20Catalog\n" 

138 "\n" 

139 "The contents of the OpSim database's Summary table can be found at\n" 

140 "\n" 

141 "https://www.lsst.org/scientists/simulations/opsim/summary-table-column-descriptions-v335\n" 

142 "\n" 

143 "If you do not wish to use any of these columns, you can just pass in an empty " 

144 "dict. If you want to use a pre-made mapping, use the DefaultPhoSimHeaderMap " 

145 "imported from lsst.sims.catUtils.exampleCatalogDefinitions\n" 

146 "\n" 

147 "Note: do not specify ra, dec, alt, az, mjd, filter, or rotSkyPos in your " 

148 "phoSimHeaderMap. These are handled directly by the ObservationMetaData " 

149 "to ensure self-consistency.\n" 

150 "\n" 

151 "For reference, the OpSim columns you can choose to map (i.e. those contained " 

152 "in your ObservationMetaData) are:\n\n" + 

153 raw_opsim_contents + 

154 "\n(Even if your ObservationMetaData contains no extra OpSim Columns " 

155 "you may wish to consider adding default PhoSim parameters through " 

156 "the phoSimHeaderMap)") 

157 

158 try: 

159 

160 # PhoSim wants the MJD at the middle of the visit (i.e. between the two exposures 

161 # in our two-snap model). OpSim gives the MJD at the start of the visit. 

162 # below we calculate the change in MJD necessary to transform the OpSim value 

163 # into the PhoSim value 

164 vistime = evaluate_phosim_header('vistime', phosim_header_map, obs) 

165 if vistime is not None: 

166 delta_t = 0.5*vistime 

167 else: 

168 delta_t = 16.5 # half of the default 33 seconds 

169 

170 file_handle.write('rightascension %.17f\n' % obs.pointingRA) 

171 file_handle.write('declination %.17f\n' % obs.pointingDec) 

172 file_handle.write('mjd %.17f\n' % (obs.mjd.TAI + delta_t/86400.0)) 

173 alt, az, pa = altAzPaFromRaDec(obs.pointingRA, obs.pointingDec, obs, includeRefraction=False) 

174 file_handle.write('altitude %.17f\n' % alt) 

175 file_handle.write('azimuth %.17f\n' % az) 

176 file_handle.write('filter %d\n' % 

177 {'u': 0, 'g': 1, 'r': 2, 'i': 3, 'z': 4, 'y': 5}[obs.bandpass]) 

178 

179 file_handle.write('rotskypos %.17f\n' % obs.rotSkyPos) 

180 except: 

181 print("\n\n") 

182 print("The ObservationMetaData you tried to write a PhoSim header from") 

183 print("lacks one of the required parameters") 

184 print("(pointingRA, pointingDec, mjd, bandpass, rotSkyPos)\n") 

185 raise 

186 

187 # sort the header map keys so that PhoSim headers generated with the same 

188 # map always have parameters in the same order. 

189 sorted_header_keys = list(phosim_header_map.keys()) 

190 sorted_header_keys.sort() 

191 

192 if (obs.OpsimMetaData is not None and 'opsim_version' in obs.OpsimMetaData and 

193 'opsim_version' in phosim_header_map): 

194 if obs.OpsimMetaData['opsim_version'] != phosim_header_map['opsim_version']: 

195 raise RuntimeError('Your ObservationMetaData was built from ' 

196 + 'a v%d OpSim database, but your phosim_header_map ' % obs.OpsimMetaData['opsim_version'] 

197 + 'is expecting a v%d OpSim database. Your InstanceCatalog ' % phosim_header_map['opsim_version'] 

198 + 'header is not going to make any sense if you proceed, so ' 

199 + 'we are throwing this error.') 

200 

201 for name in sorted_header_keys: 

202 if name == 'opsim_version': 

203 continue 

204 val = evaluate_phosim_header(name, phosim_header_map, obs) 

205 if val is not None: 

206 if isinstance(val, float) or isinstance(val, np.float): 

207 file_handle.write('%s %.7f\n' % (name, val)) 

208 elif isinstance(val, int) or isinstance(val, np.int): 

209 file_handle.write('%s %d\n' % (name, val)) 

210 elif isinstance(val, int): 

211 file_handle.write('%s %ld\n' % (name, val)) 

212 else: 

213 file_handle.write('%s %s\n' % (name, str(val))) 

214 

215 

216class PhosimInputBase(InstanceCatalog): 

217 

218 override_formats = {'raPhoSim': '%.16g', 'decPhoSim': '%.16g'} 

219 

220 filtMap = dict([(c, i) for i, c in enumerate('ugrizy')]) 

221 

222 specFileMap = PhoSimSpecMap 

223 

224 cannot_be_null = ['sedFilepath'] 

225 

226 delimiter = " " 

227 

228 @cached 

229 def get_prefix(self): 

230 chunkiter = range(len(self.column_by_name(self.refIdCol))) 

231 return np.array(['object' for i in chunkiter], dtype=(str, 6)) 

232 

233 @cached 

234 def get_sedFilepath(self): 

235 return np.array([self.specFileMap[k] if k in self.specFileMap else None 

236 for k in self.column_by_name('sedFilename')]) 

237 

238 @cached 

239 def get_spatialmodel(self): 

240 chunkiter = range(len(self._current_chunk)) 

241 return np.array([self.spatialModel for i in chunkiter], dtype=(str, 8)) 

242 

243 @cached 

244 def get_phoSimMagNorm(self): 

245 """ 

246 This getter returns the magnitude normalization expected by PhoSim (the magnitude at 

247 500 nm). 

248 

249 To account for variability, the getter adds the delta_lsst_x column from the Variability 

250 mixin where 'x' is the bandpass defined by self.observation_metadata.bandpass (assuming 

251 that self.observation_metadata.bandpass is not list-like; if it is list-like, then no 

252 variability is added to the magNorm value). 

253 

254 Redshift is currently ignored. That may or may not be appropriate. This requires 

255 further investigation into the behavior of PhoSim. 

256 """ 

257 

258 try: 

259 string_class = basestring 

260 except: 

261 string_class = str 

262 

263 magNorm = self.column_by_name('magNorm') 

264 varName = None 

265 if self.obs_metadata is not None: 

266 if self.obs_metadata.bandpass is not None: 

267 if isinstance(self.obs_metadata.bandpass, string_class): 

268 varName = 'delta_lsst_' + self.obs_metadata.bandpass 

269 

270 if varName is not None and varName in self._all_available_columns: 

271 magNorm_out = magNorm + self.column_by_name(varName) 

272 return magNorm_out 

273 else: 

274 return magNorm 

275 

276 def write_header(self, file_handle): 

277 if not hasattr(self, 'phoSimHeaderMap'): 

278 self.phoSimHeaderMap = None 

279 

280 write_phoSim_header(self.obs_metadata, file_handle, self.phoSimHeaderMap) 

281 

282 

283class PhoSimCatalogPoint(PhosimInputBase, PhoSimAstrometryStars): 

284 

285 catalog_type = 'phoSim_catalog_POINT' 

286 

287 column_outputs = ['prefix', 'uniqueId', 'raPhoSim', 'decPhoSim', 'phoSimMagNorm', 'sedFilepath', 

288 'redshift', 'gamma1', 'gamma2', 'kappa', 'raOffset', 'decOffset', 

289 'spatialmodel', 'internalExtinctionModel', 

290 'galacticExtinctionModel', 'galacticAv', 'galacticRv'] 

291 

292 default_columns = [('redshift', 0., float), ('gamma1', 0., float), ('gamma2', 0., float), 

293 ('kappa', 0., float), ('raOffset', 0., float), ('decOffset', 0., float), 

294 ('galacticExtinctionModel', 'CCM', (str, 3)), ('galacticRv', 3.1, float), 

295 ('internalExtinctionModel', 'none', (str, 4))] 

296 

297 default_formats = {'S': '%s', 'f': '%.9g', 'i': '%i'} 

298 

299 spatialModel = "point" 

300 

301 transformations = {'raPhoSim': np.degrees, 'decPhoSim': np.degrees} 

302 

303 

304class PhoSimCatalogZPoint(PhosimInputBase, PhoSimAstrometryGalaxies, EBVmixin): 

305 

306 catalog_type = 'phoSim_catalog_ZPOINT' 

307 

308 column_outputs = ['prefix', 'uniqueId', 'raPhoSim', 'decPhoSim', 'phoSimMagNorm', 'sedFilepath', 

309 'redshift', 'gamma1', 'gamma2', 'kappa', 'raOffset', 'decOffset', 

310 'spatialmodel', 'internalExtinctionModel', 

311 'galacticExtinctionModel', 'galacticAv', 'galacticRv'] 

312 

313 default_columns = [('gamma1', 0., float), ('gamma2', 0., float), ('kappa', 0., float), 

314 ('raOffset', 0., float), ('decOffset', 0., float), 

315 ('spatialmodel', 'ZPOINT', (str, 6)), 

316 ('galacticExtinctionModel', 'CCM', (str, 3)), 

317 ('galacticAv', 0.1, float), ('galacticRv', 3.1, float), 

318 ('internalExtinctionModel', 'none', (str, 4))] 

319 

320 default_formats = {'S': '%s', 'f': '%.9g', 'i': '%i'} 

321 

322 spatialModel = "point" 

323 

324 transformations = {'raPhoSim': np.degrees, 'decPhoSim': np.degrees} 

325 

326 

327class PhoSimCatalogSN(PhoSimCatalogZPoint, FrozenSNCat, EBVmixin): 

328 """ 

329 Mixin for PhoSim Instance Catalogs in PhoSim 

330 """ 

331 catalog_type = 'phoSim_SNcatalog' 

332 writeSEDFile = True 

333 

334 def get_sedFilepath(self): 

335 return self.column_by_name('TsedFilepath') 

336 

337 def get_phoSimMagNorm(self): 

338 return self.column_by_name('magNorm') 

339 

340 

341class PhoSimCatalogSersic2D(PhoSimCatalogZPoint): 

342 

343 catalog_type = 'phoSim_catalog_SERSIC2D' 

344 

345 column_outputs = ['prefix', 'uniqueId', 'raPhoSim', 'decPhoSim', 'phoSimMagNorm', 'sedFilepath', 

346 'redshift', 'gamma1', 'gamma2', 'kappa', 'raOffset', 'decOffset', 

347 'spatialmodel', 'majorAxis', 'minorAxis', 'positionAngle', 'sindex', 

348 'internalExtinctionModel', 'internalAv', 'internalRv', 

349 'galacticExtinctionModel', 'galacticAv', 'galacticRv',] 

350 

351 default_columns = [('gamma1', 0., float), ('gamma2', 0., float), ('kappa', 0., float), 

352 ('raOffset', 0., float), ('decOffset', 0., float), 

353 ('galacticAv', 0.1, float), ('galacticRv', 3.1, float), 

354 ('galacticExtinctionModel', 'CCM', (str, 3)), 

355 ('internalExtinctionModel', 'CCM', (str, 3)), ('internalAv', 0., float), 

356 ('internalRv', 3.1, float)] 

357 

358 default_formats = {'S': '%s', 'f': '%.9g', 'i': '%i'} 

359 

360 spatialModel = "sersic2d" 

361 

362 transformations = {'raPhoSim': np.degrees, 'decPhoSim': np.degrees, 'positionAngle': np.degrees, 

363 'majorAxis': arcsecFromRadians, 'minorAxis': arcsecFromRadians} 

364 

365 @compound('raPhoSim', 'decPhoSim') 

366 def get_phoSimCoordinates(self): 

367 """Getter for RA, Dec coordinates expected by PhoSim. 

368 

369 These are observed RA, Dec coordinates with the effects of nutation, aberration, 

370 and precession subtracted out by the PhosimInputBase._dePrecess() method. 

371 This preserves the relative effects of nutation, aberration, and precession while 

372 re-aligning the catalog with the boresite RA, Dec so that astrometric solutions 

373 make sense.""" 

374 

375 ra = self.column_by_name('raJ2000') 

376 dec = self.column_by_name('decJ2000') 

377 raObs, decObs = _observedFromICRS(ra, dec, includeRefraction = False, obs_metadata=self.obs_metadata, 

378 epoch=self.db_obj.epoch) 

379 

380 return self._dePrecess(raObs, decObs, self.obs_metadata) 

381 

382 

383class PhoSimCatalogSSM(PhosimInputBase, PhoSimAstrometrySSM): 

384 

385 catalog_type = 'phoSim_catalog_SSM' 

386 

387 column_outputs = ['prefix', 'uniqueId', 'raPhoSim', 'decPhoSim', 'phoSimMagNorm', 'sedFilepath', 

388 'redshift', 'gamma1', 'gamma2', 'kappa', 'raOffset', 'decOffset', 

389 'spatialmodel', 'internalExtinctionModel', 'galacticExtinctionModel'] 

390 

391 default_columns = [('redshift', 0., float), ('gamma1', 0., float), ('gamma2', 0., float), 

392 ('kappa', 0., float), ('raOffset', 0., float), ('decOffset', 0., float), 

393 ('galacticExtinctionModel', 'none', (str, 4)), 

394 ('internalExtinctionModel', 'none', (str, 4))] 

395 

396 default_formats = {'S': '%s', 'f': '%.9g', 'i': '%i'} 

397 

398 spatialModel = "point" 

399 

400 transformations = {'raPhoSim': np.degrees, 'decPhoSim': np.degrees}