Source code for momxml.utilities

r'''
Various support functions that did not fit anywhere else.
'''

import ephem
from numpy import pi, cos, sin, arcsin, sqrt, arctan2
from lofarobsxml import Angle
import sys

[docs]class InvalidStationSetError(ValueError): r''' To be raised if an invalid station set is provided. '''
[docs]class AutoReprBaseClass(object): r''' Base class that implements a simplistic __repr__ function. The order in which the members are printed is the same as that in which the arguments are set in bthe constructor body. ''' def __repr__(self): name = self.__class__.__name__ as_dict = self.__dict__ members = sorted(as_dict.keys()) longest_member = sorted([len(s) for s in members])[-1] member_strings = [mem.ljust(longest_member)+' = '+repr(as_dict[mem]) for mem in members] sep = '\n'+' '*(len(name)+1) indented_member_strings = [('\n'+ ' '*(longest_member+3)).join( member.split('\n')) for member in member_strings] unadjusted = ',\n'.join(indented_member_strings) return name+'('+sep.join(unadjusted.split('\n'))+')'
[docs]def with_auto_repr(cls): r''' Class decorator that adds a nicer default __repr__ method to a class. **Examples** >>> class Elements(object): ... def __init__(self, a, b): ... self.a = a ... self.b = b >>> aa = Elements(1, '3') >>> print(repr(aa)[0:-16]) <lofarobsxml.utilities.Elements object at >>> Elements = with_auto_repr(Elements) >>> bb = Elements (1, 'v') >>> print(repr(bb)) Elements(a = 1, b = 'v') ''' class AutoReprClass(cls): r''' Base class that implements a simplistic __repr__ function. The order in which the members are printed is the same as that in which the arguments are set in bthe constructor body. ''' def __repr__(self): name = cls.__name__ as_dict = self.__dict__ members = sorted(as_dict.keys()) longest_member = sorted([len(s) for s in members])[-1] member_strings = [mem.ljust(longest_member)+' = '+repr(as_dict[mem]) for mem in members] sep = '\n'+' '*(len(name)+1) indented_member_strings = [('\n'+ ' '*(longest_member+3)).join( member.split('\n')) for member in member_strings] unadjusted = ',\n'.join(indented_member_strings) return name+'('+sep.join(unadjusted.split('\n'))+')' return AutoReprClass
[docs]def typecheck(variable, type_class, name = None): r''' Raise TypeError if ``variable`` is not an instance of ``type_class``. **Parameters** variable : any object The variable that will be type-checked. type_class : type or list of types The desired type of ``variable``. name : string A descriptive name of the variable, used in the error message. **Examples** >>> typecheck(4.0 , float) >>> typecheck(None, [int, type(None)]) >>> typecheck(5 , [int, type(None)]) >>> typecheck(4.0, [int, type(None)]) Traceback (most recent call last): ... TypeError: type(4.0) not in ['int', 'NoneType'] >>> a = 'blaargh' >>> typecheck(a, int, 'a') Traceback (most recent call last): ... TypeError: type(a)('blaargh') not in ['int'] ''' template = 'type(%(var)r) not in %(types)r' if name is not None: template = 'type(%(name)s)(%(var)r) not in %(types)r' if type(type_class) == list: type_list = type_class else: type_list = [type_class] if type(variable) not in type_list: raise TypeError(template % {'name' : name, 'var' : variable, 'types' : [tp.__name__ for tp in type_list]})
[docs]def indent(string, amount): r''' Indent a multi-line string by the given amount. string : string The string to indent. amount : int The direction and amount to indent. **Examples** >> indent('Hi\nThere', 2) Hi There >> indent('Hi\nThere\n\n', -1) i here <BLANKLINE> <BLANKLINE> ''' lines = string.split('\n') if amount > 0: lines = [line if '' == line else ' '*amount + line for line in lines] if amount < 0: lines = [line[-amount:] for line in lines] return '\n'.join(lines)
[docs]def unique(sequence): r''' Return a list containing all unique elements of a sequence. **Parameters** sequence : sequence List from which to return all unique elements. **Returns** A list. **Examples** >>> sorted(unique([3, 2, 4, 3, 1, 1, 2])) [1, 2, 3, 4] ''' return list(set(sequence))
[docs]def flatten_list(list_of_lists): r''' Takes a list of lists and spits out one list with all sub lists concatenated. [[1, 2, 3], [4, 5]] -> [1, 2, 3, 4, 5] **Parameters** list_of_lists : list of lists The list to flatten. **Returns** A one dimensional list. **Examples** >>> flatten_list([[1, 2, 3], ['a', True], ['b', ['c', 4]]]) [1, 2, 3, 'a', True, 'b', ['c', 4]] ''' return [element for sub_list in list_of_lists for element in sub_list]
[docs]def lower_case(boolean): r''' Return lower case string representation of a boolean value. **Parameters** boolean : bool The value to convert to a string. **Examples** >>> lower_case(True) 'true' >>> lower_case(False) 'false' ''' return repr(boolean).lower()
[docs]def parse_subband_list(parset_subband_list): r''' Parse a subband list from a parset or SAS/MAC / MoM spec. **Parameters** parset_subband_list : string Value of Observation.Beam[0].subbandList **Returns** A list of integers containing the subband numbers. **Raises** ValueError If a syntax problem is encountered. **Examples** >>> sb_spec = '[154..163,185..194,215..224,245..254,275..284,10*374]' >>> parse_subband_list(sb_spec) [154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 374, 374, 374, 374, 374, 374, 374, 374, 374, 374] >>> sb_spec = '[77..87,116..127,155..166,194..205,233..243,272..282]' >>> parse_subband_list(sb_spec) [77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282] >>> parse_subband_list('[]') [] >>> parse_subband_list('1,2,10..15,200..202,400') [1, 2, 10, 11, 12, 13, 14, 15, 200, 201, 202, 400] ''' stripped_subband_list = parset_subband_list.strip('[] \n\t') if stripped_subband_list == '': return [] sub_lists = [word.strip().split('..') for word in stripped_subband_list.split(',')] subbands = [] for sub_list in sub_lists: if len(sub_list) == 1: multiplication = sub_list[0].split('*') if len(multiplication) == 2: subbands += [int(multiplication[1])]*int(multiplication[0]) else: subbands.append(int(sub_list[0])) elif len(sub_list) == 2: subbands += range(int(sub_list[0]), int(sub_list[1])+1) else: raise ValueError('%r is not a valid sub_range in a subband list' % sub_list) return subbands
[docs]def lofar_observer(date = None): r''' **Parameters** date : ephem.Date The date to set for the ephem.Observer() instance **Returns** An ephem.Observer() instance for the LOFAR core. **Examples** >>> lofar_observer('2013/04/15 12:34:56') <ephem.Observer date='2013/4/15 12:34:56' epoch='2000/1/1 12:00:00' lon=6:52:11.4 lat=52:54:54.4 elevation=49.343999999999994m horizon=0:00:00.0 temp=15.0C pressure=1010.0mBar> ''' lofar = ephem.Observer() lofar.long = +6.869837540*pi/180 lofar.lat = +52.915122495*pi/180 lofar.elevation = +49.344 if date is not None: lofar.date = date return lofar
[docs]def lofar_sidereal_time(date): r''' Returns an ephem.Angle object with the current sidereal time at LOFAR CS002 LBA. The CS002 LBA position in ITRF2005 coordinates at epoch 2009.5. **Examples** >>> type(lofar_sidereal_time(ephem.Observer().date)) <class 'ephem.Angle'> >>> lofar = ephem.Observer() >>> lofar.long = +6.869837540*pi/180 >>> lofar.lat = +52.915122495*pi/180 >>> lofar.elevation = +49.344 >>> lofar.date = ephem.Observer().date >>> abs(lofar.sidereal_time() - lofar_sidereal_time(lofar.date)) 0.0 ''' # CS002 LBA in ITRF2005, epoch 2009.5 lofar = lofar_observer(date) return lofar.sidereal_time()
[docs]def next_date_with_lofar_lst(lst_rad, start_date = None): r''' ''' if not start_date: start_date = ephem.Observer().date else: start_date = ephem.Date(start_date) lst = lst_rad lst_at_start_rad = float(lofar_sidereal_time(start_date)) while lst < lst_at_start_rad: lst = lst + 2*pi delta_lst_rad = lst - lst_at_start_rad delta_utc_rad = delta_lst_rad/1.002737904 return ephem.Date(start_date + ephem.hour*(delta_utc_rad*12/pi))
[docs]def next_sunrise(date, observer = None): r''' Return an ephem.Date instance with the next sunrise at LOFAR, or any other ephem.Observer, if provided. **Parameters** date : ephem.Date Date from which to look for sunrise. observer : None or ephem.Observer Location for which Sunrise is computed. Default is LOFAR core if None is provided. **Returns** An ephem.Date instance. **Examples** >>> print(next_sunrise('2013/04/03 12:00:00')) 2013/4/4 04:58:56 ''' if observer is None: observer = lofar_observer(date) return observer.next_rising(ephem.Sun())
[docs]def next_sunset(date, observer = None): r''' Return an ephem.Date instance with the next sunset at LOFAR, or any other ephem.Observer, if provided. **Parameters** date : ephem.Date Date from which to look for sunrise. observer : None or ephem.Observer Location for which Sunrise is computed. Default is LOFAR core if None is provided. **Returns** An ephem.Date instance. **Examples** >>> print(next_sunset('2013/04/03 12:00:00')) 2013/4/3 18:11:18 ''' if observer is None: observer = lofar_observer(date) return observer.next_setting(ephem.Sun())
[docs]def antenna_field_sort_key(name): r''' Produce a sort key for station names. **Parameters** name : string The antenna field's name. **Examples** >>> antenna_field_sort_key('CS002HBA1') 21 >>> antenna_field_sort_key('CS021LBA') 210 >>> antenna_field_sort_key('RS106HBA') 10600 >>> antenna_field_sort_key('UK608HBA') 60800 ''' station_number = int(name[2:5])*10 if name[0:2].upper() != 'CS': station_number *=10 if 'HBA1' in name: station_number += 1 return station_number
[docs]def sort_station_list(stations): r''' Sort a station list so we first get the CS, then the RS and EU in numerical order. **Parameters** stations : list of strings The station list to sort. **Examples** >>> sort_station_list(['UK608', 'RS407', 'DE603', 'RS106', ... 'RS205', 'CS026', 'SE607', 'CS501', 'DE605', 'RS509', ... 'CS001']) ['CS001', 'CS026', 'CS501', 'RS106', 'RS205', 'RS407', 'RS509', 'DE603', 'DE605', 'SE607', 'UK608'] >>> sort_station_list(['UK608HBA', 'RS407HBA', 'DE603HBA', 'RS106HBA', ... 'CS026HBA1', 'SE607HBA', 'CS026HBA0', 'CS501HBA0', 'DE605HBA', 'RS509HBA', ... 'CS001HBA1']) ['CS001HBA1', 'CS026HBA0', 'CS026HBA1', 'CS501HBA0', 'RS106HBA', 'RS407HBA', 'RS509HBA', 'DE603HBA', 'DE605HBA', 'SE607HBA', 'UK608HBA'] ''' return sorted(stations, key=antenna_field_sort_key)
[docs]def station_list(station_set, include = None, exclude = None): r''' Provides a sorted list of station names, given a station set name, a list of stations to include, and a list of stations to exclude. Names use upper case letters. **Parameters** station_set : string One of 'superterp', 'core', 'remote', 'nl', 'all', or 'none', where 'nl' is the concatenation of the 'core' and 'remote' sets, and all is 'nl' plus 'eu'. include : None or list of strings List of station names to append to the station set. exclude : None or list of strings List of station names to remove from the station set. **Returns** A sorted list of strings containing LOFAR station names. **Examples** >>> station_list('superterp') ['CS002', 'CS003', 'CS004', 'CS005', 'CS006', 'CS007'] >>> len(station_list('core')) 24 >>> station_list('remote') ['RS106', 'RS205', 'RS208', 'RS210', 'RS305', 'RS306', 'RS307', 'RS310', 'RS406', 'RS407', 'RS409', 'RS503', 'RS508', 'RS509'] >>> len(station_list('nl')) 38 >>> (station_list('nl', exclude = station_list('remote')) == ... station_list('core')) True >>> station_list('eu') ['DE601', 'DE602', 'DE603', 'DE604', 'DE605', 'FR606', 'SE607', 'UK608', 'DE609', 'PL610', 'PL611', 'PL612'] >>> station_list('all')==station_list('nl', include=station_list('eu')) True >>> len(unique(station_list('all'))) 50 >>> station_list('wsrt') Traceback (most recent call last): ... lofarobsxml.utilities.InvalidStationSetError: wsrt is not a valid station set. ''' superterp = ['CS002', 'CS003', 'CS004', 'CS005', 'CS006', 'CS007'] core = ['CS001'] + superterp + ['CS011', 'CS013', 'CS017', 'CS021', 'CS024', 'CS026', 'CS028', 'CS030', 'CS031', 'CS032', 'CS101', 'CS103', 'CS201', 'CS301', 'CS302', 'CS401', 'CS501'] remote = ['RS106', 'RS205', 'RS208', 'RS210', 'RS305', 'RS306', 'RS307', 'RS310', 'RS406', 'RS407', 'RS409', 'RS503', 'RS508', 'RS509'] netherlands = core + remote europe = ['DE601', 'DE602', 'DE603', 'DE604', 'DE605', 'FR606', 'SE607', 'UK608', 'DE609', 'PL610', 'PL611', 'PL612'] all_stations = netherlands + europe lookup_table = {'superterp': superterp, 'core' : core, 'remote' : remote, 'nl' : netherlands, 'eu' : europe, 'all' : all_stations, 'none' : []} try: if include is None: include_list = [] else: include_list = [station.upper() for station in include] superset = unique(lookup_table[station_set] + include_list) except (KeyError,): raise InvalidStationSetError('%s is not a valid station set.' % sys.exc_info()[1].args[0]) if exclude is None: exclude_list = [] else: exclude_list = [station.upper() for station in exclude] return sort_station_list([s for s in superset if s not in exclude_list])
[docs]def exclude_conflicting_eu_stations(stations): r''' The international stations are connected to the same BlueGene IONodes as the HBA1 ear in some core stations. For HBA_ONE, HBA_DUAL_INNER, and HBA_DUAL mode, we need to remove either the core stations, or the eu stations that conflict. This function removes the conflicting eu stations. **Parameters** stations : list of strings The station names in the observation. **Returns** A list of strings containing only non-conflicting stations, from which the EU stations have been removed. **Examples** >>> exclude_conflicting_eu_stations(['CS001', 'CS002', 'RS407']) ['CS001', 'CS002', 'RS407'] >>> exclude_conflicting_eu_stations(['CS001', 'CS002', 'RS407', 'DE605']) ['CS001', 'CS002', 'RS407', 'DE605'] >>> exclude_conflicting_eu_stations(['CS001', 'CS002', 'CS028', 'RS407', ... 'DE601', 'DE605', 'UK608']) ['CS001', 'CS002', 'CS028', 'RS407', 'DE605', 'UK608'] ''' exclude_dict = {'DE601': 'CS001', 'DE602': 'CS031', 'DE603': 'CS028', 'DE604': 'CS011', 'DE605': 'CS401', 'FR606': 'CS030', 'SE607': 'CS301', 'UK608': 'CS013'} good_stations = [] for station in stations: try: if exclude_dict[station] not in stations: good_stations.append(station) except KeyError: good_stations.append(station) return good_stations
[docs]def exclude_conflicting_nl_stations(stations): r''' The international stations are connected to the same BlueGene IONodes as the HBA1 ear in some core stations. For HBA_ONE, HBA_DUAL_INNER, and HBA_DUAL mode, we need to remove either the core stations, or the eu stations that conflict. This function removes the conflicting core stations. **Parameters** stations : list of strings The station names in the observation. **Returns** A list of strings containing only non-conflicting stations, from which the core stations have been removed. **Examples** >>> exclude_conflicting_nl_stations(['CS001', 'CS002', 'RS407']) ['CS001', 'CS002', 'RS407'] >>> exclude_conflicting_nl_stations(['CS001', 'CS002', 'RS407', 'DE605']) ['CS001', 'CS002', 'RS407', 'DE605'] >>> exclude_conflicting_nl_stations(['CS001', 'CS002', 'CS028', ... 'RS407', 'DE601', 'DE605', 'UK608']) ['CS002', 'CS028', 'RS407', 'DE601', 'DE605', 'UK608'] ''' exclude_dict = {'CS001': 'DE601', 'CS031': 'DE602', 'CS028': 'DE603', 'CS011': 'DE604', 'CS401': 'DE605', 'CS030': 'FR606', 'CS301': 'SE607', 'CS013': 'UK608'} good_stations = [] for station in stations: try: if exclude_dict[station] not in stations: good_stations.append(station) except KeyError: good_stations.append(station) return good_stations
[docs]def validate_enumeration(name, value, allowed): r''' If ``value`` of kind ``name`` is not in the list of ``allowed`` values, raise a ``ValueError``. Very useful to verify if a caller provided a wrong value for a string that is part of an enumeration. **Parameters** name : string The kind of thing the value could represent. value : string The value to be tested. allowed : list of strings List of all valid values. **Returns** True if ``value`` is in ``allowed``. **Raises** ValueError If ``value`` is not in ``allowed``. **Example** >>> validate_enumeration('station set', 'core', ... allowed = ['core', 'remote', 'nl', 'all']) True >>> validate_enumeration('station set', 'wsrt', ... allowed = ['core', 'remote', 'nl', 'all']) Traceback (most recent call last): ... ValueError: 'wsrt' is not a valid station set; choose one of 'core', 'remote', 'nl', 'all' ''' if value not in allowed: raise ValueError('%r is not a valid %s; choose one of %s' % (value, name, '\''+'\', \''.join(allowed)+'\'')) return True
def lm_from_radec(ra_angle, dec_angle, ra0_angle, dec0_angle): cos_dec = cos(float(dec_angle)) sin_dec = sin(float(dec_angle)) cos_dec0 = cos(float(dec0_angle)) sin_dec0 = sin(float(dec0_angle)) sin_dra = sin(float(ra_angle - ra0_angle)) cos_dra = cos(float(ra_angle - ra0_angle)) l_rad = cos_dec*sin_dra m_rad = sin_dec*cos_dec0 - cos_dec*sin_dec0*cos_dra return (l_rad, m_rad) def radec_from_lm(l_rad, m_rad, ra0_angle, dec0_angle): n_rad = sqrt(1.0 - l_rad*l_rad - m_rad*m_rad) cos_dec0 = cos(float(dec0_angle)) sin_dec0 = sin(float(dec0_angle)) ra_rad = float(ra0_angle) + arctan2(l_rad, cos_dec0*n_rad - m_rad*sin_dec0) dec_rad = arcsin(m_rad*cos_dec0 + sin_dec0*n_rad) return (Angle(rad = ra_rad), Angle(rad = dec_rad)) def rotate_lm_CCW(l_rad, m_rad, ccw_angle): cs = cos(float(ccw_angle)) ss = sin(float(ccw_angle)) l_new = l_rad*cs + m_rad*ss m_new = -l_rad*ss + m_rad*cs return l_new, m_new