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*
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
25class transient_catalogue_crossmatch(object):
26 """
27 *crossmatch a list of transients against a suite of catalogues according to given search algorithm*
29 **Key Arguments**
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
38 **Usage**
40 To setup your logger, settings and database connections, please use the ``fundamentals`` package (`see tutorial here <http://fundamentals.readthedocs.io/en/latest/#tutorial>`_).
42 To initiate a transient_catalogue_crossmatch object, use the following:
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 ```
55 Then to run the transient through the search algorithm found in the settings file, use the ``match`` method:
57 ```python
58 classifications = xmatcher.match()
59 ```
62 .. todo ::
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
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
90 if dbSettings:
91 self.dbConn = database(
92 log=self.log,
93 dbSettings=dbSettings
94 ).connect()
96 # xt-self-arg-tmpx
97 return None
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)*
103 **Return**
105 - ``classification`` -- the crossmatch results and classifications assigned to the transients
108 See the class docstring for usage.
110 .. todo ::
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')
122 classifications = []
124 # COUNT NUMBER OF TRANSIENT TO CROSSMATCH
125 numberOfTransients = len(self.transients)
126 count = 0
128 # GRAB SEARCH ALGORITHM
129 sa = self.settings["search algorithm"]
131 # FOR EACH TRANSIENT SOURCE IN THE LIST ...
132 allCatalogueMatches = []
133 catalogueMatches = []
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 )
170 # ADD CLASSIFICATION AND CROSSMATCHES IF FOUND
171 if catalogueMatches:
172 allCatalogueMatches = allCatalogueMatches + catalogueMatches
174 synonymIDs = []
175 synonymIDs[:] = [xm["transient_object_id"]
176 for xm in allCatalogueMatches]
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[:]
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]:
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())
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 )
222 # ADD CLASSIFICATION AND CROSSMATCHES IF FOUND
223 if catalogueMatches:
224 allCatalogueMatches = allCatalogueMatches + catalogueMatches
225 catalogueMatches = []
227 associationIDs = []
228 associationIDs[:] = [xm["transient_object_id"]
229 for xm in allCatalogueMatches]
231 nonAssociationTransients = []
232 nonAssociationTransients[:] = [
233 t for t in self.transients if t["id"] not in associationIDs]
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 )
272 # ADD CLASSIFICATION AND CROSSMATCHES IF FOUND
273 if catalogueMatches:
274 allCatalogueMatches = allCatalogueMatches + catalogueMatches
276 self.log.debug('completed the ``match`` method')
277 return allCatalogueMatches
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)*
290 **Key Arguments**
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*
300 **Return**
303 - matchedObjects -- any sources matched against the object
305 **Usage**
307 Take a list of transients from somewhere
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 },
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 },
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 ```
346 Then run the ``angular_crossmatch_against_catalogue`` method to crossmatch against the catalogues and return results:
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 ```
360 .. todo ::
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')
373 self.log.info("STARTING %s SEARCH" %
374 (search_name,))
376 start_time = time.time()
378 # DEFAULTS
380 # print search_name, classificationType
382 magnitudeLimitFilter = None
383 upperMagnitudeLimit = False
384 lowerMagnitudeLimit = False
386 catalogueName = searchPara["database table"]
388 if not "mag column" in searchPara:
389 searchPara["mag column"] = None
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
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"]
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"]
420 # VARIABLES
421 matchedObjects = []
422 matchSubset = []
424 transRAs = []
425 transRAs[:] = [t['ra'] for t in objectList]
426 transDecs = []
427 transDecs[:] = [t['dec'] for t in objectList]
429 if len(transRAs) == 0:
430 return []
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 )
447 # catalogueMatches ARE ORDERED BY ANGULAR SEPARATION
448 indices, catalogueMatches = cs.search()
449 count = 1
450 annotatedcatalogueMatches = []
452 for i, xm in zip(indices, catalogueMatches):
454 # CALCULATE PHYSICAL PARAMETERS ... IF WE CAN
455 if "cmSepArcsec" in xm:
456 xm["separationArcsec"] = xm["cmSepArcsec"]
457 # CALCULATE SEPARATION IN ARCSEC
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()
468 xm["northSeparationArcsec"] = north
469 xm["eastSeparationArcsec"] = east
470 del xm["cmSepArcsec"]
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
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)
496 catalogueMatches = annotatedcatalogueMatches
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 )
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 )
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
525 self.log.debug(
526 'completed the ``angular_crossmatch_against_catalogue`` method')
528 self.log.debug("FINISHED %s SEARCH IN %0.5f s" %
529 (search_name, time.time() - start_time,))
531 return catalogueMatches
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*
541 **Key Arguments**
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
549 **Return**
551 - ``crossmatchDict`` -- the annotated crossmatch dictionary
554 .. todo ::
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')
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
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 )
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"]
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
618 crossmatchDict['catalogue_object_type'] = self.colMaps[
619 catalogueName]["object_type"]
620 crossmatchDict["search_name"] = search_name
621 crossmatchDict["raDeg"] = crossmatchDict["ra"]
623 crossmatchDict["decDeg"] = crossmatchDict["dec"]
624 del crossmatchDict["ra"]
625 del crossmatchDict["dec"]
626 crossmatchDict["original_search_radius_arcsec"] = searchPara[
627 "angular radius arcsec"]
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"]
638 crossmatchDict["physical_separation_kpc"] = physical_separation_kpc
640 self.log.debug(
641 'completed the ``_annotate_crossmatch_with_value_added_parameters`` method')
642 return crossmatchDict
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*
652 **Key Arguments**
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
660 **Return**
662 - ``brightStarMatches`` -- the trimmed matched sources (bright stars associations only)
665 .. todo ::
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')
677 import decimal
678 decimal.getcontext().prec = 10
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)
689 self.log.debug('completed the ``_bright_star_match`` method')
690 return brightStarMatches
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*
701 **Key Arguments**
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
710 **Return**
712 - ``galaxyMatches`` -- the trimmed matched sources (associated galaxies only)
715 .. todo ::
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')
727 import decimal
728 decimal.getcontext().prec = 10
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)
742 self.log.debug('completed the ``_galaxy_association_cuts`` method')
743 return galaxyMatches
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*
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)
757 **Key Arguments**
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*
766 **Return**
768 - matchedObjects -- any sources matched against the object
771 To run a physical separation crossmatch, run in a similar way to the angular separation crossmatch:
773 **Usage**
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 ```
786 .. todo ::
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')
799 start_time = time.time()
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
821 matchedObjects = []
822 matchSubset = []
824 # RETURN ALL ANGULAR MATCHES BEFORE RETURNING NEAREST PHYSICAL SEARCH
825 tmpSearchPara = dict(searchPara)
826 tmpSearchPara["match nearest source only"] = False
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 )
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
849 if row["major_axis_arcsec"] and ("ned" not in search_name or (row["unkMag"] and row["unkMag"] < 20.)):
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"]
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"])
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)
882 if matchSubset:
884 from operator import itemgetter
886 physicalDicts = []
887 physicalDicts[:] = [m for m in matchSubset if m[
888 'physical_separation_kpc']]
890 semiMajor = []
891 semiMajor[:] = [m for m in matchSubset if not m[
892 'physical_separation_kpc']]
894 matchSubset = sorted(physicalDicts, key=itemgetter(
895 'physical_separation_kpc'), reverse=False) + sorted(semiMajor, key=itemgetter(
896 'separationArcsec'), reverse=False)
898 if nearestOnly == True:
899 theseMatches = matchSubset[0]
900 else:
901 theseMatches = matchSubset
903 matchedObjects = matchSubset
904 self.log.debug(
905 'completed the ``physical_separation_crossmatch_against_catalogue`` method')
907 self.log.debug("FINISHED %s SEARCH IN %0.5f s" %
908 (search_name, time.time() - start_time,))
910 return matchedObjects
912 # use the tab-trigger below for new method
913 # xt-class-method