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*classify a set of transients defined by a database query in the sherlock settings file*
6:Author:
7 David Young
8"""
9from __future__ import print_function
10from __future__ import division
11from builtins import zip
12from builtins import str
13from builtins import range
14from builtins import object
15from past.utils import old_div
16import sys
17import os
18import collections
19import codecs
20import re
21import math
22import time
23import inspect
24import yaml
25from random import randint
26os.environ['TERM'] = 'vt100'
27from datetime import datetime, date, time, timedelta
28from operator import itemgetter
29import numpy as np
30from fundamentals import tools
31from fundamentals.mysql import readquery, directory_script_runner, writequery
32from fundamentals.renderer import list_of_dictionaries
33from HMpTy.mysql import conesearch
34from HMpTy.htm import sets
35from sherlock.imports import ned
36from sherlock.commonutils import get_crossmatch_catalogues_column_map
37import psutil
38from fundamentals import fmultiprocess
39from fundamentals.mysql import insert_list_of_dictionaries_into_database_tables
40import copy
41import psutil
42import copy
43from operator import itemgetter
44from astrocalc.coords import unit_conversion
46theseBatches = []
47crossmatchArray = []
50class transient_classifier(object):
51 """
52 *The Sherlock Transient Classifier*
54 **Key Arguments**
56 - ``log`` -- logger
57 - ``settings`` -- the settings dictionary
58 - ``update`` -- update the transient database with crossmatch results (boolean)
59 - ``ra`` -- right ascension of a single transient source. Default *False*
60 - ``dec`` -- declination of a single transient source. Default *False*
61 - ``name`` -- the ID of a single transient source. Default *False*
62 - ``verbose`` -- amount of details to print about crossmatches to stdout. 0|1|2 Default *0*
63 - ``updateNed`` -- update the local NED database before running the classifier. Classification will not be as accuracte the NED database is not up-to-date. Default *True*.
64 - ``daemonMode`` -- run sherlock in daemon mode. In daemon mode sherlock remains live and classifies sources as they come into the database. Default *True*
65 - ``updatePeakMags`` -- update peak magnitudes in human-readable annotation of objects (can take some time - best to run occationally)
66 - ``lite`` -- return only a lite version of the results with the topped ranked matches only. Default *False*
67 - ``oneRun`` -- only process one batch of transients, usful for unit testing. Default *False*
69 **Usage**
71 To setup your logger, settings and database connections, please use the ``fundamentals`` package (`see tutorial here <http://fundamentals.readthedocs.io/en/latest/#tutorial>`_).
73 To initiate a transient_classifier object, use the following:
75 .. todo::
77 - update the package tutorial if needed
79 The sherlock classifier can be run in one of two ways. The first is to pass into the coordinates of an object you wish to classify:
81 ```python
82 from sherlock import transient_classifier
83 classifier = transient_classifier(
84 log=log,
85 settings=settings,
86 ra="08:57:57.19",
87 dec="+43:25:44.1",
88 name="PS17gx",
89 verbose=0
90 )
91 classifications, crossmatches = classifier.classify()
92 ```
94 The crossmatches returned are a list of dictionaries giving details of the crossmatched sources. The classifications returned are a list of classifications resulting from these crossmatches. The lists are ordered from most to least likely classification and the indicies for the crossmatch and the classification lists are synced.
96 The second way to run the classifier is to not pass in a coordinate set and therefore cause sherlock to run the classifier on the transient database referenced in the sherlock settings file:
98 ```python
99 from sherlock import transient_classifier
100 classifier = transient_classifier(
101 log=log,
102 settings=settings,
103 update=True
104 )
105 classifier.classify()
106 ```
108 Here the transient list is selected out of the database using the ``transient query`` value in the settings file:
110 ```yaml
111 database settings:
112 transients:
113 user: myusername
114 password: mypassword
115 db: nice_transients
116 host: 127.0.0.1
117 transient table: transientBucket
118 transient query: "select primaryKeyId as 'id', transientBucketId as 'alt_id', raDeg 'ra', decDeg 'dec', name 'name', sherlockClassification as 'object_classification'
119 from transientBucket where object_classification is null
120 transient primary id column: primaryKeyId
121 transient classification column: sherlockClassification
122 tunnel: False
123 ```
125 By setting ``update=True`` the classifier will update the ``sherlockClassification`` column of the ``transient table`` with new classification and populate the ``sherlock_crossmatches`` table with key details of the crossmatched sources from the catalogues database. By setting ``update=False`` results are printed to stdout but the database is not updated (useful for dry runs and testing new algorithms),
128 .. todo ::
130 - update key arguments values and definitions with defaults
131 - update return values and definitions
132 - update usage examples and text
133 - update docstring text
134 - check sublime snippet exists
135 - clip any useful text to docs mindmap
136 - regenerate the docs and check redendering of this docstring
137 """
138 # INITIALISATION
140 def __init__(
141 self,
142 log,
143 settings=False,
144 update=False,
145 ra=False,
146 dec=False,
147 name=False,
148 verbose=0,
149 updateNed=True,
150 daemonMode=False,
151 updatePeakMags=True,
152 oneRun=False,
153 lite=False
154 ):
155 self.log = log
156 log.debug("instansiating a new 'classifier' object")
157 self.settings = settings
158 self.update = update
159 self.ra = ra
160 self.dec = dec
161 self.name = name
162 self.cl = False
163 self.verbose = verbose
164 self.updateNed = updateNed
165 self.daemonMode = daemonMode
166 self.updatePeakMags = updatePeakMags
167 self.oneRun = oneRun
168 self.lite = lite
169 self.filterPreference = [
170 "R", "_r", "G", "V", "_g", "B", "I", "_i", "_z", "J", "H", "K", "U", "_u", "_y", "W1", "unkMag"
171 ]
173 # COLLECT ADVANCED SETTINGS IF AVAILABLE
174 parentDirectory = os.path.dirname(__file__)
175 advs = parentDirectory + "/advanced_settings.yaml"
176 level = 0
177 exists = False
178 count = 1
179 while not exists and len(advs) and count < 10:
180 count += 1
181 level -= 1
182 exists = os.path.exists(advs)
183 if not exists:
184 advs = "/".join(parentDirectory.split("/")
185 [:level]) + "/advanced_settings.yaml"
186 print(advs)
187 if not exists:
188 advs = {}
189 else:
190 with open(advs, 'r') as stream:
191 advs = yaml.load(stream)
192 # MERGE ADVANCED SETTINGS AND USER SETTINGS (USER SETTINGS OVERRIDE)
193 self.settings = {**advs, **self.settings}
195 # INITIAL ACTIONS
196 # SETUP DATABASE CONNECTIONS
197 # SETUP ALL DATABASE CONNECTIONS
198 from sherlock import database
199 db = database(
200 log=self.log,
201 settings=self.settings
202 )
203 dbConns, dbVersions = db.connect()
204 self.dbVersions = dbVersions
205 self.transientsDbConn = dbConns["transients"]
206 self.cataloguesDbConn = dbConns["catalogues"]
208 # SIZE OF BATCHES TO SPLIT TRANSIENT INTO BEFORE CLASSIFYING
209 self.largeBatchSize = self.settings["database-batch-size"]
210 self.miniBatchSize = 1000
212 # LITE VERSION CANNOT BE RUN ON A DATABASE QUERY AS YET
213 if self.ra == False:
214 self.lite = False
216 # IS SHERLOCK CLASSIFIER BEING QUERIED FROM THE COMMAND-LINE?
217 if self.ra and self.dec:
218 self.cl = True
219 if not self.name:
220 self.name = "Transient"
222 # ASTROCALC UNIT CONVERTER OBJECT
223 self.converter = unit_conversion(
224 log=self.log
225 )
227 if self.ra and not isinstance(self.ra, float) and ":" in self.ra:
228 # ASTROCALC UNIT CONVERTER OBJECT
229 self.ra = self.converter.ra_sexegesimal_to_decimal(
230 ra=self.ra
231 )
232 self.dec = self.converter.dec_sexegesimal_to_decimal(
233 dec=self.dec
234 )
236 # DATETIME REGEX - EXPENSIVE OPERATION, LET"S JUST DO IT ONCE
237 self.reDatetime = re.compile('^[0-9]{4}-[0-9]{2}-[0-9]{2}T')
239 return None
241 def classify(self):
242 """
243 *classify the transients selected from the transient selection query in the settings file or passed in via the CL or other code*
245 **Return**
247 - ``crossmatches`` -- list of dictionaries of crossmatched associated sources
248 - ``classifications`` -- the classifications assigned to the transients post-crossmatches (dictionary of rank ordered list of classifications)
251 See class docstring for usage.
253 .. todo ::
255 - update key arguments values and definitions with defaults
256 - update return values and definitions
257 - update usage examples and text
258 - update docstring text
259 - check sublime snippet exists
260 - clip any useful text to docs mindmap
261 - regenerate the docs and check redendering of this docstring
262 """
264 global theseBatches
265 global crossmatchArray
267 self.log.debug('starting the ``classify`` method')
269 remaining = 1
271 # THE COLUMN MAPS - WHICH COLUMNS IN THE CATALOGUE TABLES = RA, DEC,
272 # REDSHIFT, MAG ETC
273 colMaps = get_crossmatch_catalogues_column_map(
274 log=self.log,
275 dbConn=self.cataloguesDbConn
276 )
278 if self.transientsDbConn and self.update:
279 self._create_tables_if_not_exist()
281 import time
282 start_time = time.time()
284 # COUNT SEARCHES
285 sa = self.settings["search algorithm"]
286 searchCount = 0
287 brightnessFilters = ["bright", "faint", "general"]
288 for search_name, searchPara in list(sa.items()):
289 for bf in brightnessFilters:
290 if bf in searchPara:
291 searchCount += 1
293 cpuCount = psutil.cpu_count()
294 if searchCount > cpuCount:
295 searchCount = cpuCount
297 miniBatchSize = self.miniBatchSize
299 while remaining:
301 # IF A TRANSIENT HAS NOT BEEN PASSED IN VIA THE COMMAND-LINE, THEN
302 # QUERY THE TRANSIENT DATABASE
303 if not self.ra and not self.dec:
305 # COUNT REMAINING TRANSIENTS
306 from fundamentals.mysql import readquery
307 sqlQuery = self.settings["database settings"][
308 "transients"]["transient count"]
309 thisInt = randint(0, 100)
310 if "where" in sqlQuery:
311 sqlQuery = sqlQuery.replace(
312 "where", "where %(thisInt)s=%(thisInt)s and " % locals())
314 if remaining == 1 or remaining < self.largeBatchSize:
315 rows = readquery(
316 log=self.log,
317 sqlQuery=sqlQuery,
318 dbConn=self.transientsDbConn,
319 )
320 remaining = rows[0]["count(*)"]
321 else:
322 remaining = remaining - self.largeBatchSize
324 print(
325 "%(remaining)s transient sources requiring a classification remain" % locals())
327 # START THE TIME TO TRACK CLASSIFICATION SPPED
328 start_time = time.time()
330 # A LIST OF DICTIONARIES OF TRANSIENT METADATA
331 transientsMetadataList = self._get_transient_metadata_from_database_list()
333 count = len(transientsMetadataList)
334 print(
335 " now classifying the next %(count)s transient sources" % locals())
337 # EXAMPLE OF TRANSIENT METADATA
338 # { 'name': 'PS17gx',
339 # 'alt_id': 'PS17gx',
340 # 'object_classification': 'SN',
341 # 'dec': '+43:25:44.1',
342 # 'id': 1,
343 # 'ra': '08:57:57.19'}
344 # TRANSIENT PASSED VIA COMMAND-LINE
345 else:
346 # CONVERT SINGLE TRANSIENTS TO LIST
347 if not isinstance(self.ra, list):
348 self.ra = [self.ra]
349 self.dec = [self.dec]
350 self.name = [self.name]
352 # GIVEN TRANSIENTS UNIQUE NAMES IF NOT PROVIDED
353 if not self.name[0]:
354 self.name = []
355 for i, v in enumerate(self.ra):
356 self.name.append("transient_%(i)05d" % locals())
358 transientsMetadataList = []
359 for r, d, n in zip(self.ra, self.dec, self.name):
360 transient = {
361 'name': n,
362 'object_classification': None,
363 'dec': d,
364 'id': n,
365 'ra': r
366 }
367 transientsMetadataList.append(transient)
368 remaining = 0
370 if self.oneRun:
371 remaining = 0
373 if len(transientsMetadataList) == 0:
374 if self.daemonMode == False:
375 remaining = 0
376 print("No transients need classified")
377 return None, None
378 else:
379 print(
380 "No remaining transients need classified, will try again in 5 mins")
381 time.sleep("10")
383 # FROM THE LOCATIONS OF THE TRANSIENTS, CHECK IF OUR LOCAL NED DATABASE
384 # NEEDS UPDATED
385 if self.updateNed:
387 self._update_ned_stream(
388 transientsMetadataList=transientsMetadataList
389 )
391 # SOME TESTING SHOWED THAT 25 IS GOOD
392 total = len(transientsMetadataList)
393 batches = int((old_div(float(total), float(miniBatchSize))) + 1.)
395 if batches == 0:
396 batches = 1
398 start = 0
399 end = 0
400 theseBatches = []
401 for i in range(batches):
402 end = end + miniBatchSize
403 start = i * miniBatchSize
404 thisBatch = transientsMetadataList[start:end]
405 theseBatches.append(thisBatch)
407 if self.verbose:
408 print("BATCH SIZE = %(total)s" % locals())
409 print("MINI BATCH SIZE = %(batches)s x %(miniBatchSize)s" % locals())
411 poolSize = self.settings["cpu-pool-size"]
412 if poolSize and batches < poolSize:
413 poolSize = batches
415 start_time2 = time.time()
417 if self.verbose:
418 print("START CROSSMATCH")
420 crossmatchArray = fmultiprocess(log=self.log, function=_crossmatch_transients_against_catalogues,
421 inputArray=list(range(len(theseBatches))), poolSize=poolSize, settings=self.settings, colMaps=colMaps)
423 if self.verbose:
424 print("FINISH CROSSMATCH/START RANKING: %d" %
425 (time.time() - start_time2,))
426 start_time2 = time.time()
428 classifications = {}
429 crossmatches = []
431 for sublist in crossmatchArray:
432 sublist = sorted(
433 sublist, key=itemgetter('transient_object_id'))
435 # REORGANISE INTO INDIVIDUAL TRANSIENTS FOR RANKING AND
436 # TOP-LEVEL CLASSIFICATION EXTRACTION
438 batch = []
439 if len(sublist) != 0:
440 transientId = sublist[0]['transient_object_id']
441 for s in sublist:
442 if s['transient_object_id'] != transientId:
443 # RANK TRANSIENT CROSSMATCH BATCH
444 cl, cr = self._rank_classifications(
445 batch, colMaps)
446 crossmatches.extend(cr)
447 classifications = dict(
448 list(classifications.items()) + list(cl.items()))
450 transientId = s['transient_object_id']
451 batch = [s]
452 else:
453 batch.append(s)
455 # RANK FINAL BATCH
456 cl, cr = self._rank_classifications(
457 batch, colMaps)
458 classifications = dict(
459 list(classifications.items()) + list(cl.items()))
460 crossmatches.extend(cr)
462 for t in transientsMetadataList:
463 if t["id"] not in classifications:
464 classifications[t["id"]] = ["ORPHAN"]
466 # UPDATE THE TRANSIENT DATABASE IF UPDATE REQUESTED (ADD DATA TO
467 # tcs_crossmatch_table AND A CLASSIFICATION TO THE ORIGINAL TRANSIENT
468 # TABLE)
469 if self.verbose:
470 print("FINISH RANKING/START UPDATING TRANSIENT DB: %d" %
471 (time.time() - start_time2,))
472 start_time2 = time.time()
473 if self.update and not self.ra:
474 self._update_transient_database(
475 crossmatches=crossmatches,
476 classifications=classifications,
477 transientsMetadataList=transientsMetadataList,
478 colMaps=colMaps
479 )
481 if self.verbose:
482 print("FINISH UPDATING TRANSIENT DB/START ANNOTATING TRANSIENT DB: %d" %
483 (time.time() - start_time2,))
484 start_time2 = time.time()
486 # COMMAND-LINE SINGLE CLASSIFICATION
487 if self.ra:
488 classifications = self.update_classification_annotations_and_summaries(
489 False, True, crossmatches, classifications)
490 for k, v in classifications.items():
491 if len(v) == 1 and v[0] == "ORPHAN":
492 v.append(
493 "No contexual information is available for this transient")
495 if self.lite != False:
496 crossmatches = self._lighten_return(crossmatches)
498 if self.cl:
499 self._print_results_to_stdout(
500 classifications=classifications,
501 crossmatches=crossmatches
502 )
504 return classifications, crossmatches
506 if self.updatePeakMags and self.settings["database settings"]["transients"]["transient peak magnitude query"]:
507 self.update_peak_magnitudes()
509 # BULK RUN -- NOT A COMMAND-LINE SINGLE CLASSIFICATION
510 self.update_classification_annotations_and_summaries(
511 self.updatePeakMags)
513 print("FINISH ANNOTATING TRANSIENT DB: %d" %
514 (time.time() - start_time2,))
515 start_time2 = time.time()
517 classificationRate = old_div(count, (time.time() - start_time))
518 print(
519 "Sherlock is classify at a rate of %(classificationRate)2.1f transients/sec" % locals())
521 self.log.debug('completed the ``classify`` method')
522 return None, None
524 def _get_transient_metadata_from_database_list(
525 self):
526 """use the transient query in the settings file to generate a list of transients to corssmatch and classify
528 **Return**
531 - ``transientsMetadataList``
533 .. todo ::
535 - update key arguments values and definitions with defaults
536 - update return values and definitions
537 - update usage examples and text
538 - update docstring text
539 - check sublime snippet exists
540 - clip any useful text to docs mindmap
541 - regenerate the docs and check redendering of this docstring
542 """
543 self.log.debug(
544 'starting the ``_get_transient_metadata_from_database_list`` method')
546 sqlQuery = self.settings["database settings"][
547 "transients"]["transient query"] + " limit " + str(self.largeBatchSize)
549 thisInt = randint(0, 100)
550 if "where" in sqlQuery:
551 sqlQuery = sqlQuery.replace(
552 "where", "where %(thisInt)s=%(thisInt)s and " % locals())
554 transientsMetadataList = readquery(
555 log=self.log,
556 sqlQuery=sqlQuery,
557 dbConn=self.transientsDbConn,
558 quiet=False
559 )
561 self.log.debug(
562 'completed the ``_get_transient_metadata_from_database_list`` method')
563 return transientsMetadataList
565 def _update_ned_stream(
566 self,
567 transientsMetadataList
568 ):
569 """ update the NED stream within the catalogues database at the locations of the transients
571 **Key Arguments**
573 - ``transientsMetadataList`` -- the list of transient metadata lifted from the database.
576 .. todo ::
578 - update key arguments values and definitions with defaults
579 - update return values and definitions
580 - update usage examples and text
581 - update docstring text
582 - check sublime snippet exists
583 - clip any useful text to docs mindmap
584 - regenerate the docs and check redendering of this docstring
585 """
586 self.log.debug('starting the ``_update_ned_stream`` method')
588 coordinateList = []
589 for i in transientsMetadataList:
590 # thisList = str(i["ra"]) + " " + str(i["dec"])
591 thisList = (i["ra"], i["dec"])
592 coordinateList.append(thisList)
594 coordinateList = self._remove_previous_ned_queries(
595 coordinateList=coordinateList
596 )
598 # MINIMISE COORDINATES IN LIST TO REDUCE NUMBER OF REQUIRE NED QUERIES
599 coordinateList = self._consolidate_coordinateList(
600 coordinateList=coordinateList
601 )
603 stream = ned(
604 log=self.log,
605 settings=self.settings,
606 coordinateList=coordinateList,
607 radiusArcsec=self.settings["ned stream search radius arcec"]
608 )
609 stream.ingest()
611 sqlQuery = """SET session sql_mode = "";""" % locals(
612 )
613 writequery(
614 log=self.log,
615 sqlQuery=sqlQuery,
616 dbConn=self.cataloguesDbConn
617 )
618 sqlQuery = """update tcs_cat_ned_stream set magnitude = CAST(`magnitude_filter` AS DECIMAL(5,2)) where magnitude is null and magnitude_filter is not null;""" % locals(
619 )
620 writequery(
621 log=self.log,
622 sqlQuery=sqlQuery,
623 dbConn=self.cataloguesDbConn
624 )
626 self.log.debug('completed the ``_update_ned_stream`` method')
627 return None
629 def _remove_previous_ned_queries(
630 self,
631 coordinateList):
632 """iterate through the transient locations to see if we have recent local NED coverage of that area already
634 **Key Arguments**
636 - ``coordinateList`` -- set of coordinate to check for previous queries
639 **Return**
641 - ``updatedCoordinateList`` -- coordinate list with previous queries removed
644 .. todo ::
646 - update key arguments values and definitions with defaults
647 - update return values and definitions
648 - update usage examples and text
649 - update docstring text
650 - check sublime snippet exists
651 - clip any useful text to docs mindmap
652 - regenerate the docs and check redendering of this docstring
653 """
654 self.log.debug('starting the ``_remove_previous_ned_queries`` method')
656 # 1 DEGREE QUERY RADIUS
657 radius = 60. * 60.
658 updatedCoordinateList = []
659 keepers = []
661 # CALCULATE THE OLDEST RESULTS LIMIT
662 now = datetime.now()
663 td = timedelta(
664 days=self.settings["ned stream refresh rate in days"])
665 refreshLimit = now - td
666 refreshLimit = refreshLimit.strftime("%Y-%m-%d %H:%M:%S")
668 raList = []
669 raList[:] = [c[0] for c in coordinateList]
670 decList = []
671 decList[:] = [c[1] for c in coordinateList]
673 # MATCH COORDINATES AGAINST PREVIOUS NED SEARCHES
674 cs = conesearch(
675 log=self.log,
676 dbConn=self.cataloguesDbConn,
677 tableName="tcs_helper_ned_query_history",
678 columns="*",
679 ra=raList,
680 dec=decList,
681 radiusArcsec=radius,
682 separations=True,
683 distinct=True,
684 sqlWhere="dateQueried > '%(refreshLimit)s'" % locals(),
685 closest=False
686 )
687 matchIndies, matches = cs.search()
689 # DETERMINE WHICH COORDINATES REQUIRE A NED QUERY
690 curatedMatchIndices = []
691 curatedMatches = []
692 for i, m in zip(matchIndies, matches.list):
693 match = False
694 row = m
695 row["separationArcsec"] = row["cmSepArcsec"]
696 raStream = row["raDeg"]
697 decStream = row["decDeg"]
698 radiusStream = row["arcsecRadius"]
699 dateStream = row["dateQueried"]
700 angularSeparation = row["separationArcsec"]
702 if angularSeparation + self.settings["first pass ned search radius arcec"] < radiusStream:
703 curatedMatchIndices.append(i)
704 curatedMatches.append(m)
706 # NON MATCHES
707 for i, v in enumerate(coordinateList):
708 if i not in curatedMatchIndices:
709 updatedCoordinateList.append(v)
711 self.log.debug('completed the ``_remove_previous_ned_queries`` method')
712 return updatedCoordinateList
714 def _update_transient_database(
715 self,
716 crossmatches,
717 classifications,
718 transientsMetadataList,
719 colMaps):
720 """ update transient database with classifications and crossmatch results
722 **Key Arguments**
724 - ``crossmatches`` -- the crossmatches and associations resulting from the catlaogue crossmatches
725 - ``classifications`` -- the classifications assigned to the transients post-crossmatches (dictionary of rank ordered list of classifications)
726 - ``transientsMetadataList`` -- the list of transient metadata lifted from the database.
727 - ``colMaps`` -- maps of the important column names for each table/view in the crossmatch-catalogues database
730 .. todo ::
732 - update key arguments values and definitions with defaults
733 - update return values and definitions
734 - update usage examples and text
735 - update docstring text
736 - check sublime snippet exists
737 - clip any useful text to docs mindmap
738 - regenerate the docs and check redendering of this docstring
739 """
741 self.log.debug('starting the ``_update_transient_database`` method')
743 import time
744 start_time = time.time()
745 print("UPDATING TRANSIENTS DATABASE WITH RESULTS")
746 print("DELETING OLD RESULTS")
748 now = datetime.now()
749 now = now.strftime("%Y-%m-%d_%H-%M-%S-%f")
751 transientTable = self.settings["database settings"][
752 "transients"]["transient table"]
753 transientTableClassCol = self.settings["database settings"][
754 "transients"]["transient classification column"]
755 transientTableIdCol = self.settings["database settings"][
756 "transients"]["transient primary id column"]
758 # COMBINE ALL CROSSMATCHES INTO A LIST OF DICTIONARIES TO DUMP INTO
759 # DATABASE TABLE
760 transientIDs = [str(c)
761 for c in list(classifications.keys())]
762 transientIDs = ",".join(transientIDs)
764 # REMOVE PREVIOUS MATCHES
765 sqlQuery = """delete from sherlock_crossmatches where transient_object_id in (%(transientIDs)s);""" % locals(
766 )
767 writequery(
768 log=self.log,
769 sqlQuery=sqlQuery,
770 dbConn=self.transientsDbConn,
771 )
772 sqlQuery = """delete from sherlock_classifications where transient_object_id in (%(transientIDs)s);""" % locals(
773 )
774 writequery(
775 log=self.log,
776 sqlQuery=sqlQuery,
777 dbConn=self.transientsDbConn,
778 )
780 print("FINISHED DELETING OLD RESULTS/ADDING TO CROSSMATCHES: %d" %
781 (time.time() - start_time,))
782 start_time = time.time()
784 if len(crossmatches):
785 insert_list_of_dictionaries_into_database_tables(
786 dbConn=self.transientsDbConn,
787 log=self.log,
788 dictList=crossmatches,
789 dbTableName="sherlock_crossmatches",
790 dateModified=True,
791 batchSize=10000,
792 replace=True,
793 dbSettings=self.settings["database settings"][
794 "transients"]
795 )
797 print("FINISHED ADDING TO CROSSMATCHES/UPDATING CLASSIFICATIONS IN TRANSIENT TABLE: %d" %
798 (time.time() - start_time,))
799 start_time = time.time()
801 sqlQuery = ""
802 inserts = []
803 for k, v in list(classifications.items()):
804 thisInsert = {
805 "transient_object_id": k,
806 "classification": v[0]
807 }
808 inserts.append(thisInsert)
810 print("FINISHED UPDATING CLASSIFICATIONS IN TRANSIENT TABLE/UPDATING sherlock_classifications TABLE: %d" %
811 (time.time() - start_time,))
812 start_time = time.time()
814 insert_list_of_dictionaries_into_database_tables(
815 dbConn=self.transientsDbConn,
816 log=self.log,
817 dictList=inserts,
818 dbTableName="sherlock_classifications",
819 dateModified=True,
820 batchSize=10000,
821 replace=True,
822 dbSettings=self.settings["database settings"][
823 "transients"]
824 )
826 print("FINISHED UPDATING sherlock_classifications TABLE: %d" %
827 (time.time() - start_time,))
828 start_time = time.time()
830 self.log.debug('completed the ``_update_transient_database`` method')
831 return None
833 def _rank_classifications(
834 self,
835 crossmatchArray,
836 colMaps):
837 """*rank the classifications returned from the catalogue crossmatcher, annotate the results with a classification rank-number (most likely = 1) and a rank-score (weight of classification)*
839 **Key Arguments**
841 - ``crossmatchArrayIndex`` -- the index of list of unranked crossmatch classifications
842 - ``colMaps`` -- dictionary of dictionaries with the name of the database-view (e.g. `tcs_view_agn_milliquas_v4_5`) as the key and the column-name dictary map as value (`{view_name: {columnMap}}`).
845 **Return**
847 - ``classifications`` -- the classifications assigned to the transients post-crossmatches
848 - ``crossmatches`` -- the crossmatches annotated with rankings and rank-scores
851 .. todo ::
853 - update key arguments values and definitions with defaults
854 - update return values and definitions
855 - update usage examples and text
856 - update docstring text
857 - check sublime snippet exists
858 - clip any useful text to docs mindmap
859 - regenerate the docs and check redendering of this docstring
860 """
861 self.log.debug('starting the ``_rank_classifications`` method')
863 crossmatches = crossmatchArray
865 # GROUP CROSSMATCHES INTO DISTINCT SOURCES (DUPLICATE ENTRIES OF THE
866 # SAME ASTROPHYSICAL SOURCE ACROSS MULTIPLE CATALOGUES)
867 ra, dec = list(zip(*[(r["raDeg"], r["decDeg"]) for r in crossmatches]))
869 from HMpTy.htm import sets
870 xmatcher = sets(
871 log=self.log,
872 ra=ra,
873 dec=dec,
874 radius=1. / (60. * 60.), # in degrees
875 sourceList=crossmatches
876 )
877 groupedMatches = xmatcher.match
879 associatationTypeOrder = ["AGN", "CV", "NT", "SN", "VS", "BS"]
881 # ADD DISTINCT-SOURCE KEY
882 dupKey = 0
883 distinctMatches = []
884 for x in groupedMatches:
885 dupKey += 1
886 mergedMatch = copy.deepcopy(x[0])
887 mergedMatch["merged_rank"] = int(dupKey)
889 # ADD OTHER ESSENTIAL KEYS
890 for e in ['z', 'photoZ', 'photoZErr']:
891 if e not in mergedMatch:
892 mergedMatch[e] = None
893 bestQualityCatalogue = colMaps[mergedMatch[
894 "catalogue_view_name"]]["object_type_accuracy"]
895 bestDirectDistance = {
896 "direct_distance": mergedMatch["direct_distance"],
897 "direct_distance_modulus": mergedMatch["direct_distance_modulus"],
898 "direct_distance_scale": mergedMatch["direct_distance_scale"],
899 "qual": colMaps[mergedMatch["catalogue_view_name"]]["object_type_accuracy"]
900 }
901 if not mergedMatch["direct_distance"]:
902 bestDirectDistance["qual"] = 0
904 bestSpecz = {
905 "z": mergedMatch["z"],
906 "distance": mergedMatch["distance"],
907 "distance_modulus": mergedMatch["distance_modulus"],
908 "scale": mergedMatch["scale"],
909 "qual": colMaps[mergedMatch["catalogue_view_name"]]["object_type_accuracy"]
910 }
911 if not mergedMatch["distance"]:
912 bestSpecz["qual"] = 0
914 bestPhotoz = {
915 "photoZ": mergedMatch["photoZ"],
916 "photoZErr": mergedMatch["photoZErr"],
917 "qual": colMaps[mergedMatch["catalogue_view_name"]]["object_type_accuracy"]
918 }
919 if not mergedMatch["photoZ"]:
920 bestPhotoz["qual"] = 0
922 # ORDER THESE FIRST IN NAME LISTING
923 mergedMatch["search_name"] = None
924 mergedMatch["catalogue_object_id"] = None
925 primeCats = ["NED", "SDSS", "MILLIQUAS"]
926 for cat in primeCats:
927 for i, m in enumerate(x):
928 # MERGE SEARCH NAMES
929 snippet = m["search_name"].split(" ")[0].upper()
930 if cat.upper() in snippet:
931 if not mergedMatch["search_name"]:
932 mergedMatch["search_name"] = m["search_name"].split(" ")[
933 0].upper()
934 elif "/" not in mergedMatch["search_name"] and snippet not in mergedMatch["search_name"].upper():
935 mergedMatch["search_name"] = mergedMatch["search_name"].split(
936 " ")[0].upper() + "/" + m["search_name"].split(" ")[0].upper()
937 elif snippet not in mergedMatch["search_name"].upper():
938 mergedMatch[
939 "search_name"] += "/" + m["search_name"].split(" ")[0].upper()
940 elif "/" not in mergedMatch["search_name"]:
941 mergedMatch["search_name"] = mergedMatch["search_name"].split(
942 " ")[0].upper()
943 mergedMatch["catalogue_table_name"] = mergedMatch[
944 "search_name"]
946 # MERGE CATALOGUE SOURCE NAMES
947 if not mergedMatch["catalogue_object_id"]:
948 mergedMatch["catalogue_object_id"] = str(
949 m["catalogue_object_id"])
951 # NOW ADD THE REST
952 for i, m in enumerate(x):
953 # MERGE SEARCH NAMES
954 snippet = m["search_name"].split(" ")[0].upper()
955 if snippet not in primeCats:
956 if not mergedMatch["search_name"]:
957 mergedMatch["search_name"] = m["search_name"].split(" ")[
958 0].upper()
959 elif "/" not in mergedMatch["search_name"] and snippet not in mergedMatch["search_name"].upper():
960 mergedMatch["search_name"] = mergedMatch["search_name"].split(
961 " ")[0].upper() + "/" + m["search_name"].split(" ")[0].upper()
962 elif snippet not in mergedMatch["search_name"].upper():
963 mergedMatch[
964 "search_name"] += "/" + m["search_name"].split(" ")[0].upper()
965 elif "/" not in mergedMatch["search_name"]:
966 mergedMatch["search_name"] = mergedMatch["search_name"].split(
967 " ")[0].upper()
968 mergedMatch["catalogue_table_name"] = mergedMatch[
969 "search_name"]
971 # MERGE CATALOGUE SOURCE NAMES
972 if not mergedMatch["catalogue_object_id"]:
973 mergedMatch["catalogue_object_id"] = str(
974 m["catalogue_object_id"])
975 # else:
976 # mergedMatch["catalogue_object_id"] = str(
977 # mergedMatch["catalogue_object_id"])
978 # m["catalogue_object_id"] = str(
979 # m["catalogue_object_id"])
980 # if m["catalogue_object_id"].replace(" ", "").lower() not in mergedMatch["catalogue_object_id"].replace(" ", "").lower():
981 # mergedMatch["catalogue_object_id"] += "/" + \
982 # m["catalogue_object_id"]
984 for i, m in enumerate(x):
985 m["merged_rank"] = int(dupKey)
986 if i > 0:
987 # MERGE ALL BEST MAGNITUDE MEASUREMENTS
988 for f in self.filterPreference:
990 if f in m and m[f] and (f not in mergedMatch or (f + "Err" in mergedMatch and f + "Err" in m and (mergedMatch[f + "Err"] == None or (m[f + "Err"] and mergedMatch[f + "Err"] > m[f + "Err"])))):
991 mergedMatch[f] = m[f]
992 try:
993 mergedMatch[f + "Err"] = m[f + "Err"]
994 except:
995 pass
996 mergedMatch["original_search_radius_arcsec"] = "multiple"
997 mergedMatch["catalogue_object_subtype"] = "multiple"
998 mergedMatch["catalogue_view_name"] = "multiple"
1000 # DETERMINE BEST CLASSIFICATION
1001 if mergedMatch["classificationReliability"] == 3 and m["classificationReliability"] < 3:
1002 mergedMatch["association_type"] = m["association_type"]
1003 mergedMatch["catalogue_object_type"] = m[
1004 "catalogue_object_type"]
1005 mergedMatch["classificationReliability"] = m[
1006 "classificationReliability"]
1008 if m["classificationReliability"] != 3 and colMaps[m["catalogue_view_name"]]["object_type_accuracy"] > bestQualityCatalogue:
1009 bestQualityCatalogue = colMaps[
1010 m["catalogue_view_name"]]["object_type_accuracy"]
1011 mergedMatch["association_type"] = m["association_type"]
1012 mergedMatch["catalogue_object_type"] = m[
1013 "catalogue_object_type"]
1014 mergedMatch["classificationReliability"] = m[
1015 "classificationReliability"]
1017 if m["classificationReliability"] != 3 and colMaps[m["catalogue_view_name"]]["object_type_accuracy"] == bestQualityCatalogue and m["association_type"] in associatationTypeOrder and (mergedMatch["association_type"] not in associatationTypeOrder or associatationTypeOrder.index(m["association_type"]) < associatationTypeOrder.index(mergedMatch["association_type"])):
1018 mergedMatch["association_type"] = m["association_type"]
1019 mergedMatch["catalogue_object_type"] = m[
1020 "catalogue_object_type"]
1021 mergedMatch["classificationReliability"] = m[
1022 "classificationReliability"]
1024 # FIND BEST DISTANCES
1025 if "direct_distance" in m and m["direct_distance"] and colMaps[m["catalogue_view_name"]]["object_type_accuracy"] > bestDirectDistance["qual"]:
1026 bestDirectDistance = {
1027 "direct_distance": m["direct_distance"],
1028 "direct_distance_modulus": m["direct_distance_modulus"],
1029 "direct_distance_scale": m["direct_distance_scale"],
1030 "catalogue_object_type": m["catalogue_object_type"],
1031 "qual": colMaps[m["catalogue_view_name"]]["object_type_accuracy"]
1032 }
1033 # FIND BEST SPEC-Z
1034 if "z" in m and m["z"] and colMaps[m["catalogue_view_name"]]["object_type_accuracy"] > bestSpecz["qual"]:
1035 bestSpecz = {
1036 "z": m["z"],
1037 "distance": m["distance"],
1038 "distance_modulus": m["distance_modulus"],
1039 "scale": m["scale"],
1040 "catalogue_object_type": m["catalogue_object_type"],
1041 "qual": colMaps[m["catalogue_view_name"]]["object_type_accuracy"]
1042 }
1043 # FIND BEST PHOT-Z
1044 if "photoZ" in m and m["photoZ"] and colMaps[m["catalogue_view_name"]]["object_type_accuracy"] > bestPhotoz["qual"]:
1045 bestPhotoz = {
1046 "photoZ": m["photoZ"],
1047 "photoZErr": m["photoZErr"],
1048 "distance": m["distance"],
1049 "distance_modulus": m["distance_modulus"],
1050 "scale": m["scale"],
1051 "catalogue_object_type": m["catalogue_object_type"],
1052 "qual": colMaps[m["catalogue_view_name"]]["object_type_accuracy"]
1053 }
1054 # CLOSEST ANGULAR SEP & COORDINATES
1055 if m["separationArcsec"] < mergedMatch["separationArcsec"]:
1056 mergedMatch["separationArcsec"] = m["separationArcsec"]
1057 mergedMatch["raDeg"] = m["raDeg"]
1058 mergedMatch["decDeg"] = m["decDeg"]
1060 # MERGE THE BEST RESULTS
1061 for l in [bestPhotoz, bestSpecz, bestDirectDistance]:
1062 for k, v in list(l.items()):
1063 if k != "qual" and v:
1064 mergedMatch[k] = v
1066 mergedMatch["catalogue_object_id"] = str(mergedMatch[
1067 "catalogue_object_id"]).replace(" ", "")
1069 # RECALULATE PHYSICAL DISTANCE SEPARATION
1070 if mergedMatch["direct_distance_scale"]:
1071 mergedMatch["physical_separation_kpc"] = mergedMatch[
1072 "direct_distance_scale"] * mergedMatch["separationArcsec"]
1074 elif mergedMatch["scale"]:
1075 mergedMatch["physical_separation_kpc"] = mergedMatch[
1076 "scale"] * mergedMatch["separationArcsec"]
1078 if "/" in mergedMatch["search_name"]:
1079 mergedMatch["search_name"] = "multiple"
1081 distinctMatches.append(mergedMatch)
1083 crossmatches = []
1084 for xm, gm in zip(distinctMatches, groupedMatches):
1085 # SPEC-Z GALAXIES
1086 if (xm["physical_separation_kpc"] is not None and xm["physical_separation_kpc"] != "null" and xm["physical_separation_kpc"] < 20. and (("z" in xm and xm["z"] is not None) or "photoZ" not in xm or xm["photoZ"] is None or xm["photoZ"] < 0.)):
1087 rankScore = xm["classificationReliability"] * 1000 + 2. - \
1088 (50 - old_div(xm["physical_separation_kpc"], 20))
1089 # PHOTO-Z GALAXIES
1090 elif (xm["physical_separation_kpc"] is not None and xm["physical_separation_kpc"] != "null" and xm["physical_separation_kpc"] < 20. and xm["association_type"] == "SN"):
1091 rankScore = xm["classificationReliability"] * 1000 + 5 - \
1092 (50 - old_div(xm["physical_separation_kpc"], 20))
1093 # NOT SPEC-Z, NON PHOTO-Z GALAXIES & PHOTO-Z GALAXIES
1094 elif (xm["association_type"] == "SN"):
1095 rankScore = xm["classificationReliability"] * 1000 + 5.
1096 # VS
1097 elif (xm["association_type"] == "VS"):
1098 rankScore = xm["classificationReliability"] * \
1099 1000 + xm["separationArcsec"] + 2.
1100 # BS
1101 elif (xm["association_type"] == "BS"):
1102 rankScore = xm["classificationReliability"] * \
1103 1000 + xm["separationArcsec"]
1104 else:
1105 rankScore = xm["classificationReliability"] * \
1106 1000 + xm["separationArcsec"] + 10.
1107 xm["rankScore"] = rankScore
1108 crossmatches.append(xm)
1109 if len(gm) > 1:
1110 for g in gm:
1111 g["rankScore"] = rankScore
1113 crossmatches = sorted(
1114 crossmatches, key=itemgetter('rankScore'), reverse=False)
1115 crossmatches = sorted(
1116 crossmatches, key=itemgetter('transient_object_id'))
1118 transient_object_id = None
1119 uniqueIndexCheck = []
1120 classifications = {}
1121 crossmatchesKeep = []
1122 rank = 0
1123 transClass = []
1124 for xm in crossmatches:
1125 rank += 1
1126 if rank == 1:
1127 transClass.append(xm["association_type"])
1128 classifications[xm["transient_object_id"]] = transClass
1129 if rank == 1 or self.lite == False:
1130 xm["rank"] = rank
1131 crossmatchesKeep.append(xm)
1132 crossmatches = crossmatchesKeep
1134 crossmatchesKeep = []
1135 if self.lite == False:
1136 for xm in crossmatches:
1137 group = groupedMatches[xm["merged_rank"] - 1]
1138 xm["merged_rank"] = None
1139 crossmatchesKeep.append(xm)
1141 if len(group) > 1:
1142 groupKeep = []
1143 uniqueIndexCheck = []
1144 for g in group:
1145 g["merged_rank"] = xm["rank"]
1146 g["rankScore"] = xm["rankScore"]
1147 index = "%(catalogue_table_name)s%(catalogue_object_id)s" % g
1148 # IF WE HAVE HIT A NEW SOURCE
1149 if index not in uniqueIndexCheck:
1150 uniqueIndexCheck.append(index)
1151 crossmatchesKeep.append(g)
1153 crossmatches = crossmatchesKeep
1155 self.log.debug('completed the ``_rank_classifications`` method')
1157 return classifications, crossmatches
1159 def _print_results_to_stdout(
1160 self,
1161 classifications,
1162 crossmatches):
1163 """*print the classification and crossmatch results for a single transient object to stdout*
1165 **Key Arguments**
1167 - ``crossmatches`` -- the unranked crossmatch classifications
1168 - ``classifications`` -- the classifications assigned to the transients post-crossmatches (dictionary of rank ordered list of classifications)
1171 .. todo ::
1173 - update key arguments values and definitions with defaults
1174 - update return values and definitions
1175 - update usage examples and text
1176 - update docstring text
1177 - check sublime snippet exists
1178 - clip any useful text to docs mindmap
1179 - regenerate the docs and check redendering of this docstring
1180 """
1181 self.log.debug('starting the ``_print_results_to_stdout`` method')
1183 if self.verbose == 0:
1184 return
1186 crossmatchesCopy = copy.deepcopy(crossmatches)
1188 # REPORT ONLY THE MOST PREFERED MAGNITUDE VALUE
1189 basic = ["association_type", "rank", "rankScore", "catalogue_table_name", "catalogue_object_id", "catalogue_object_type", "catalogue_object_subtype",
1190 "raDeg", "decDeg", "separationArcsec", "physical_separation_kpc", "direct_distance", "distance", "z", "photoZ", "photoZErr", "Mag", "MagFilter", "MagErr", "classificationReliability", "merged_rank"]
1191 verbose = ["search_name", "catalogue_view_name", "original_search_radius_arcsec", "direct_distance_modulus", "distance_modulus", "direct_distance_scale", "major_axis_arcsec", "scale", "U", "UErr",
1192 "B", "BErr", "V", "VErr", "R", "RErr", "I", "IErr", "J", "JErr", "H", "HErr", "K", "KErr", "_u", "_uErr", "_g", "_gErr", "_r", "_rErr", "_i", "_iErr", "_z", "_zErr", "_y", "G", "GErr", "_yErr", "unkMag"]
1193 dontFormat = ["decDeg", "raDeg", "rank",
1194 "catalogue_object_id", "catalogue_object_subtype", "merged_rank"]
1195 if self.verbose == 2:
1196 basic = basic + verbose
1198 for n in self.name:
1200 if n in classifications:
1201 headline = "\n" + n + "'s Predicted Classification: " + \
1202 classifications[n][0]
1203 else:
1204 headline = n + "'s Predicted Classification: ORPHAN"
1205 print(headline)
1206 print("Suggested Associations:")
1208 myCrossmatches = []
1209 myCrossmatches[:] = [c for c in crossmatchesCopy if c[
1210 "transient_object_id"] == n]
1212 for c in myCrossmatches:
1213 for f in self.filterPreference:
1214 if f in c and c[f]:
1215 c["Mag"] = c[f]
1216 c["MagFilter"] = f.replace("_", "").replace("Mag", "")
1217 if f + "Err" in c:
1218 c["MagErr"] = c[f + "Err"]
1219 else:
1220 c["MagErr"] = None
1221 break
1223 allKeys = []
1224 for c in myCrossmatches:
1225 for k, v in list(c.items()):
1226 if k not in allKeys:
1227 allKeys.append(k)
1229 for c in myCrossmatches:
1230 for k in allKeys:
1231 if k not in c:
1232 c[k] = None
1234 printCrossmatches = []
1235 for c in myCrossmatches:
1236 ordDict = collections.OrderedDict(sorted({}.items()))
1237 for k in basic:
1238 if k in c:
1239 if k == "catalogue_table_name":
1240 c[k] = c[k].replace(
1241 "tcs_cat_", "").replace("_", " ")
1242 if k == "classificationReliability":
1243 if c[k] == 1:
1244 c["classification reliability"] = "synonym"
1245 elif c[k] == 2:
1246 c["classification reliability"] = "association"
1247 elif c[k] == 3:
1248 c["classification reliability"] = "annotation"
1249 k = "classification reliability"
1250 if k == "catalogue_object_subtype" and "sdss" in c["catalogue_table_name"]:
1251 if c[k] == 6:
1252 c[k] = "galaxy"
1253 elif c[k] == 3:
1254 c[k] = "star"
1255 columnName = k.replace(
1256 "tcs_cat_", "").replace("_", " ")
1257 value = c[k]
1258 if k not in dontFormat:
1259 try:
1260 ordDict[columnName] = "%(value)0.2f" % locals()
1261 except:
1262 ordDict[columnName] = value
1263 else:
1264 ordDict[columnName] = value
1266 printCrossmatches.append(ordDict)
1268 from fundamentals.renderer import list_of_dictionaries
1269 dataSet = list_of_dictionaries(
1270 log=self.log,
1271 listOfDictionaries=printCrossmatches
1272 )
1273 tableData = dataSet.table(filepath=None)
1275 print(tableData)
1277 self.log.debug('completed the ``_print_results_to_stdout`` method')
1278 return None
1280 def _lighten_return(
1281 self,
1282 crossmatches):
1283 """*lighten the classification and crossmatch results for smaller database footprint*
1285 **Key Arguments**
1287 - ``classifications`` -- the classifications assigned to the transients post-crossmatches (dictionary of rank ordered list of classifications)
1288 """
1289 self.log.debug('starting the ``_lighten_return`` method')
1291 # REPORT ONLY THE MOST PREFERED MAGNITUDE VALUE
1292 basic = ["transient_object_id", "association_type", "catalogue_table_name", "catalogue_object_id", "catalogue_object_type",
1293 "raDeg", "decDeg", "separationArcsec", "northSeparationArcsec", "eastSeparationArcsec", "physical_separation_kpc", "direct_distance", "distance", "z", "photoZ", "photoZErr", "Mag", "MagFilter", "MagErr", "classificationReliability", "major_axis_arcsec"]
1294 verbose = ["search_name", "catalogue_view_name", "original_search_radius_arcsec", "direct_distance_modulus", "distance_modulus", "direct_distance_scale", "scale", "U", "UErr",
1295 "B", "BErr", "V", "VErr", "R", "RErr", "I", "IErr", "J", "JErr", "H", "HErr", "K", "KErr", "_u", "_uErr", "_g", "_gErr", "_r", "_rErr", "_i", "_iErr", "_z", "_zErr", "_y", "G", "GErr", "_yErr", "unkMag"]
1296 dontFormat = ["decDeg", "raDeg", "rank",
1297 "catalogue_object_id", "catalogue_object_subtype", "merged_rank", "classificationReliability"]
1298 if self.verbose == 2:
1299 basic = basic + verbose
1301 for c in crossmatches:
1302 for f in self.filterPreference:
1303 if f in c and c[f]:
1304 c["Mag"] = c[f]
1305 c["MagFilter"] = f.replace("_", "").replace("Mag", "")
1306 if f + "Err" in c:
1307 c["MagErr"] = c[f + "Err"]
1308 else:
1309 c["MagErr"] = None
1310 break
1312 allKeys = []
1313 for c in crossmatches:
1314 for k, v in list(c.items()):
1315 if k not in allKeys:
1316 allKeys.append(k)
1318 for c in crossmatches:
1319 for k in allKeys:
1320 if k not in c:
1321 c[k] = None
1323 liteCrossmatches = []
1324 for c in crossmatches:
1325 ordDict = collections.OrderedDict(sorted({}.items()))
1326 for k in basic:
1327 if k in c:
1328 if k == "catalogue_table_name":
1329 c[k] = c[k].replace(
1330 "tcs_cat_", "").replace("_", " ")
1331 if k == "catalogue_object_subtype" and "sdss" in c["catalogue_table_name"]:
1332 if c[k] == 6:
1333 c[k] = "galaxy"
1334 elif c[k] == 3:
1335 c[k] = "star"
1336 columnName = k.replace(
1337 "tcs_cat_", "")
1338 value = c[k]
1339 if k not in dontFormat:
1340 try:
1341 ordDict[columnName] = float(f'{value:0.2f}')
1342 except:
1343 ordDict[columnName] = value
1344 else:
1345 ordDict[columnName] = value
1347 liteCrossmatches.append(ordDict)
1349 self.log.debug('completed the ``_lighten_return`` method')
1350 return liteCrossmatches
1352 def _consolidate_coordinateList(
1353 self,
1354 coordinateList):
1355 """*match the coordinate list against itself with the parameters of the NED search queries to minimise duplicated NED queries*
1357 **Key Arguments**
1359 - ``coordinateList`` -- the original coordinateList.
1362 **Return**
1364 - ``updatedCoordinateList`` -- the coordinate list with duplicated search areas removed
1367 **Usage**
1369 .. todo::
1371 - add usage info
1372 - create a sublime snippet for usage
1373 - update package tutorial if needed
1375 ```python
1376 usage code
1377 ```
1380 .. todo ::
1382 - update key arguments values and definitions with defaults
1383 - update return values and definitions
1384 - update usage examples and text
1385 - update docstring text
1386 - check sublime snippet exists
1387 - clip any useful text to docs mindmap
1388 - regenerate the docs and check redendering of this docstring
1389 """
1390 self.log.debug('starting the ``_consolidate_coordinateList`` method')
1392 raList = []
1393 raList[:] = np.array([c[0] for c in coordinateList])
1394 decList = []
1395 decList[:] = np.array([c[1] for c in coordinateList])
1397 nedStreamRadius = old_div(self.settings[
1398 "ned stream search radius arcec"], (60. * 60.))
1399 firstPassNedSearchRadius = old_div(self.settings[
1400 "first pass ned search radius arcec"], (60. * 60.))
1401 radius = nedStreamRadius - firstPassNedSearchRadius
1403 # LET'S BE CONSERVATIVE
1404 # radius = radius * 0.9
1406 xmatcher = sets(
1407 log=self.log,
1408 ra=raList,
1409 dec=decList,
1410 radius=radius, # in degrees
1411 sourceList=coordinateList,
1412 convertToArray=False
1413 )
1414 allMatches = xmatcher.match
1416 updatedCoordianteList = []
1417 for aSet in allMatches:
1418 updatedCoordianteList.append(aSet[0])
1420 self.log.debug('completed the ``_consolidate_coordinateList`` method')
1421 return updatedCoordianteList
1423 def classification_annotations(
1424 self):
1425 """*add a detialed classification annotation to each classification in the sherlock_classifications table*
1427 **Key Arguments**
1429 # -
1432 **Return**
1434 - None
1437 **Usage**
1439 .. todo::
1441 - add usage info
1442 - create a sublime snippet for usage
1443 - write a command-line tool for this method
1444 - update package tutorial with command-line tool info if needed
1446 ```python
1447 usage code
1448 ```
1451 .. todo ::
1453 - update key arguments values and definitions with defaults
1454 - update return values and definitions
1455 - update usage examples and text
1456 - update docstring text
1457 - check sublime snippet exists
1458 - clip any useful text to docs mindmap
1459 - regenerate the docs and check redendering of this docstring
1460 """
1461 self.log.debug('starting the ``classification_annotations`` method')
1463 from fundamentals.mysql import readquery
1464 sqlQuery = u"""
1465 select * from sherlock_classifications cl, sherlock_crossmatches xm where cl.transient_object_id=xm.transient_object_id and cl.annotation is null
1466 """ % locals()
1467 topXMs = readquery(
1468 log=self.log,
1469 sqlQuery=sqlQuery,
1470 dbConn=self.transientsDbConn
1471 )
1473 for xm in topXMs:
1474 annotation = []
1475 classType = xm["classificationReliability"]
1476 if classType == 1:
1477 annotation.append("is synonymous with")
1478 elif classType in [2, 3]:
1479 annotation.append("is possibly associated with")
1481 self.log.debug('completed the ``classification_annotations`` method')
1482 return None
1484 def update_classification_annotations_and_summaries(
1485 self,
1486 updatePeakMagnitudes=True,
1487 cl=False,
1488 crossmatches=False,
1489 classifications=False):
1490 """*update classification annotations and summaries*
1492 **Key Arguments**
1494 - ``updatePeakMagnitudes`` -- update the peak magnitudes in the annotations to give absolute magnitudes. Default *True*
1495 - ``cl`` -- reporting only to the command-line, do not update database. Default *False*
1496 - ``crossmatches`` -- crossmatches will be passed for the single classifications to report annotations from command-line
1497 - ``classifications`` -- classifications will be passed for the single classifications to have annotation appended to the dictionary for stand-alone non-database scripts
1499 **Return**
1501 - None
1504 **Usage**
1506 .. todo::
1508 - add usage info
1509 - create a sublime snippet for usage
1510 - write a command-line tool for this method
1511 - update package tutorial with command-line tool info if needed
1513 ```python
1514 usage code
1515 ```
1518 .. todo ::
1520 - update key arguments values and definitions with defaults
1521 - update return values and definitions
1522 - update usage examples and text
1523 - update docstring text
1524 - check sublime snippet exists
1525 - clip any useful text to docs mindmap
1526 - regenerate the docs and check redendering of this docstring
1527 """
1528 self.log.debug(
1529 'starting the ``update_classification_annotations_and_summaries`` method')
1531 # import time
1532 # start_time = time.time()
1533 # print "COLLECTING TRANSIENTS WITH NO ANNOTATIONS"
1535 # BULK RUN
1536 if crossmatches == False:
1537 if updatePeakMagnitudes:
1538 sqlQuery = u"""
1539 SELECT * from sherlock_crossmatches cm, sherlock_classifications cl where rank =1 and cl.transient_object_id= cm.transient_object_id and ((cl.classification not in ("AGN","CV","BS","VS") AND cm.dateLastModified > DATE_SUB(NOW(), INTERVAL 1 Day)) or cl.annotation is null)
1540 -- SELECT * from sherlock_crossmatches cm, sherlock_classifications cl where rank =1 and cl.transient_object_id= cm.transient_object_id and (cl.annotation is null or cl.dateLastModified is null or cl.dateLastModified > DATE_SUB(NOW(), INTERVAL 30 DAY)) order by cl.dateLastModified asc limit 100000
1541 """ % locals()
1542 else:
1543 sqlQuery = u"""
1544 SELECT * from sherlock_crossmatches cm, sherlock_classifications cl where rank =1 and cl.transient_object_id=cm.transient_object_id and cl.summary is null
1545 """ % locals()
1547 rows = readquery(
1548 log=self.log,
1549 sqlQuery=sqlQuery,
1550 dbConn=self.transientsDbConn
1551 )
1552 # COMMAND-LINE SINGLE CLASSIFICATION
1553 else:
1554 rows = crossmatches
1556 # print "FINISHED COLLECTING TRANSIENTS WITH NO ANNOTATIONS/GENERATING ANNOTATIONS: %d" % (time.time() - start_time,)
1557 # start_time = time.time()
1559 updates = []
1561 for row in rows:
1562 annotation, summary, sep = self.generate_match_annotation(
1563 match=row, updatePeakMagnitudes=updatePeakMagnitudes)
1565 if cl and "rank" in row and row["rank"] == 1:
1566 if classifications != False:
1567 classifications[
1568 row["transient_object_id"]].append(annotation)
1569 if self.verbose != 0:
1570 print(annotation)
1572 update = {
1573 "transient_object_id": row["transient_object_id"],
1574 "annotation": annotation,
1575 "summary": summary,
1576 "separationArcsec": sep
1577 }
1578 updates.append(update)
1580 if cl:
1581 return classifications
1583 # print "FINISHED GENERATING ANNOTATIONS/ADDING ANNOTATIONS TO TRANSIENT DATABASE: %d" % (time.time() - start_time,)
1584 # start_time = time.time()
1586 insert_list_of_dictionaries_into_database_tables(
1587 dbConn=self.transientsDbConn,
1588 log=self.log,
1589 dictList=updates,
1590 dbTableName="sherlock_classifications",
1591 dateModified=True,
1592 batchSize=10000,
1593 replace=True,
1594 dbSettings=self.settings["database settings"]["transients"]
1595 )
1597 # print "FINISHED ADDING ANNOTATIONS TO TRANSIENT DATABASE/UPDATING ORPHAN ANNOTATIONS: %d" % (time.time() - start_time,)
1598 # start_time = time.time()
1600 sqlQuery = """update sherlock_classifications set annotation = "The transient location is not matched against any known catalogued source", summary = "No catalogued match" where classification = 'ORPHAN' and summary is null """ % locals()
1601 writequery(
1602 log=self.log,
1603 sqlQuery=sqlQuery,
1604 dbConn=self.transientsDbConn,
1605 )
1607 # print "FINISHED UPDATING ORPHAN ANNOTATIONS: %d" % (time.time() - start_time,)
1608 # start_time = time.time()
1610 self.log.debug(
1611 'completed the ``update_classification_annotations_and_summaries`` method')
1612 return None
1614 # use the tab-trigger below for new method
1615 def update_peak_magnitudes(
1616 self):
1617 """*update peak magnitudes*
1619 **Key Arguments**
1621 # -
1624 **Return**
1626 - None
1629 **Usage**
1631 .. todo::
1633 - add usage info
1634 - create a sublime snippet for usage
1635 - write a command-line tool for this method
1636 - update package tutorial with command-line tool info if needed
1638 ```python
1639 usage code
1640 ```
1643 .. todo ::
1645 - update key arguments values and definitions with defaults
1646 - update return values and definitions
1647 - update usage examples and text
1648 - update docstring text
1649 - check sublime snippet exists
1650 - clip any useful text to docs mindmap
1651 - regenerate the docs and check redendering of this docstring
1652 """
1653 self.log.debug('starting the ``update_peak_magnitudes`` method')
1655 sqlQuery = self.settings["database settings"][
1656 "transients"]["transient peak magnitude query"]
1658 sqlQuery = """UPDATE sherlock_crossmatches s,
1659 (%(sqlQuery)s) t
1660 SET
1661 s.transientAbsMag = ROUND(t.mag - IFNULL(direct_distance_modulus,
1662 distance_modulus),
1663 2)
1664 WHERE
1665 IFNULL(direct_distance_modulus,
1666 distance_modulus) IS NOT NULL
1667 AND (s.association_type not in ("AGN","CV","BS","VS")
1668 or s.transientAbsMag is null)
1669 AND t.id = s.transient_object_id
1670 AND (s.dateLastModified > DATE_SUB(NOW(), INTERVAL 1 DAY));""" % locals()
1672 writequery(
1673 log=self.log,
1674 sqlQuery=sqlQuery,
1675 dbConn=self.transientsDbConn,
1676 )
1678 self.log.debug('completed the ``update_peak_magnitudes`` method')
1679 return None
1681 def _create_tables_if_not_exist(
1682 self):
1683 """*create the sherlock helper tables if they don't yet exist*
1685 **Key Arguments**
1687 # -
1690 **Return**
1692 - None
1695 **Usage**
1697 .. todo::
1699 - add usage info
1700 - create a sublime snippet for usage
1701 - write a command-line tool for this method
1702 - update package tutorial with command-line tool info if needed
1704 ```python
1705 usage code
1706 ```
1709 .. todo ::
1711 - update key arguments values and definitions with defaults
1712 - update return values and definitions
1713 - update usage examples and text
1714 - update docstring text
1715 - check sublime snippet exists
1716 - clip any useful text to docs mindmap
1717 - regenerate the docs and check redendering of this docstring
1718 """
1719 self.log.debug('starting the ``_create_tables_if_not_exist`` method')
1721 transientTable = self.settings["database settings"][
1722 "transients"]["transient table"]
1723 transientTableClassCol = self.settings["database settings"][
1724 "transients"]["transient classification column"]
1725 transientTableIdCol = self.settings["database settings"][
1726 "transients"]["transient primary id column"]
1728 crossmatchTable = "sherlock_crossmatches"
1729 createStatement = """
1730CREATE TABLE IF NOT EXISTS `sherlock_crossmatches` (
1731 `transient_object_id` bigint(20) unsigned DEFAULT NULL,
1732 `catalogue_object_id` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL,
1733 `catalogue_table_id` smallint(5) unsigned DEFAULT NULL,
1734 `separationArcsec` double DEFAULT NULL,
1735 `northSeparationArcsec` double DEFAULT NULL,
1736 `eastSeparationArcsec` double DEFAULT NULL,
1737 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
1738 `z` double DEFAULT NULL,
1739 `scale` double DEFAULT NULL,
1740 `distance` double DEFAULT NULL,
1741 `distance_modulus` double DEFAULT NULL,
1742 `photoZ` double DEFAULT NULL,
1743 `photoZErr` double DEFAULT NULL,
1744 `association_type` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
1745 `dateCreated` datetime DEFAULT NULL,
1746 `physical_separation_kpc` double DEFAULT NULL,
1747 `catalogue_object_type` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
1748 `catalogue_object_subtype` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
1749 `association_rank` int(11) DEFAULT NULL,
1750 `catalogue_table_name` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
1751 `catalogue_view_name` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
1752 `rank` int(11) DEFAULT NULL,
1753 `rankScore` double DEFAULT NULL,
1754 `search_name` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
1755 `major_axis_arcsec` double DEFAULT NULL,
1756 `direct_distance` double DEFAULT NULL,
1757 `direct_distance_scale` double DEFAULT NULL,
1758 `direct_distance_modulus` double DEFAULT NULL,
1759 `raDeg` double DEFAULT NULL,
1760 `decDeg` double DEFAULT NULL,
1761 `original_search_radius_arcsec` double DEFAULT NULL,
1762 `catalogue_view_id` int(11) DEFAULT NULL,
1763 `U` double DEFAULT NULL,
1764 `UErr` double DEFAULT NULL,
1765 `B` double DEFAULT NULL,
1766 `BErr` double DEFAULT NULL,
1767 `V` double DEFAULT NULL,
1768 `VErr` double DEFAULT NULL,
1769 `R` double DEFAULT NULL,
1770 `RErr` double DEFAULT NULL,
1771 `I` double DEFAULT NULL,
1772 `IErr` double DEFAULT NULL,
1773 `J` double DEFAULT NULL,
1774 `JErr` double DEFAULT NULL,
1775 `H` double DEFAULT NULL,
1776 `HErr` double DEFAULT NULL,
1777 `K` double DEFAULT NULL,
1778 `KErr` double DEFAULT NULL,
1779 `_u` double DEFAULT NULL,
1780 `_uErr` double DEFAULT NULL,
1781 `_g` double DEFAULT NULL,
1782 `_gErr` double DEFAULT NULL,
1783 `_r` double DEFAULT NULL,
1784 `_rErr` double DEFAULT NULL,
1785 `_i` double DEFAULT NULL,
1786 `_iErr` double DEFAULT NULL,
1787 `_z` double DEFAULT NULL,
1788 `_zErr` double DEFAULT NULL,
1789 `_y` double DEFAULT NULL,
1790 `_yErr` double DEFAULT NULL,
1791 `G` double DEFAULT NULL,
1792 `GErr` double DEFAULT NULL,
1793 `W1` double DEFAULT NULL,
1794 `W1Err` double DEFAULT NULL,
1795 `unkMag` double DEFAULT NULL,
1796 `unkMagErr` double DEFAULT NULL,
1797 `dateLastModified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
1798 `updated` tinyint(4) DEFAULT '0',
1799 `classificationReliability` tinyint(4) DEFAULT NULL,
1800 `transientAbsMag` double DEFAULT NULL,
1801 `merged_rank` tinyint(4) DEFAULT NULL,
1802 PRIMARY KEY (`id`),
1803 KEY `key_transient_object_id` (`transient_object_id`),
1804 KEY `key_catalogue_object_id` (`catalogue_object_id`),
1805 KEY `idx_separationArcsec` (`separationArcsec`),
1806 KEY `idx_rank` (`rank`)
1807) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1809CREATE TABLE IF NOT EXISTS `sherlock_classifications` (
1810 `transient_object_id` bigint(20) NOT NULL,
1811 `classification` varchar(45) DEFAULT NULL,
1812 `annotation` TEXT COLLATE utf8_unicode_ci DEFAULT NULL,
1813 `summary` VARCHAR(50) COLLATE utf8_unicode_ci DEFAULT NULL,
1814 `separationArcsec` DOUBLE DEFAULT NULL,
1815 `matchVerified` TINYINT NULL DEFAULT NULL,
1816 `developmentComment` VARCHAR(100) NULL,
1817 `dateLastModified` datetime DEFAULT CURRENT_TIMESTAMP,
1818 `dateCreated` datetime DEFAULT CURRENT_TIMESTAMP,
1819 `updated` varchar(45) DEFAULT '0',
1820 PRIMARY KEY (`transient_object_id`),
1821 KEY `key_transient_object_id` (`transient_object_id`),
1822 KEY `idx_summary` (`summary`),
1823 KEY `idx_classification` (`classification`),
1824 KEY `idx_dateLastModified` (`dateLastModified`)
1825) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1827""" % locals()
1829 # A FIX FOR MYSQL VERSIONS < 5.6
1830 triggers = []
1831 if float(self.dbVersions["transients"][:3]) < 5.6:
1832 createStatement = createStatement.replace(
1833 "`dateLastModified` datetime DEFAULT CURRENT_TIMESTAMP,", "`dateLastModified` datetime DEFAULT NULL,")
1834 createStatement = createStatement.replace(
1835 "`dateCreated` datetime DEFAULT CURRENT_TIMESTAMP,", "`dateCreated` datetime DEFAULT NULL,")
1837 triggers.append("""
1838CREATE TRIGGER dateCreated
1839BEFORE INSERT ON `%(crossmatchTable)s`
1840FOR EACH ROW
1841BEGIN
1842 IF NEW.dateCreated IS NULL THEN
1843 SET NEW.dateCreated = NOW();
1844 SET NEW.dateLastModified = NOW();
1845 END IF;
1846END""" % locals())
1848 try:
1849 writequery(
1850 log=self.log,
1851 sqlQuery=createStatement,
1852 dbConn=self.transientsDbConn,
1853 Force=True
1854 )
1855 except:
1856 self.log.info(
1857 "Could not create table (`%(crossmatchTable)s`). Probably already exist." % locals())
1859 sqlQuery = u"""
1860 SHOW TRIGGERS;
1861 """ % locals()
1862 rows = readquery(
1863 log=self.log,
1864 sqlQuery=sqlQuery,
1865 dbConn=self.transientsDbConn,
1866 )
1868 # DON'T ADD TRIGGERS IF THEY ALREADY EXIST
1869 for r in rows:
1870 if r["Trigger"] in ("sherlock_classifications_BEFORE_INSERT", "sherlock_classifications_AFTER_INSERT"):
1871 return None
1873 triggers.append("""CREATE TRIGGER `sherlock_classifications_BEFORE_INSERT` BEFORE INSERT ON `sherlock_classifications` FOR EACH ROW
1874BEGIN
1875 IF new.classification = "ORPHAN" THEN
1876 SET new.annotation = "The transient location is not matched against any known catalogued source", new.summary = "No catalogued match";
1877 END IF;
1878END""" % locals())
1880 triggers.append("""CREATE TRIGGER `sherlock_classifications_AFTER_INSERT` AFTER INSERT ON `sherlock_classifications` FOR EACH ROW
1881BEGIN
1882 update `%(transientTable)s` set `%(transientTableClassCol)s` = new.classification
1883 where `%(transientTableIdCol)s` = new.transient_object_id;
1884END""" % locals())
1886 for t in triggers:
1887 try:
1888 writequery(
1889 log=self.log,
1890 sqlQuery=t,
1891 dbConn=self.transientsDbConn,
1892 Force=True
1893 )
1894 except:
1895 self.log.info(
1896 "Could not create trigger (`%(crossmatchTable)s`). Probably already exist." % locals())
1898 self.log.debug('completed the ``_create_tables_if_not_exist`` method')
1899 return None
1901 # use the tab-trigger below for new method
1902 def generate_match_annotation(
1903 self,
1904 match,
1905 updatePeakMagnitudes=False):
1906 """*generate a human readale annotation for the transient-catalogue source match*
1908 **Key Arguments**
1910 - ``match`` -- the source crossmatched against the transient
1911 - ``updatePeakMagnitudes`` -- update the peak magnitudes in the annotations to give absolute magnitudes. Default *False*
1914 **Return**
1916 - None
1919 **Usage**
1923 ```python
1924 usage code
1925 ```
1927 ---
1929 ```eval_rst
1930 .. todo::
1932 - add usage info
1933 - create a sublime snippet for usage
1934 - write a command-line tool for this method
1935 - update package tutorial with command-line tool info if needed
1936 ```
1937 """
1938 self.log.debug('starting the ``generate_match_annotation`` method')
1940 if "catalogue_object_subtype" not in match:
1941 match["catalogue_object_subtype"] = None
1942 catalogue = match["catalogue_table_name"]
1943 objectId = match["catalogue_object_id"]
1944 objectType = match["catalogue_object_type"]
1945 objectSubtype = match["catalogue_object_subtype"]
1946 catalogueString = catalogue
1947 if "catalogue" not in catalogueString.lower():
1948 catalogueString = catalogue + " catalogue"
1949 if "/" in catalogueString:
1950 catalogueString += "s"
1952 if "ned" in catalogue.lower():
1953 objectId = objectId.replace("+", "%2B")
1954 objectId = '''<a href="https://ned.ipac.caltech.edu/cgi-bin/objsearch?objname=%(objectId)s&extend=no&hconst=73&omegam=0.27&omegav=0.73&corr_z=1&out_csys=Equatorial&out_equinox=J2000.0&obj_sort=RA+or+Longitude&of=pre_text&zv_breaker=30000.0&list_limit=5&img_stamp=YES">%(objectId)s</a>''' % locals()
1955 elif "sdss" in catalogue.lower():
1956 objectId = "http://skyserver.sdss.org/dr12/en/tools/explore/Summary.aspx?id=%(objectId)s" % locals(
1957 )
1959 ra = self.converter.ra_decimal_to_sexegesimal(
1960 ra=match["raDeg"],
1961 delimiter=""
1962 )
1963 dec = self.converter.dec_decimal_to_sexegesimal(
1964 dec=match["decDeg"],
1965 delimiter=""
1966 )
1967 betterName = "SDSS J" + ra[0:9] + dec[0:9]
1968 objectId = '''<a href="%(objectId)s">%(betterName)s</a>''' % locals()
1969 elif "milliquas" in catalogue.lower():
1970 thisName = objectId
1971 objectId = objectId.replace(" ", "+")
1972 objectId = '''<a href="https://heasarc.gsfc.nasa.gov/db-perl/W3Browse/w3table.pl?popupFrom=Query+Results&tablehead=name%%3Dheasarc_milliquas%%26description%%3DMillion+Quasars+Catalog+%%28MILLIQUAS%%29%%2C+Version+4.8+%%2822+June+2016%%29%%26url%%3Dhttp%%3A%%2F%%2Fheasarc.gsfc.nasa.gov%%2FW3Browse%%2Fgalaxy-catalog%%2Fmilliquas.html%%26archive%%3DN%%26radius%%3D1%%26mission%%3DGALAXY+CATALOG%%26priority%%3D5%%26tabletype%%3DObject&dummy=Examples+of+query+constraints%%3A&varon=name&bparam_name=%%3D%%22%(objectId)s%%22&bparam_name%%3A%%3Aunit=+&bparam_name%%3A%%3Aformat=char25&varon=ra&bparam_ra=&bparam_ra%%3A%%3Aunit=degree&bparam_ra%%3A%%3Aformat=float8%%3A.5f&varon=dec&bparam_dec=&bparam_dec%%3A%%3Aunit=degree&bparam_dec%%3A%%3Aformat=float8%%3A.5f&varon=bmag&bparam_bmag=&bparam_bmag%%3A%%3Aunit=mag&bparam_bmag%%3A%%3Aformat=float8%%3A4.1f&varon=rmag&bparam_rmag=&bparam_rmag%%3A%%3Aunit=mag&bparam_rmag%%3A%%3Aformat=float8%%3A4.1f&varon=redshift&bparam_redshift=&bparam_redshift%%3A%%3Aunit=+&bparam_redshift%%3A%%3Aformat=float8%%3A6.3f&varon=radio_name&bparam_radio_name=&bparam_radio_name%%3A%%3Aunit=+&bparam_radio_name%%3A%%3Aformat=char22&varon=xray_name&bparam_xray_name=&bparam_xray_name%%3A%%3Aunit=+&bparam_xray_name%%3A%%3Aformat=char22&bparam_lii=&bparam_lii%%3A%%3Aunit=degree&bparam_lii%%3A%%3Aformat=float8%%3A.5f&bparam_bii=&bparam_bii%%3A%%3Aunit=degree&bparam_bii%%3A%%3Aformat=float8%%3A.5f&bparam_broad_type=&bparam_broad_type%%3A%%3Aunit=+&bparam_broad_type%%3A%%3Aformat=char4&bparam_optical_flag=&bparam_optical_flag%%3A%%3Aunit=+&bparam_optical_flag%%3A%%3Aformat=char3&bparam_red_psf_flag=&bparam_red_psf_flag%%3A%%3Aunit=+&bparam_red_psf_flag%%3A%%3Aformat=char1&bparam_blue_psf_flag=&bparam_blue_psf_flag%%3A%%3Aunit=+&bparam_blue_psf_flag%%3A%%3Aformat=char1&bparam_ref_name=&bparam_ref_name%%3A%%3Aunit=+&bparam_ref_name%%3A%%3Aformat=char6&bparam_ref_redshift=&bparam_ref_redshift%%3A%%3Aunit=+&bparam_ref_redshift%%3A%%3Aformat=char6&bparam_qso_prob=&bparam_qso_prob%%3A%%3Aunit=percent&bparam_qso_prob%%3A%%3Aformat=int2%%3A3d&bparam_alt_name_1=&bparam_alt_name_1%%3A%%3Aunit=+&bparam_alt_name_1%%3A%%3Aformat=char22&bparam_alt_name_2=&bparam_alt_name_2%%3A%%3Aunit=+&bparam_alt_name_2%%3A%%3Aformat=char22&Entry=&Coordinates=J2000&Radius=Default&Radius_unit=arcsec&NR=CheckCaches%%2FGRB%%2FSIMBAD%%2BSesame%%2FNED&Time=&ResultMax=1000&displaymode=Display&Action=Start+Search&table=heasarc_milliquas">%(thisName)s</a>''' % locals()
1974 if objectSubtype and str(objectSubtype).lower() in ["uvs", "radios", "xray", "qso", "irs", 'uves', 'viss', 'hii', 'gclstr', 'ggroup', 'gpair', 'gtrpl']:
1975 objectType = objectSubtype
1977 if objectType == "star":
1978 objectType = "stellar source"
1979 elif objectType == "agn":
1980 objectType = "AGN"
1981 elif objectType == "cb":
1982 objectType = "CV"
1983 elif objectType == "unknown":
1984 objectType = "unclassified source"
1986 sep = match["separationArcsec"]
1987 if match["classificationReliability"] == 1:
1988 classificationReliability = "synonymous"
1989 psep = match["physical_separation_kpc"]
1990 if psep:
1991 location = '%(sep)0.1f" (%(psep)0.1f Kpc) from the %(objectType)s core' % locals(
1992 )
1993 else:
1994 location = '%(sep)0.1f" from the %(objectType)s core' % locals(
1995 )
1996 else:
1997 # elif match["classificationReliability"] in (2, 3):
1998 classificationReliability = "possibly associated"
1999 n = float(match["northSeparationArcsec"])
2000 if n > 0:
2001 nd = "S"
2002 else:
2003 nd = "N"
2004 e = float(match["eastSeparationArcsec"])
2005 if e > 0:
2006 ed = "W"
2007 else:
2008 ed = "E"
2009 n = math.fabs(float(n))
2010 e = math.fabs(float(e))
2011 psep = match["physical_separation_kpc"]
2012 if psep:
2013 location = '%(n)0.2f" %(nd)s, %(e)0.2f" %(ed)s (%(psep)0.1f Kpc) from the %(objectType)s centre' % locals(
2014 )
2015 else:
2016 location = '%(n)0.2f" %(nd)s, %(e)0.2f" %(ed)s from the %(objectType)s centre' % locals(
2017 )
2018 location = location.replace("unclassified", "object's")
2020 best_mag = None
2021 best_mag_error = None
2022 best_mag_filter = None
2023 filters = ["R", "V", "B", "I", "J", "G", "H", "K", "U",
2024 "_r", "_g", "_i", "_g", "_z", "_y", "_u", "W1", "unkMag"]
2025 for f in filters:
2026 if f in match and match[f] and not best_mag:
2027 best_mag = match[f]
2028 try:
2029 best_mag_error = match[f + "Err"]
2030 except:
2031 pass
2032 subfilter = f.replace(
2033 "_", "").replace("Mag", "")
2034 best_mag_filter = f.replace(
2035 "_", "").replace("Mag", "") + "="
2036 if "unk" in best_mag_filter:
2037 best_mag_filter = ""
2038 subfilter = ''
2040 if not best_mag_filter:
2041 if str(best_mag).lower() in ("8", "11", "18"):
2042 best_mag_filter = "an "
2043 else:
2044 best_mag_filter = "a "
2045 else:
2046 if str(best_mag_filter)[0].lower() in ("r", "i", "h"):
2047 best_mag_filter = "an " + best_mag_filter
2048 else:
2049 best_mag_filter = "a " + best_mag_filter
2050 if not best_mag:
2051 best_mag = "an unknown-"
2052 best_mag_filter = ""
2053 else:
2054 best_mag = "%(best_mag)0.2f " % locals()
2056 distance = None
2057 if "direct_distance" in match and match["direct_distance"]:
2058 d = match["direct_distance"]
2059 distance = "distance of %(d)0.1f Mpc" % locals()
2061 if match["z"]:
2062 z = match["z"]
2063 distance += "(z=%(z)0.3f)" % locals()
2064 elif "z" in match and match["z"]:
2065 z = match["z"]
2066 distance = "z=%(z)0.3f" % locals()
2067 elif "photoZ" in match and match["photoZ"]:
2068 z = match["photoZ"]
2069 zErr = match["photoZErr"]
2070 if not zErr:
2071 distance = "photoZ=%(z)0.3f" % locals()
2072 else:
2073 distance = "photoZ=%(z)0.3f (±%(zErr)0.3f)" % locals()
2075 if distance:
2076 distance = "%(distance)s" % locals()
2078 distance_modulus = None
2079 if match["direct_distance_modulus"]:
2080 distance_modulus = match["direct_distance_modulus"]
2081 elif match["distance_modulus"]:
2082 distance_modulus = match["distance_modulus"]
2084 if updatePeakMagnitudes:
2085 if distance:
2086 absMag = match["transientAbsMag"]
2087 absMag = """ A host %(distance)s implies a transient <em>M =</em> %(absMag)s mag.""" % locals(
2088 )
2089 else:
2090 absMag = ""
2091 else:
2092 if distance and distance_modulus:
2093 absMag = "%(distance_modulus)0.2f" % locals()
2094 absMag = """ A host %(distance)s implies a <em>m - M =</em> %(absMag)s.""" % locals(
2095 )
2096 else:
2097 absMag = ""
2099 annotation = "The transient is %(classificationReliability)s with <em>%(objectId)s</em>; %(best_mag_filter)s%(best_mag)smag %(objectType)s found in the %(catalogueString)s. It's located %(location)s.%(absMag)s" % locals()
2100 summary = '%(sep)0.1f" from %(objectType)s in %(catalogue)s' % locals(
2101 )
2103 self.log.debug('completed the ``generate_match_annotation`` method')
2104 return annotation, summary, sep
2106 # use the tab-trigger below for new method
2107 # xt-class-method
2110def _crossmatch_transients_against_catalogues(
2111 transientsMetadataListIndex,
2112 log,
2113 settings,
2114 colMaps):
2115 """run the transients through the crossmatch algorithm in the settings file
2117 **Key Arguments**
2120 - ``transientsMetadataListIndex`` -- the list of transient metadata lifted from the database.
2121 - ``colMaps`` -- dictionary of dictionaries with the name of the database-view (e.g. `tcs_view_agn_milliquas_v4_5`) as the key and the column-name dictary map as value (`{view_name: {columnMap}}`).
2123 **Return**
2125 - ``crossmatches`` -- a list of dictionaries of the associated sources crossmatched from the catalogues database
2128 .. todo ::
2130 - update key arguments values and definitions with defaults
2131 - update return values and definitions
2132 - update usage examples and text
2133 - update docstring text
2134 - check sublime snippet exists
2135 - clip any useful text to docs mindmap
2136 - regenerate the docs and check redendering of this docstring
2137 """
2139 from fundamentals.mysql import database
2140 from sherlock import transient_catalogue_crossmatch
2142 global theseBatches
2144 log.debug(
2145 'starting the ``_crossmatch_transients_against_catalogues`` method')
2147 # SETUP ALL DATABASE CONNECTIONS
2149 transientsMetadataList = theseBatches[transientsMetadataListIndex]
2151 dbConn = database(
2152 log=log,
2153 dbSettings=settings["database settings"]["static catalogues"]
2154 ).connect()
2156 cm = transient_catalogue_crossmatch(
2157 log=log,
2158 dbConn=dbConn,
2159 transients=transientsMetadataList,
2160 settings=settings,
2161 colMaps=colMaps
2162 )
2163 crossmatches = cm.match()
2165 log.debug(
2166 'completed the ``_crossmatch_transients_against_catalogues`` method')
2168 return crossmatches