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#!/usr/local/bin/python 

2# encoding: utf-8 

3""" 

4*crossmatch a list of transients against a suite of catalogues according to given search algorithm* 

5 

6:Author: 

7 David Young 

8""" 

9from __future__ import division 

10from builtins import zip 

11from builtins import object 

12from past.utils import old_div 

13import sys 

14import os 

15import math 

16os.environ['TERM'] = 'vt100' 

17from fundamentals import tools 

18from sherlock.catalogue_conesearch import catalogue_conesearch 

19from astrocalc.distances import converter 

20from astrocalc.coords import separations 

21import time 

22from fundamentals.mysql import database 

23 

24 

25class transient_catalogue_crossmatch(object): 

26 """ 

27 *crossmatch a list of transients against a suite of catalogues according to given search algorithm* 

28 

29 **Key Arguments** 

30 

31 - ``dbConn`` -- mysql database connection for the catalogues 

32 - ``log`` -- logger 

33 - ``settings`` -- the settings dictionary 

34 - ``colMaps`` -- maps of the important column names for each table/view in the crossmatch-catalogues database 

35 - ``transients`` -- the list of transients 

36 

37 

38 **Usage** 

39 

40 To setup your logger, settings and database connections, please use the ``fundamentals`` package (`see tutorial here <http://fundamentals.readthedocs.io/en/latest/#tutorial>`_). 

41 

42 To initiate a transient_catalogue_crossmatch object, use the following: 

43 

44 ```python 

45 from sherlock import transient_catalogue_crossmatch 

46 xmatcher = transient_catalogue_crossmatch( 

47 log=log, 

48 settings=settings, 

49 colMaps=colMaps, 

50 transients=transients, 

51 dbSettings=settings["database settings"]["static catalogues"] 

52 ) 

53 ``` 

54 

55 Then to run the transient through the search algorithm found in the settings file, use the ``match`` method: 

56 

57 ```python 

58 classifications = xmatcher.match() 

59 ``` 

60 

61 

62 .. todo :: 

63 

64 - update key arguments values and definitions with defaults 

65 - update return values and definitions 

66 - update usage examples and text 

67 - update docstring text 

68 - check sublime snippet exists 

69 - clip any useful text to docs mindmap 

70 - regenerate the docs and check redendering of this docstring 

71 """ 

72 # Initialisation 

73 

74 def __init__( 

75 self, 

76 log, 

77 settings=False, 

78 colMaps=False, 

79 transients=[], 

80 dbSettings=False, 

81 dbConn=False 

82 ): 

83 self.log = log 

84 log.debug("instansiating a new 'transient_catalogue_crossmatch' object") 

85 self.dbConn = dbConn 

86 self.settings = settings 

87 self.transients = transients 

88 self.colMaps = colMaps 

89 

90 if dbSettings: 

91 self.dbConn = database( 

92 log=self.log, 

93 dbSettings=dbSettings 

94 ).connect() 

95 

96 # xt-self-arg-tmpx 

97 return None 

98 

99 def match(self): 

100 """ 

101 *match the transients against the sherlock-catalogues according to the search algorithm and return matches alongside the predicted classification(s)* 

102 

103 **Return** 

104 

105 - ``classification`` -- the crossmatch results and classifications assigned to the transients 

106 

107 

108 See the class docstring for usage. 

109 

110 .. todo :: 

111 

112 - update key arguments values and definitions with defaults 

113 - update return values and definitions 

114 - update usage examples and text 

115 - update docstring text 

116 - check sublime snippet exists 

117 - clip any useful text to docs mindmap 

118 - regenerate the docs and check redendering of this docstring 

119 """ 

120 self.log.debug('starting the ``match`` method') 

121 

122 classifications = [] 

123 

124 # COUNT NUMBER OF TRANSIENT TO CROSSMATCH 

125 numberOfTransients = len(self.transients) 

126 count = 0 

127 

128 # GRAB SEARCH ALGORITHM 

129 sa = self.settings["search algorithm"] 

130 

131 # FOR EACH TRANSIENT SOURCE IN THE LIST ... 

132 allCatalogueMatches = [] 

133 catalogueMatches = [] 

134 

135 # SYNONYM SEARCHES 

136 # ITERATE THROUGH SEARCH ALGORITHM IN ORDER 

137 # PRESENTED IN THE SETTINGS FILE 

138 brightnessFilters = ["bright", "faint", "general"] 

139 for search_name, searchPara in list(sa.items()): 

140 for bf in brightnessFilters: 

141 if bf not in searchPara: 

142 continue 

143 if "synonym" not in searchPara[bf] or searchPara[bf]["synonym"] == False: 

144 continue 

145 self.log.debug(""" searching: %(search_name)s""" % locals()) 

146 if "physical radius kpc" in searchPara[bf]: 

147 # THE PHYSICAL SEPARATION SEARCHES 

148 self.log.debug( 

149 'checking physical distance crossmatches in %(search_name)s' % locals()) 

150 catalogueMatches = self.physical_separation_crossmatch_against_catalogue( 

151 objectList=self.transients, 

152 searchPara=searchPara, 

153 search_name=search_name + " physical", 

154 brightnessFilter=bf, 

155 classificationType="synonym" 

156 ) 

157 else: 

158 # THE ANGULAR SEPARATION SEARCHES 

159 self.log.debug( 

160 'Crossmatching against %(search_name)s' % locals()) 

161 # RENAMED from searchCatalogue 

162 catalogueMatches = self.angular_crossmatch_against_catalogue( 

163 objectList=self.transients, 

164 searchPara=searchPara, 

165 search_name=search_name + " angular", 

166 brightnessFilter=bf, 

167 classificationType="synonym" 

168 ) 

169 

170 # ADD CLASSIFICATION AND CROSSMATCHES IF FOUND 

171 if catalogueMatches: 

172 allCatalogueMatches = allCatalogueMatches + catalogueMatches 

173 

174 synonymIDs = [] 

175 synonymIDs[:] = [xm["transient_object_id"] 

176 for xm in allCatalogueMatches] 

177 

178 if self.settings["stop-search-on-synonym-match"]: 

179 remainingTransientsToMatch = [] 

180 remainingTransientsToMatch[:] = [ 

181 t for t in self.transients if t["id"] not in synonymIDs] 

182 else: 

183 remainingTransientsToMatch = self.transients[:] 

184 

185 # ASSOCIATION SEARCHES 

186 # ITERATE THROUGH SEARCH ALGORITHM IN ORDER 

187 # PRESENTED IN THE SETTINGS FILE 

188 if len(remainingTransientsToMatch) > 0: 

189 for search_name, searchPara in list(sa.items()): 

190 self.log.debug(""" searching: %(search_name)s""" % locals()) 

191 for bf in brightnessFilters: 

192 if bf not in searchPara: 

193 continue 

194 if "association" not in searchPara[bf] or searchPara[bf]["association"] == False: 

195 continue 

196 if "physical radius kpc" in searchPara[bf]: 

197 

198 # THE PHYSICAL SEPARATION SEARCHES 

199 self.log.debug( 

200 'checking physical distance crossmatches in %(search_name)s' % locals()) 

201 catalogueMatches = self.physical_separation_crossmatch_against_catalogue( 

202 objectList=remainingTransientsToMatch, 

203 searchPara=searchPara, 

204 search_name=search_name + " physical", 

205 brightnessFilter=bf, 

206 classificationType="association" 

207 ) 

208 else: 

209 # THE ANGULAR SEPARATION SEARCHES 

210 self.log.debug( 

211 'Crossmatching against %(search_name)s' % locals()) 

212 

213 # RENAMED from searchCatalogue 

214 catalogueMatches = self.angular_crossmatch_against_catalogue( 

215 objectList=remainingTransientsToMatch, 

216 searchPara=searchPara, 

217 search_name=search_name + " angular", 

218 brightnessFilter=bf, 

219 classificationType="association" 

220 ) 

221 

222 # ADD CLASSIFICATION AND CROSSMATCHES IF FOUND 

223 if catalogueMatches: 

224 allCatalogueMatches = allCatalogueMatches + catalogueMatches 

225 catalogueMatches = [] 

226 

227 associationIDs = [] 

228 associationIDs[:] = [xm["transient_object_id"] 

229 for xm in allCatalogueMatches] 

230 

231 nonAssociationTransients = [] 

232 nonAssociationTransients[:] = [ 

233 t for t in self.transients if t["id"] not in associationIDs] 

234 

235 # ANNOTATION SEARCHES 

236 # ITERATE THROUGH SEARCH ALGORITHM IN ORDER 

237 # PRESENTED IN THE SETTINGS FILE 

238 brightnessFilters = ["bright", "faint", "general"] 

239 for search_name, searchPara in list(sa.items()): 

240 for bf in brightnessFilters: 

241 if bf not in searchPara: 

242 continue 

243 if "annotation" not in searchPara[bf] or searchPara[bf]["annotation"] == False: 

244 continue 

245 self.log.debug(""" searching: %(search_name)s""" % locals()) 

246 if "physical radius kpc" in searchPara[bf]: 

247 # THE PHYSICAL SEPARATION SEARCHES 

248 self.log.debug( 

249 'checking physical distance crossmatches in %(search_name)s' % locals()) 

250 if bf in searchPara: 

251 catalogueMatches = self.physical_separation_crossmatch_against_catalogue( 

252 objectList=nonAssociationTransients, 

253 searchPara=searchPara, 

254 search_name=search_name + " physical", 

255 brightnessFilter=bf, 

256 classificationType="annotation" 

257 ) 

258 else: 

259 # THE ANGULAR SEPARATION SEARCHES 

260 self.log.debug( 

261 'Crossmatching against %(search_name)s' % locals()) 

262 # RENAMED from searchCatalogue 

263 if bf in searchPara: 

264 catalogueMatches = self.angular_crossmatch_against_catalogue( 

265 objectList=nonAssociationTransients, 

266 searchPara=searchPara, 

267 search_name=search_name + " angular", 

268 brightnessFilter=bf, 

269 classificationType="annotation" 

270 ) 

271 

272 # ADD CLASSIFICATION AND CROSSMATCHES IF FOUND 

273 if catalogueMatches: 

274 allCatalogueMatches = allCatalogueMatches + catalogueMatches 

275 

276 self.log.debug('completed the ``match`` method') 

277 return allCatalogueMatches 

278 

279 def angular_crossmatch_against_catalogue( 

280 self, 

281 objectList, 

282 searchPara={}, 

283 search_name="", 

284 brightnessFilter=False, 

285 physicalSearch=False, 

286 classificationType=False 

287 ): 

288 """*perform an angular separation crossmatch against a given catalogue in the database and annotate the crossmatch with some value added parameters (distances, physical separations, sub-type of transient etc)* 

289 

290 **Key Arguments** 

291 

292 - ``objectList`` -- the list of transient locations to match against the crossmatch catalogue 

293 - ``searchPara`` -- the search parameters for this individual search as lifted from the search algorithm in the sherlock settings file 

294 - ``search_name`` -- the name of the search as given in the sherlock settings file 

295 - ``brightnessFilter`` -- is this search to be constrained by magnitude of the catalogue sources? Default *False*. [bright|faint|general] 

296 - ``physicalSearch`` -- is this angular search a sub-part of a physical separation search 

297 - ``classificationType`` -- synonym, association or annotation. Default *False* 

298 

299 

300 **Return** 

301 

302 

303 - matchedObjects -- any sources matched against the object 

304 

305 **Usage** 

306 

307 Take a list of transients from somewhere 

308 

309 ```python 

310 transients = [ 

311 {'ps1_designation': u'PS1-14aef', 

312 'name': u'4L3Piiq', 

313 'detection_list_id': 2, 

314 'local_comments': u'', 

315 'ra': 0.02548233704918263, 

316 'followup_id': 2065412L, 

317 'dec': -4.284933417540423, 

318 'id': 1000006110041705700L, 

319 'object_classification': 0L 

320 }, 

321 

322 {'ps1_designation': u'PS1-13dcr', 

323 'name': u'3I3Phzx', 

324 'detection_list_id': 2, 

325 'local_comments': u'', 

326 'ra': 4.754236999477372, 

327 'followup_id': 1140386L, 

328 'dec': 28.276703631398625, 

329 'id': 1001901011281636100L, 

330 'object_classification': 0L 

331 }, 

332 

333 {'ps1_designation': u'PS1-13dhc', 

334 'name': u'3I3Pixd', 

335 'detection_list_id': 2, 

336 'local_comments': u'', 

337 'ra': 1.3324973428505413, 

338 'followup_id': 1202386L, 

339 'dec': 32.98869220595689, 

340 'id': 1000519791325919200L, 

341 'object_classification': 0L 

342 } 

343 ] 

344 ``` 

345 

346 Then run the ``angular_crossmatch_against_catalogue`` method to crossmatch against the catalogues and return results: 

347 

348 ```python 

349 # ANGULAR CONESEARCH ON CATALOGUE 

350 search_name = "ned_d spec sn" 

351 searchPara = self.settings["search algorithm"][search_name] 

352 matchedObjects = xmatcher.angular_crossmatch_against_catalogue( 

353 objectList=transients, 

354 searchPara=searchPara, 

355 search_name=search_name 

356 ) 

357 ``` 

358 

359 

360 .. todo :: 

361 

362 - update key arguments values and definitions with defaults 

363 - update return values and definitions 

364 - update usage examples and text 

365 - update docstring text 

366 - check sublime snippet exists 

367 - clip any useful text to docs mindmap 

368 - regenerate the docs and check redendering of this docstring 

369 """ 

370 self.log.debug( 

371 'starting the ``angular_crossmatch_against_catalogue`` method') 

372 

373 self.log.info("STARTING %s SEARCH" % 

374 (search_name,)) 

375 

376 start_time = time.time() 

377 

378 # DEFAULTS 

379 

380 # print search_name, classificationType 

381 

382 magnitudeLimitFilter = None 

383 upperMagnitudeLimit = False 

384 lowerMagnitudeLimit = False 

385 

386 catalogueName = searchPara["database table"] 

387 

388 if not "mag column" in searchPara: 

389 searchPara["mag column"] = None 

390 

391 if brightnessFilter: 

392 if "mag column" in searchPara and searchPara["mag column"]: 

393 magnitudeLimitFilter = self.colMaps[ 

394 catalogueName][searchPara["mag column"] + "ColName"] 

395 theseSearchPara = searchPara[brightnessFilter] 

396 else: 

397 theseSearchPara = searchPara 

398 

399 # EXTRACT PARAMETERS FROM ARGUMENTS & SETTINGS FILE 

400 if classificationType == "synonym": 

401 radius = self.settings["synonym radius arcsec"] 

402 matchedType = theseSearchPara["synonym"] 

403 elif classificationType == "association": 

404 radius = theseSearchPara["angular radius arcsec"] 

405 matchedType = theseSearchPara["association"] 

406 elif classificationType == "annotation": 

407 radius = theseSearchPara["angular radius arcsec"] 

408 matchedType = theseSearchPara["annotation"] 

409 

410 if brightnessFilter == "faint": 

411 upperMagnitudeLimit = theseSearchPara["mag limit"] 

412 elif brightnessFilter == "bright": 

413 lowerMagnitudeLimit = theseSearchPara["mag limit"] 

414 elif brightnessFilter == "general": 

415 if "faint" in searchPara: 

416 lowerMagnitudeLimit = searchPara["faint"]["mag limit"] 

417 if "bright" in searchPara: 

418 upperMagnitudeLimit = searchPara["bright"]["mag limit"] 

419 

420 # VARIABLES 

421 matchedObjects = [] 

422 matchSubset = [] 

423 

424 transRAs = [] 

425 transRAs[:] = [t['ra'] for t in objectList] 

426 transDecs = [] 

427 transDecs[:] = [t['dec'] for t in objectList] 

428 

429 if len(transRAs) == 0: 

430 return [] 

431 

432 cs = catalogue_conesearch( 

433 log=self.log, 

434 ra=transRAs, 

435 dec=transDecs, 

436 radiusArcsec=radius, 

437 colMaps=self.colMaps, 

438 tableName=catalogueName, 

439 dbConn=self.dbConn, 

440 nearestOnly=False, 

441 physicalSearch=physicalSearch, 

442 upperMagnitudeLimit=upperMagnitudeLimit, 

443 lowerMagnitudeLimit=lowerMagnitudeLimit, 

444 magnitudeLimitFilter=magnitudeLimitFilter 

445 ) 

446 

447 # catalogueMatches ARE ORDERED BY ANGULAR SEPARATION 

448 indices, catalogueMatches = cs.search() 

449 count = 1 

450 annotatedcatalogueMatches = [] 

451 

452 for i, xm in zip(indices, catalogueMatches): 

453 

454 # CALCULATE PHYSICAL PARAMETERS ... IF WE CAN 

455 if "cmSepArcsec" in xm: 

456 xm["separationArcsec"] = xm["cmSepArcsec"] 

457 # CALCULATE SEPARATION IN ARCSEC 

458 

459 calculator = separations( 

460 log=self.log, 

461 ra1=objectList[i]["ra"], 

462 dec1=objectList[i]["dec"], 

463 ra2=xm["ra"], 

464 dec2=xm["dec"] 

465 ) 

466 angularSeparation, north, east = calculator.get() 

467 

468 xm["northSeparationArcsec"] = north 

469 xm["eastSeparationArcsec"] = east 

470 del xm["cmSepArcsec"] 

471 

472 xm["association_type"] = matchedType 

473 xm["catalogue_view_name"] = catalogueName 

474 xm["transient_object_id"] = objectList[i]["id"] 

475 xm["catalogue_table_name"] = self.colMaps[ 

476 catalogueName]["description"] 

477 xm["catalogue_table_id"] = self.colMaps[ 

478 catalogueName]["table_id"] 

479 xm["catalogue_view_id"] = self.colMaps[ 

480 catalogueName]["id"] 

481 if classificationType == "synonym": 

482 xm["classificationReliability"] = 1 

483 elif classificationType == "association": 

484 xm["classificationReliability"] = 2 

485 elif classificationType == "annotation": 

486 xm["classificationReliability"] = 3 

487 

488 xm = self._annotate_crossmatch_with_value_added_parameters( 

489 crossmatchDict=xm, 

490 catalogueName=catalogueName, 

491 searchPara=theseSearchPara, 

492 search_name=search_name 

493 ) 

494 annotatedcatalogueMatches.append(xm) 

495 

496 catalogueMatches = annotatedcatalogueMatches 

497 

498 # IF BRIGHT STAR SEARCH 

499 if brightnessFilter == "bright" and "star" in search_name: 

500 catalogueMatches = self._bright_star_match( 

501 matchedObjects=catalogueMatches, 

502 catalogueName=catalogueName, 

503 lowerMagnitudeLimit=lowerMagnitudeLimit, 

504 magnitudeLimitFilter=searchPara["mag column"] 

505 ) 

506 

507 if brightnessFilter == "general" and "galaxy" in search_name and "galaxy-like" not in search_name and "physical radius kpc" not in theseSearchPara: 

508 catalogueMatches = self._galaxy_association_cuts( 

509 matchedObjects=catalogueMatches, 

510 catalogueName=catalogueName, 

511 lowerMagnitudeLimit=lowerMagnitudeLimit, 

512 upperMagnitudeLimit=upperMagnitudeLimit, 

513 magnitudeLimitFilter=searchPara["mag column"] 

514 ) 

515 

516 if "match nearest source only" in theseSearchPara and theseSearchPara["match nearest source only"] == True and len(catalogueMatches): 

517 nearestMatches = [] 

518 transList = [] 

519 for c in catalogueMatches: 

520 if c["transient_object_id"] not in transList: 

521 transList.append(c["transient_object_id"]) 

522 nearestMatches.append(c) 

523 catalogueMatches = nearestMatches 

524 

525 self.log.debug( 

526 'completed the ``angular_crossmatch_against_catalogue`` method') 

527 

528 self.log.debug("FINISHED %s SEARCH IN %0.5f s" % 

529 (search_name, time.time() - start_time,)) 

530 

531 return catalogueMatches 

532 

533 def _annotate_crossmatch_with_value_added_parameters( 

534 self, 

535 crossmatchDict, 

536 catalogueName, 

537 searchPara, 

538 search_name): 

539 """*annotate each crossmatch with physical parameters such are distances etc* 

540 

541 **Key Arguments** 

542 

543 - ``crossmatchDict`` -- the crossmatch dictionary 

544 - ``catalogueName`` -- the name of the catalogue the crossmatch results from 

545 - ``searchPara`` -- the search parameters for this individual search as lifted from the search algorithm in the sherlock settings file 

546 - ``search_name`` -- the name of the search as given in the sherlock settings file 

547 

548 

549 **Return** 

550 

551 - ``crossmatchDict`` -- the annotated crossmatch dictionary 

552 

553 

554 .. todo :: 

555 

556 - update key arguments values and definitions with defaults 

557 - update return values and definitions 

558 - update usage examples and text 

559 - update docstring text 

560 - check sublime snippet exists 

561 - clip any useful text to docs mindmap 

562 - regenerate the docs and check redendering of this docstring 

563 """ 

564 self.log.debug( 

565 'starting the ``_annotate_crossmatch_with_value_added_parameters`` method') 

566 

567 redshift = None 

568 z = None 

569 scale = None 

570 distance = None 

571 distance_modulus = None 

572 major_axis_arcsec = None 

573 direct_distance = None 

574 direct_distance_scale = None 

575 direct_distance_modulus = None 

576 

577 # IF THERE'S A REDSHIFT, CALCULATE PHYSICAL PARAMETERS 

578 if 'z' in crossmatchDict: 

579 # THE CATALOGUE HAS A REDSHIFT COLUMN 

580 redshift = crossmatchDict['z'] 

581 if redshift and redshift > 0.0: 

582 # CALCULATE DISTANCE MODULUS, ETC 

583 c = converter(log=self.log) 

584 dists = c.redshift_to_distance( 

585 z=redshift, 

586 WM=0.3, 

587 WV=0.7, 

588 H0=70.0 

589 ) 

590 

591 if dists: 

592 z = dists['z'] 

593 scale = dists["da_scale"] 

594 distance = dists["dl_mpc"] 

595 distance_modulus = dists["dmod"] 

596 # ADD MAJOR AXIS VALUE 

597 if "or within semi major axis" in searchPara and searchPara["or within semi major axis"] == True and "semiMajor" in crossmatchDict and crossmatchDict["semiMajor"]: 

598 major_axis_arcsec = crossmatchDict[ 

599 "semiMajor"] * self.colMaps[catalogueName]["semiMajorToArcsec"] 

600 

601 if "semiMajor" in crossmatchDict: 

602 del crossmatchDict["semiMajor"] 

603 # ADD DISTANCE VALUES 

604 if "distance" in crossmatchDict and crossmatchDict["distance"]: 

605 direct_distance = crossmatchDict["distance"] 

606 direct_distance_scale = old_div(direct_distance, 206.264806) 

607 direct_distance_modulus = 5 * \ 

608 math.log10(direct_distance * 1e6) - 5 

609 # crossmatchDict['z'] = z 

610 crossmatchDict['scale'] = scale 

611 crossmatchDict['distance'] = distance 

612 crossmatchDict['distance_modulus'] = distance_modulus 

613 crossmatchDict['major_axis_arcsec'] = major_axis_arcsec 

614 crossmatchDict['direct_distance'] = direct_distance 

615 crossmatchDict['direct_distance_scale'] = direct_distance_scale 

616 crossmatchDict['direct_distance_modulus'] = direct_distance_modulus 

617 

618 crossmatchDict['catalogue_object_type'] = self.colMaps[ 

619 catalogueName]["object_type"] 

620 crossmatchDict["search_name"] = search_name 

621 crossmatchDict["raDeg"] = crossmatchDict["ra"] 

622 

623 crossmatchDict["decDeg"] = crossmatchDict["dec"] 

624 del crossmatchDict["ra"] 

625 del crossmatchDict["dec"] 

626 crossmatchDict["original_search_radius_arcsec"] = searchPara[ 

627 "angular radius arcsec"] 

628 

629 physical_separation_kpc = None 

630 # CALCULATE MOST ACCURATE PHYSICAL SEPARATION 

631 if crossmatchDict["direct_distance_scale"]: 

632 physical_separation_kpc = crossmatchDict[ 

633 "direct_distance_scale"] * crossmatchDict["separationArcsec"] 

634 elif crossmatchDict["scale"]: 

635 physical_separation_kpc = crossmatchDict[ 

636 "scale"] * crossmatchDict["separationArcsec"] 

637 

638 crossmatchDict["physical_separation_kpc"] = physical_separation_kpc 

639 

640 self.log.debug( 

641 'completed the ``_annotate_crossmatch_with_value_added_parameters`` method') 

642 return crossmatchDict 

643 

644 def _bright_star_match( 

645 self, 

646 matchedObjects, 

647 catalogueName, 

648 magnitudeLimitFilter, 

649 lowerMagnitudeLimit): 

650 """*perform a bright star match on the crossmatch results if required by the catalogue search* 

651 

652 **Key Arguments** 

653 

654 - ``matchedObjects`` -- the list of matched sources from the catalogue crossmatch 

655 - ``catalogueName`` -- the name of the catalogue the crossmatch results from 

656 - ``magnitudeLimitFilter`` -- the name of the column containing the magnitude to filter on 

657 - ``lowerMagnitudeLimit`` -- the lower magnitude limit to match bright stars against 

658 

659 

660 **Return** 

661 

662 - ``brightStarMatches`` -- the trimmed matched sources (bright stars associations only) 

663 

664 

665 .. todo :: 

666 

667 - update key arguments values and definitions with defaults 

668 - update return values and definitions 

669 - update usage examples and text 

670 - update docstring text 

671 - check sublime snippet exists 

672 - clip any useful text to docs mindmap 

673 - regenerate the docs and check redendering of this docstring 

674 """ 

675 self.log.debug('starting the ``_bright_star_match`` method') 

676 

677 import decimal 

678 decimal.getcontext().prec = 10 

679 

680 # MATCH BRIGHT STAR ASSOCIATIONS 

681 brightStarMatches = [] 

682 for row in matchedObjects: 

683 mag = decimal.Decimal(row[magnitudeLimitFilter]) 

684 if mag and mag < lowerMagnitudeLimit: 

685 sep = decimal.Decimal(row["separationArcsec"]) 

686 if sep < decimal.Decimal(decimal.Decimal(10)**(-decimal.Decimal(0.2) * mag + decimal.Decimal(3.7))) and sep < 20.: 

687 brightStarMatches.append(row) 

688 

689 self.log.debug('completed the ``_bright_star_match`` method') 

690 return brightStarMatches 

691 

692 def _galaxy_association_cuts( 

693 self, 

694 matchedObjects, 

695 catalogueName, 

696 magnitudeLimitFilter, 

697 upperMagnitudeLimit, 

698 lowerMagnitudeLimit): 

699 """*perform a bright star match on the crossmatch results if required by the catalogue search* 

700 

701 **Key Arguments** 

702 

703 - ``matchedObjects`` -- the list of matched sources from the catalogue crossmatch 

704 - ``catalogueName`` -- the name of the catalogue the crossmatch results from 

705 - ``magnitudeLimitFilter`` -- the name of the column containing the magnitude to filter on 

706 - ``lowerMagnitudeLimit`` -- the lower magnitude limit to match general galaxies against 

707 - ``upperMagnitudeLimit`` -- the upper magnitude limit to match general galaxies against 

708 

709 

710 **Return** 

711 

712 - ``galaxyMatches`` -- the trimmed matched sources (associated galaxies only) 

713 

714 

715 .. todo :: 

716 

717 - update key arguments values and definitions with defaults 

718 - update return values and definitions 

719 - update usage examples and text 

720 - update docstring text 

721 - check sublime snippet exists 

722 - clip any useful text to docs mindmap 

723 - regenerate the docs and check redendering of this docstring 

724 """ 

725 self.log.debug('starting the ``_galaxy_association_cuts`` method') 

726 

727 import decimal 

728 decimal.getcontext().prec = 10 

729 

730 # MATCH BRIGHT STAR ASSOCIATIONS 

731 galaxyMatches = [] 

732 for row in matchedObjects: 

733 if not magnitudeLimitFilter or row[magnitudeLimitFilter] == None: 

734 galaxyMatches.append(row) 

735 else: 

736 mag = decimal.Decimal(row[magnitudeLimitFilter]) 

737 if mag and mag < lowerMagnitudeLimit and mag > upperMagnitudeLimit: 

738 sep = decimal.Decimal(row["separationArcsec"]) 

739 if sep < decimal.Decimal(decimal.Decimal(10)**(decimal.Decimal(old_div((decimal.Decimal(25.) - mag), decimal.Decimal(6.))))): 

740 galaxyMatches.append(row) 

741 

742 self.log.debug('completed the ``_galaxy_association_cuts`` method') 

743 return galaxyMatches 

744 

745 def physical_separation_crossmatch_against_catalogue( 

746 self, 

747 objectList, 

748 searchPara, 

749 search_name, 

750 brightnessFilter=False, 

751 classificationType=False 

752 ): 

753 """*perform an physical separation crossmatch against a given catalogue in the database* 

754 

755 This search is basically the same as the angular separation search except extra filtering is done to exclude sources outside the physical search radius (matched sources require distance info to calulate physical separations) 

756 

757 **Key Arguments** 

758 

759 - ``objectList`` -- transients to be crossmatched 

760 - ``searchPara`` -- parameters of the search (from settings file) 

761 - ``search_name`` -- the name of the search 

762 - ``brightnessFilter`` -- is this search to be constrained by magnitude of the catalogue sources? Default *False*. [bright|faint|general] 

763 - ``classificationType`` -- synonym, association or annotation. Default *False* 

764 

765 

766 **Return** 

767 

768 - matchedObjects -- any sources matched against the object 

769 

770 

771 To run a physical separation crossmatch, run in a similar way to the angular separation crossmatch: 

772 

773 **Usage** 

774 

775 ```python 

776 search_name = "ned spec sn" 

777 searchPara = self.settings["search algorithm"][search_name] 

778 matchedObjects = xmatcher.physical_separation_crossmatch_against_catalogue( 

779 objectList=transients, 

780 searchPara=searchPara, 

781 search_name=search_name 

782 ) 

783 ``` 

784 

785 

786 .. todo :: 

787 

788 - update key arguments values and definitions with defaults 

789 - update return values and definitions 

790 - update usage examples and text 

791 - update docstring text 

792 - check sublime snippet exists 

793 - clip any useful text to docs mindmap 

794 - regenerate the docs and check redendering of this docstring 

795 """ 

796 self.log.debug( 

797 'starting the ``physical_separation_crossmatch_against_catalogue`` method') 

798 

799 start_time = time.time() 

800 

801 bf = brightnessFilter 

802 # SETUP PARAMETERS 

803 tableName = searchPara["database table"] 

804 if bf: 

805 angularRadius = searchPara[bf]["angular radius arcsec"] 

806 physicalRadius = searchPara[bf]["physical radius kpc"] 

807 matchedType = searchPara[bf][classificationType] 

808 if "match nearest source only" in searchPara[bf]: 

809 nearestOnly = searchPara[bf]["match nearest source only"] 

810 else: 

811 nearestOnly = False 

812 else: 

813 angularRadius = searchPara["angular radius arcsec"] 

814 physicalRadius = searchPara["physical radius kpc"] 

815 matchedType = searchPara[classificationType] 

816 if "match nearest source only" in searchPara: 

817 nearestOnly = searchPara["match nearest source only"] 

818 else: 

819 nearestOnly = False 

820 

821 matchedObjects = [] 

822 matchSubset = [] 

823 

824 # RETURN ALL ANGULAR MATCHES BEFORE RETURNING NEAREST PHYSICAL SEARCH 

825 tmpSearchPara = dict(searchPara) 

826 tmpSearchPara["match nearest source only"] = False 

827 

828 # ANGULAR CONESEARCH ON CATALOGUE - RETURN ALL MATCHES 

829 catalogueMatches = self.angular_crossmatch_against_catalogue( 

830 objectList=objectList, 

831 searchPara=tmpSearchPara, 

832 search_name=search_name, 

833 physicalSearch=True, 

834 brightnessFilter=brightnessFilter, 

835 classificationType=classificationType 

836 ) 

837 

838 # OK - WE HAVE SOME ANGULAR SEPARATION MATCHES. NOW SEARCH THROUGH THESE FOR MATCHES WITH 

839 # A PHYSICAL SEPARATION WITHIN THE PHYSICAL RADIUS. 

840 if catalogueMatches: 

841 for row in catalogueMatches: 

842 thisMatch = False 

843 physical_separation_kpc = row["physical_separation_kpc"] 

844 newsearch_name = search_name 

845 # FIRST CHECK FOR MAJOR AXIS MEASUREMENT 

846 # BYPASS NED FAULTY AXES MEASUREMENTS: 

847 # https://gist.github.com/search?utf8=%E2%9C%93&q=user%3Athespacedoctor+ned 

848 

849 if row["major_axis_arcsec"] and ("ned" not in search_name or (row["unkMag"] and row["unkMag"] < 20.)): 

850 

851 if row["separationArcsec"] < row["major_axis_arcsec"] * self.settings["galaxy radius stetch factor"]: 

852 thisMatch = True 

853 newsearch_name = newsearch_name + \ 

854 " (within %s * major axis)" % ( 

855 self.settings["galaxy radius stetch factor"],) 

856 newAngularSep = row[ 

857 "major_axis_arcsec"] * self.settings["galaxy radius stetch factor"] 

858 

859 # NOW CHECK FOR A DIRECT DISTANCE MEASUREMENT 

860 if row["direct_distance_scale"] and physical_separation_kpc < physicalRadius and thisMatch == False: 

861 if row["separationArcsec"] > 300.: 

862 continue 

863 thisMatch = True 

864 newsearch_name = newsearch_name + " (direct distance)" 

865 newAngularSep = old_div(physicalRadius, 

866 row["direct_distance_scale"]) 

867 # NEW CHECK FOR A REDSHIFT DISTANCE 

868 elif row["scale"] and physical_separation_kpc < physicalRadius: 

869 thisMatch = True 

870 newsearch_name = newsearch_name + " (redshift distance)" 

871 newAngularSep = old_div(physicalRadius, row["scale"]) 

872 

873 if thisMatch == True: 

874 row["physical_separation_kpc"] = physical_separation_kpc 

875 row["original_search_radius_arcsec"] = newAngularSep 

876 if physical_separation_kpc: 

877 self.log.debug( 

878 "\t\tPhysical separation = %.2f kpc" % (physical_separation_kpc,)) 

879 row["search_name"] = newsearch_name 

880 matchSubset.append(row) 

881 

882 if matchSubset: 

883 

884 from operator import itemgetter 

885 

886 physicalDicts = [] 

887 physicalDicts[:] = [m for m in matchSubset if m[ 

888 'physical_separation_kpc']] 

889 

890 semiMajor = [] 

891 semiMajor[:] = [m for m in matchSubset if not m[ 

892 'physical_separation_kpc']] 

893 

894 matchSubset = sorted(physicalDicts, key=itemgetter( 

895 'physical_separation_kpc'), reverse=False) + sorted(semiMajor, key=itemgetter( 

896 'separationArcsec'), reverse=False) 

897 

898 if nearestOnly == True: 

899 theseMatches = matchSubset[0] 

900 else: 

901 theseMatches = matchSubset 

902 

903 matchedObjects = matchSubset 

904 self.log.debug( 

905 'completed the ``physical_separation_crossmatch_against_catalogue`` method') 

906 

907 self.log.debug("FINISHED %s SEARCH IN %0.5f s" % 

908 (search_name, time.time() - start_time,)) 

909 

910 return matchedObjects 

911 

912 # use the tab-trigger below for new method 

913 # xt-class-method