From 3d236b9fc1febd0c570ba369a4cf2fb24401b55f Mon Sep 17 00:00:00 2001 From: Calvin Leung Date: Mon, 26 Apr 2021 12:51:52 -0700 Subject: [PATCH 1/7] feat(tools): Change CHIME position --- ch_util/ephemeris.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/ch_util/ephemeris.py b/ch_util/ephemeris.py index 095d2764..8a170423 100644 --- a/ch_util/ephemeris.py +++ b/ch_util/ephemeris.py @@ -124,19 +124,26 @@ STELLAR_S, ) - -#: CHIME's latitude [degrees]. -#: Kiyo looked these up on Google Earth. Should replace with 'official' numbers. -CHIMELATITUDE = 49.32 # degrees - -#: CHIME's longitude [degrees]. -#: Kiyo looked these up on Google Earth. Should replace with 'official' numbers. -CHIMELONGITUDE = -119.62 # degrees - -#: CHIME's altitude [metres]. -#: Mateus looked this up on Wikipedia. Should replace with 'official' number. -CHIMEALTITUDE = 545.0 # metres - +# Calvin derived the horizontal position of the geometric center of the focal lines and the elevation of the focal line from survey coordinates: +CHIMELATITUDE = 49.3207092194 +CHIMELONGITUDE = -119.6236774310 +CHIMEALTITUDE = 555.372 + +# Kiyo looked these up on Google Earth. Should replace with 'official' numbers. +# CHIMELATITUDE = 49.32 # degrees +# CHIMELONGITUDE = -119.62 # degrees +# Mateus looked this up on Wikipedia. Should replace with 'official' number. +# CHIMEALTITUDE = 545.0 # metres +# Calvin replaced old coordinates with the position of the geometric center of the focal lines derived from survey coordinates: see https://bao.chimenet.ca/doc/documents/1327 +CHIMELATITUDE = 49.3207092194 +CHIMELONGITUDE = -119.6236774310 +CHIMEALTITUDE = 555.372 + +# Calvin also positioned the GBO/TONE Outrigger similatly. +# GBO/TONE Outrigger +TONELATITUDE = 38.4292962636 +TONELONGITUDE = -79.8451625395 +TONEALTITUDE = 810.000 # Create the Observer instances for CHIME and outriggers chime = Observer( From 69311649900827ad089b4118f80d923da2c24ccd Mon Sep 17 00:00:00 2001 From: Calvin Leung Date: Mon, 26 Apr 2021 12:54:22 -0700 Subject: [PATCH 2/7] feat(tools): added TONEAntenna objects for GBO outrigger --- ch_util/ephemeris.py | 7 ++++++ ch_util/tools.py | 60 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/ch_util/ephemeris.py b/ch_util/ephemeris.py index 8a170423..c2cefd8d 100644 --- a/ch_util/ephemeris.py +++ b/ch_util/ephemeris.py @@ -153,6 +153,13 @@ lsd_start=datetime(2013, 11, 15), ) +tone = Observer( + lon=TONELONGITUDE, + lat=TONELATITUDE, + alt=TONEALTITUDE, + lsd_start=datetime(2013, 11, 15), +) + def _get_chime(): import warnings diff --git a/ch_util/tools.py b/ch_util/tools.py index f626d90e..255e808b 100644 --- a/ch_util/tools.py +++ b/ch_util/tools.py @@ -159,6 +159,7 @@ "chime": [49.3207125, -119.623670], "pathfinder": [49.3202245, -119.6183635], "galt_26m": [49.320909, -119.620174], + "gbo_tone": [38.4292962636, -79.8451625395], } # Classes @@ -440,6 +441,16 @@ class CHIMEAntenna(ArrayAntenna): _delay = 0 # Treat CHIME antennas as defining the delay zero point +class TONEAntenna(ArrayAntenna): + """Antenna that is part of GBO/TONE Outrigger. + Let's allow for a global rotation and offset. + """ + + _rotation = 0.00 + _offset = [0.00, 0.00, 0.00] + _delay = 0 + + class HolographyAntenna(Antenna): """Antenna used for holography. @@ -1109,6 +1120,7 @@ def get_correlator_inputs(lay_time, correlator=None, connect=True): Fetch only for specified correlator. Use the serial number in database, or `pathfinder` or `chime`, which will substitute the correct serial. If `None` return for all correlators. + Option `tone` added for GBO 12 dish outrigger prototype array. connect : bool, optional Connect to database and set the user to Jrs65 prior to query. Default is True. @@ -1144,6 +1156,12 @@ def get_correlator_inputs(lay_time, correlator=None, connect=True): correlator = "K7BP16-0004" elif correlator.lower() == "chime": correlator = "FCC" + elif correlator.lower() == "tone": + # A hack to return GBO correlator inputs + correlator = "tone" + connect = False + laytime = 0 + return fake_tone_database() if not connect_this_rank(): return None @@ -1313,6 +1331,48 @@ def get_feed_positions(feeds, get_zpos=False): return pos +def fake_tone_database(): + positions_and_polarizations = [ + ("S", [15.08, -1.61]), + ("E", [15.08, -1.61]), + ("S", [-9.19, -15.24]), + ("E", [-9.19, -15.24]), + ("S", [7.02, 14.93]), + ("E", [7.02, 14.93]), + ("S", [9.01, -5.02]), + ("E", [9.01, -5.02]), + ("S", [2.8, 2.67]), + ("E", [2.8, 2.67]), + ("S", [-1.66, 10.38]), + ("E", [-1.66, 10.38]), + ("S", [-7.63, -0.79]), + ("E", [-7.63, -0.79]), + ("S", [-15.43, -5.33]), + ("E", [-15.43, -5.33]), + ] + inputs = [] + for id, pol_ns_ew in enumerate(positions_and_polarizations): + inputs.append( + TONEAntenna( + id=id, + crate=0, + slot=0, + sma=0, + corr_order=0, + input_sn=f"TONE{id:04}", + corr="tone", + reflector=None, + antenna=f"ANT{id//2:04}", + rf_thru="N/A", + cyl=0, + pol=pol_ns_ew[0], + flag=True, + pos=[pol_ns_ew[1][0], pol_ns_ew[1][1], 0], + ) + ) + return inputs + + def get_feed_polarisations(feeds): """Get an array of the feed polarisations. From cc8a59d23f4e98af47042a79e423b3e27f4e1b3d Mon Sep 17 00:00:00 2001 From: Calvin Leung Date: Mon, 26 Apr 2021 12:55:45 -0700 Subject: [PATCH 3/7] test(ephemeris): changed csd_zero to reflect newest position https://bao.chimenet.ca/doc/documents/1327 --- tests/test_ephemeris.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_ephemeris.py b/tests/test_ephemeris.py index 3812ec66..436f6b2d 100644 --- a/tests/test_ephemeris.py +++ b/tests/test_ephemeris.py @@ -68,7 +68,8 @@ def test_transit_against_transit_ra(): def test_csd(): """Test CHIME sidereal day definition.""" # csd_zero = 1384489290.908534 - csd_zero = 1384489290.224582 + # csd_zero = 1384489290.224582 + csd_zero = 1384489291.0995445 et1 = ephemeris.datetime_to_unix(datetime(2013, 11, 14)) # Check the zero of CSD (1e-7 accuracy ~ 10 milliarcsec) From 510b66b13b5a9a7fc5f66043e584039c1257738b Mon Sep 17 00:00:00 2001 From: Calvin Leung Date: Mon, 26 Apr 2021 12:59:32 -0700 Subject: [PATCH 4/7] feat(ephemeris,tools): refactor ephemeris and tools to use arbitrary caput.observer --- ch_util/ephemeris.py | 38 ++++++++++++++++++-------------------- ch_util/tools.py | 23 +++++++++++++++-------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/ch_util/ephemeris.py b/ch_util/ephemeris.py index c2cefd8d..b4bdcd78 100644 --- a/ch_util/ephemeris.py +++ b/ch_util/ephemeris.py @@ -297,7 +297,7 @@ def parse_date(datestring): return datetime.strptime(datestring, "%Y%m%d") - timedelta(hours=tzoffset) -def utc_lst_to_mjd(datestring, lst): +def utc_lst_to_mjd(datestring, lst, obs=chime): """Convert datetime string and LST to corresponding modified Julian Day Parameters @@ -306,6 +306,7 @@ def utc_lst_to_mjd(datestring, lst): Date as YYYYMMDD-AAA, where AAA is one of [UTC, PST, PDT] lst : float Local sidereal time at DRAO (CHIME) in decimal hours + obs : caput.Observer object Returns ------- @@ -314,7 +315,7 @@ def utc_lst_to_mjd(datestring, lst): """ return ( unix_to_skyfield_time( - chime.lsa_to_unix(lst * 360 / 24, datetime_to_unix(parse_date(datestring))) + obs.lsa_to_unix(lst * 360 / 24, datetime_to_unix(parse_date(datestring))) ).tt - 2400000.5 ) @@ -356,7 +357,7 @@ def sphdist(long1, lat1, long2, lat2): return Angle(radians=2 * dist) -def solar_transit(start_time, end_time=None): +def solar_transit(start_time, end_time=None, obs=chime): """Find the Solar transits between two times for CHIME. Parameters @@ -376,10 +377,10 @@ def solar_transit(start_time, end_time=None): planets = skyfield_wrapper.ephemeris sun = planets["sun"] - return chime.transit_times(sun, start_time, end_time) + return obs.transit_times(sun, start_time, end_time) -def lunar_transit(start_time, end_time=None): +def lunar_transit(start_time, end_time=None, obs=chime): """Find the Lunar transits between two times for CHIME. Parameters @@ -399,7 +400,7 @@ def lunar_transit(start_time, end_time=None): planets = skyfield_wrapper.ephemeris moon = planets["moon"] - return chime.transit_times(moon, start_time, end_time) + return obs.transit_times(moon, start_time, end_time) # Create CHIME specific versions of various calls. @@ -449,7 +450,7 @@ def chime_local_datetime(*args): return dt_aware.replace(tzinfo=None) - dt_aware.utcoffset() -def solar_setting(start_time, end_time=None): +def solar_setting(start_time, end_time=None, obs=chime): """Find the Solar settings between two times for CHIME. Parameters @@ -470,10 +471,10 @@ def solar_setting(start_time, end_time=None): planets = skyfield_wrapper.ephemeris sun = planets["sun"] # Use 0.6 degrees for the angular diameter of the Sun to be conservative: - return chime.set_times(sun, start_time, end_time, diameter=0.6) + return obs.set_times(sun, start_time, end_time, diameter=0.6) -def lunar_setting(start_time, end_time=None): +def lunar_setting(start_time, end_time=None, obs=chime): """Find the Lunar settings between two times for CHIME. Parameters @@ -494,10 +495,10 @@ def lunar_setting(start_time, end_time=None): planets = skyfield_wrapper.ephemeris moon = planets["moon"] # Use 0.6 degrees for the angular diameter of the Moon to be conservative: - return chime.set_times(moon, start_time, end_time, diameter=0.6) + return obs.set_times(moon, start_time, end_time, diameter=0.6) -def solar_rising(start_time, end_time=None): +def solar_rising(start_time, end_time=None, obs=chime): """Find the Solar risings between two times for CHIME. Parameters @@ -518,10 +519,10 @@ def solar_rising(start_time, end_time=None): planets = skyfield_wrapper.ephemeris sun = planets["sun"] # Use 0.6 degrees for the angular diameter of the Sun to be conservative: - return chime.rise_times(sun, start_time, end_time, diameter=0.6) + return obs.rise_times(sun, start_time, end_time, diameter=0.6) -def lunar_rising(start_time, end_time=None): +def lunar_rising(start_time, end_time=None, obs=chime): """Find the Lunar risings between two times for CHIME. Parameters @@ -542,7 +543,7 @@ def lunar_rising(start_time, end_time=None): planets = skyfield_wrapper.ephemeris moon = planets["moon"] # Use 0.6 degrees for the angular diameter of the Moon to be conservative: - return chime.rise_times(moon, start_time, end_time, diameter=0.6) + return obs.rise_times(moon, start_time, end_time, diameter=0.6) def _is_skyfield_obj(body): @@ -575,7 +576,7 @@ def Star_cirs(ra, dec, epoch): return cirs_radec(Star(ra=ra, dec=dec, epoch=epoch)) -def cirs_radec(body, date=None, deg=False): +def cirs_radec(body, date=None, deg=False, obs=chime): """Converts a Skyfield body in CIRS coordinates at a given epoch to ICRS coordinates observed from CHIME @@ -597,7 +598,7 @@ def cirs_radec(body, date=None, deg=False): epoch = ts.tt_jd(np.median(body.epoch)) - pos = chime.skyfield_obs().at(epoch).observe(body) + pos = obs.skyfield_obs().at(epoch).observe(body) # Matrix CT transforms from CIRS to ICRF (https://rhodesmill.org/skyfield/time.html) r_au, dec, ra = skyfield.functions.to_polar( @@ -609,7 +610,7 @@ def cirs_radec(body, date=None, deg=False): ) -def object_coords(body, date=None, deg=False, obs=None): +def object_coords(body, date=None, deg=False, obs=chime): """Calculates the RA and DEC of the source. Gives the ICRS coordinates if no date is given (=J2000), or if a date is @@ -649,9 +650,6 @@ def object_coords(body, date=None, deg=False, obs=None): else: # Calculate CIRS position with all corrections - if obs is None: - obs = chime - date = unix_to_skyfield_time(date) radec = obs.skyfield_obs().at(date).observe(body).apparent().cirs_radec(date) diff --git a/ch_util/tools.py b/ch_util/tools.py index 255e808b..62e32f24 100644 --- a/ch_util/tools.py +++ b/ch_util/tools.py @@ -2032,6 +2032,7 @@ def fringestop_time( csd=False, inplace=False, static_delays=True, + obs=ephemeris.chime, ): """Fringestop timestream data to a fixed source. @@ -2082,6 +2083,7 @@ def fringestop_time( prod_map=prod_map, csd=csd, static_delays=static_delays, + obs=obs, ) # Set any non CHIME feeds to have zero phase @@ -2181,7 +2183,16 @@ def decorrelation( return timestream -def delay(times, feeds, src, wterm=True, prod_map=None, csd=False, static_delays=True): +def delay( + times, + feeds, + src, + wterm=True, + prod_map=None, + csd=False, + static_delays=True, + obs=ephemeris.chime, +): """Calculate the delay in a visibilities observing a given source. This includes both the geometric delay and static (cable) delays. @@ -2211,17 +2222,13 @@ def delay(times, feeds, src, wterm=True, prod_map=None, csd=False, static_delays import scipy.constants - ra = (times % 1.0) * 360.0 if csd else ephemeris.lsa(times) - - src_ra, src_dec = ephemeris.object_coords(src, times.mean()) + ra = (times % 1.0) * 360.0 if csd else obs.unix_to_lsa(times) + src_ra, src_dec = ephemeris.object_coords(src, times.mean(), obs=obs) ha = (np.radians(ra) - src_ra)[np.newaxis, :] - - latitude = np.radians(ephemeris.CHIMELATITUDE) - + latitude = np.radians(obs.latitude) # Get feed positions / c feedpos = get_feed_positions(feeds, get_zpos=wterm) / scipy.constants.c feed_delays = np.array([f.delay for f in feeds]) - # Calculate the geometric delay between the feed and the reference position delay_ref = -projected_distance(ha, latitude, src_dec, *feedpos.T[..., np.newaxis]) From 65f6ba939b39daea2e40bf210c1f6b163117c89d Mon Sep 17 00:00:00 2001 From: Richard Shaw Date: Mon, 26 Apr 2021 14:06:11 -0700 Subject: [PATCH 5/7] style: reapply black to account for new 21.4 release --- ch_util/chan_monitor.py | 52 ++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/ch_util/chan_monitor.py b/ch_util/chan_monitor.py index 415b82b3..ab2ed04d 100644 --- a/ch_util/chan_monitor.py +++ b/ch_util/chan_monitor.py @@ -110,7 +110,7 @@ def __init__( self.bslins0_scrambled = False def set_bslns0(self): - """""" + """ """ prods = self.prods pstns0 = self.pstns0 @@ -130,7 +130,7 @@ def set_bslns0(self): return c_bslns0, bslns0 def phase_trans(self, vis, tm, tt): - """""" + """ """ ph = np.zeros((self.Nfr, self.Npr)) tridx = np.argmin(abs(tm - tt)) # Transit index @@ -144,7 +144,7 @@ def phase_trans(self, vis, tm, tt): return ph def getphases_tr(self): - """""" + """ """ self.ph1 = self.phase_trans(self.vis1, self.tm1, self.tt1) self.ph2 = self.phase_trans(self.vis2, self.tm2, self.tt2) @@ -157,7 +157,7 @@ def get_c_ydist( # TODO: add test for contiguous freqs!!! def yparams(xx, yy): - """""" + """ """ a, b = np.polyfit(xx, yy, 1) ydiff = np.diff(yy, axis=0) @@ -405,7 +405,7 @@ def gaus(x, A, mu, sig2): # TODO: change to 'get_yparams' def getparams_ft(self): - """""" + """ """ # TODO: Add test to eliminate bad fits! self.ft_prms1 = self.params_ft(self.tm1, self.vis1, self.dec1) if self.source2 is not None: @@ -435,7 +435,7 @@ def get_xdist(self, ft_prms, dec): return xdists def data_quality(self): - """""" + """ """ if self.pass_xd1 is None: if self.source2 is not None: self.xdist_test( @@ -477,14 +477,14 @@ def data_quality(self): self.set_good_ipts(self.bsipts) # good_prods to good_ipts def single_source_test(self): - """""" + """ """ self.getparams_ft() self.xdists1 = self.get_xdist(self.ft_prms1, self.dec1) self.data_quality() def get_dists(self): - """""" + """ """ # Get x distances in Earth coords (EW) self.getparams_ft() self.xdists1 = self.get_xdist(self.ft_prms1, self.dec1) @@ -535,7 +535,7 @@ def set_good_ipts(self, base_ipts): self.good_ipts[inp_list.index(bsip)] = True def solv_pos(self, dists, base_ipt): - """""" + """ """ from scipy.linalg import svd # Matrix defining order of subtraction for baseline distances @@ -561,7 +561,7 @@ def solv_pos(self, dists, base_ipt): return pstns def get_postns(self): - """""" + """ """ self.c_xd1 = np.nanmedian(self.c_xdists1[self.good_freqs], axis=0) self.c_xd2 = np.nanmedian(self.c_xdists2[self.good_freqs], axis=0) # Solve positions: @@ -574,7 +574,7 @@ def get_postns(self): return self.c_x1, self.c_x2, self.c_y def xdist_test(self, xds1, xds2=None, tol=2.0): - """""" + """ """ def get_centre(xdists, tol): """Returns the median (across frequencies) of NS separation dists for each @@ -736,7 +736,7 @@ def fromdate( bsep1=154, bsep2=218, ): - """ Initialize class from date """ + """Initialize class from date""" t1 = ephemeris.datetime_to_unix(date) return cls( t1, @@ -753,7 +753,7 @@ def fromdate( # or not allow for that possibility. @classmethod def fromdata(cls, data, freq_sel=None, prod_sel=None): - """ Initialize class from andata object """ + """Initialize class from andata object""" t1 = data.time[0] t2 = data.time[-1] return cls(t1, t2, freq_sel=freq_sel, prod_sel=prod_sel) @@ -761,7 +761,7 @@ def fromdata(cls, data, freq_sel=None, prod_sel=None): # TODO: test for adjacent freqs to pass to FeedLocator def get_src_cndts(self): - """""" + """ """ clr, ntt, srcs = self.get_sunfree_srcs() grd_dict = {"CygA": 4, "CasA": 4, "TauA": 3, "VirA": 1} @@ -790,7 +790,7 @@ def get_src_cndts(self): return src_cndts def get_pol_prod_idx(self, pol_inpt_idx): - """""" + """ """ pol_prod_idx = [] for ii, prd in enumerate(self.prods): if (prd[0] in pol_inpt_idx) and (prd[1] in pol_inpt_idx): @@ -799,7 +799,7 @@ def get_pol_prod_idx(self, pol_inpt_idx): return pol_prod_idx def get_feedlocator(self, pol=1): - """""" + """ """ if pol == 1: pol_inpt_idx = self.p1_idx bsipts = [self.bswp1, self.bsep1] @@ -849,17 +849,17 @@ def get_feedlocator(self, pol=1): return fl def init_feedloc_p1(self): - """""" + """ """ self.flp1 = self.get_feedlocator() return self.flp1 def init_feedloc_p2(self): - """""" + """ """ self.flp2 = self.get_feedlocator(pol=2) return self.flp2 def get_cyl_pol(self, corr_inputs, pwds): - """""" + """ """ wchp1, wchp2, echp1, echp2 = [], [], [], [] for ii, inpt in enumerate(corr_inputs): if pwds[ii]: @@ -890,7 +890,7 @@ def get_cyl_pol(self, corr_inputs, pwds): return [wchp1, wchp2, echp1, echp2] def get_pos_pol(self, corr_inputs, pwds): - """""" + """ """ Ninpts = len(pwds) p1_idx, p2_idx = [], [] pstns = np.zeros((Ninpts, 2)) # In-cylinder positions @@ -946,7 +946,7 @@ def determine_bad_gpu_nodes(self, data, frac_time_on=0.7): self.gpu_node_flag = np.sum(node_on, axis=1) > frac_time_on * node_on.shape[1] def get_prod_sel(self, data): - """""" + """ """ from ch_util import tools input_map = data.input @@ -996,7 +996,7 @@ def get_prod_sel(self, data): return prod_sel, pwds def get_data(self): - """""" + """ """ from ch_util import ni_utils self.set_acq_list() @@ -1204,7 +1204,7 @@ def single_source_check(self): self.results_summary() def full_check(self): - """""" + """ """ if self.source1 is None: self.get_data() if self.source2 is None: @@ -1260,7 +1260,7 @@ def full_check(self): self.results_summary() def results_summary(self): - """""" + """ """ self.bad_ipts = self.input_map[np.logical_not(self.good_ipts)] self.deemed_bad_but_good = self.input_map[ np.logical_and(np.logical_not(self.pwds), self.good_ipts) @@ -1274,7 +1274,7 @@ def results_summary(self): self.wrong_position = self.input_map[self.pos_err > 1.0] def get_test_res(self, fl): - """""" + """ """ for ii, ipt in enumerate(self.inputs): for jj, fl_ipt in enumerate(fl.inputs): if fl_ipt == ipt: @@ -1289,7 +1289,7 @@ def get_test_res(self, fl): return good_frac def get_res_sing_src(self, fl): - """""" + """ """ for ii, ipt in enumerate(self.inputs): for jj, fl_ipt in enumerate(fl.inputs): if fl_ipt == ipt: From a6ea4cd6ead8470cedcf669bb1a3d192049d6be3 Mon Sep 17 00:00:00 2001 From: Richard Shaw Date: Mon, 26 Apr 2021 14:37:43 -0700 Subject: [PATCH 6/7] test: properly skip database tests --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d81d330d..68c7e985 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,7 +56,7 @@ jobs: pip install pytest - name: Run tests - run: pytest --ignore=test_db.py tests/ + run: pytest --ignore=tests/test_db.py tests/ build-docs: From 95fd6044dba4a6a54a2d6f1033573d7f59d2b9c2 Mon Sep 17 00:00:00 2001 From: Calvin Leung Date: Wed, 28 Apr 2021 07:10:08 -0700 Subject: [PATCH 7/7] test(ephemeris): updated et1 to reflect new csd zero --- tests/test_ephemeris.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_ephemeris.py b/tests/test_ephemeris.py index 436f6b2d..f1698b7e 100644 --- a/tests/test_ephemeris.py +++ b/tests/test_ephemeris.py @@ -81,7 +81,8 @@ def test_csd(): ) # Check a specific precalculated CSD - csd1 = -1.1848347442894998 + csd1 = -1.1848449724498247 + assert ephemeris.csd(et1) == approx(csd1, abs=1e-7) # Check vectorization