From 850a26b7ba10f5d927d671a9edf31ad346ede957 Mon Sep 17 00:00:00 2001 From: Ayush Garg Date: Mon, 24 Mar 2025 22:57:28 -0700 Subject: [PATCH 1/7] added vec_table option to vectors_async and updated parse_result to support more vector table options --- astroquery/jplhorizons/__init__.py | 6 ++++++ astroquery/jplhorizons/core.py | 24 ++++++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/astroquery/jplhorizons/__init__.py b/astroquery/jplhorizons/__init__.py index b910dd1de7..79fb02ef91 100644 --- a/astroquery/jplhorizons/__init__.py +++ b/astroquery/jplhorizons/__init__.py @@ -229,6 +229,12 @@ class Conf(_config.ConfigNamespace): 'VX': ('vx', 'AU/d'), 'VY': ('vy', 'AU/d'), 'VZ': ('vz', 'AU/d'), + 'X_s': ('x_s', 'AU'), + 'Y_s': ('y_s', 'AU'), + 'Z_s': ('z_s', 'AU'), + 'VX_s': ('vx_s', 'AU/d'), + 'VY_s': ('vy_s', 'AU/d'), + 'VZ_s': ('vz_s', 'AU/d'), 'LT': ('lighttime', 'd'), 'RG': ('range', 'AU'), 'RR': ('range_rate', diff --git a/astroquery/jplhorizons/core.py b/astroquery/jplhorizons/core.py index 34bf0f9f06..19bd770e41 100644 --- a/astroquery/jplhorizons/core.py +++ b/astroquery/jplhorizons/core.py @@ -959,8 +959,8 @@ def elements_async(self, *, get_query_payload=False, def vectors_async(self, *, get_query_payload=False, closest_apparition=False, no_fragments=False, get_raw_response=False, cache=True, - refplane='ecliptic', aberrations='geometric', - delta_T=False,): + refplane='ecliptic', vec_table="3", + aberrations='geometric', delta_T=False,): """ Query JPL Horizons for state vectors. @@ -1057,6 +1057,14 @@ def vectors_async(self, *, get_query_payload=False, See :ref:`Horizons Reference Frames ` in the astroquery documentation for details. + + vec_table : string, optional + Selects the table of vectors to be returned. Options are numbers 1-6, + followed by any string of characters in the list [``'x'``, ``'a'``, + ``'r'``, ``'p'``]. Default: ``'3'``. + + See `Horizons User Manual `_ + for details. aberrations : string, optional Aberrations to be accounted for: [``'geometric'``, @@ -1156,6 +1164,7 @@ def vectors_async(self, *, get_query_payload=False, ('VEC_CORR', {'geometric': '"NONE"', 'astrometric': '"LT"', 'apparent': '"LT+S"'}[aberrations]), + ('VEC_TABLE', vec_table), ('VEC_DELTA_T', {True: 'YES', False: 'NO'}[delta_T]), ('OBJ_DATA', 'YES')] ) @@ -1314,16 +1323,19 @@ def _parse_result(self, response, verbose=None): elif (self.query_type == 'elements' and "JDTDB," in line): headerline = str(line).split(',') headerline[-1] = '_dump' - # read in vectors header line - elif (self.query_type == 'vectors' and "JDTDB," in line): - headerline = str(line).split(',') - headerline[-1] = '_dump' # identify end of data block if "$$EOE" in line: data_end_idx = idx # identify start of data block if "$$SOE" in line: data_start_idx = idx + 1 + + # read in vectors header line + # reading like this helps fix issues with commas after JDTDB + if self.query_type == 'vectors': + headerline_raw = str(src[idx - 2]).replace("JDTDB,", "JDTDB") + headerline = [" JDTDB", *str(headerline_raw).split("JDTDB")[1].split(',')] + headerline[-1] = '_dump' # read in targetname if "Target body name" in line: targetname = line[18:50].strip() From 8261ebaaa5da882c77fdd77c067a02fe481b901b Mon Sep 17 00:00:00 2001 From: Ayush Garg Date: Mon, 24 Mar 2025 23:16:51 -0700 Subject: [PATCH 2/7] added all other modifier codes --- astroquery/jplhorizons/__init__.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/astroquery/jplhorizons/__init__.py b/astroquery/jplhorizons/__init__.py index 79fb02ef91..e47b314e1a 100644 --- a/astroquery/jplhorizons/__init__.py +++ b/astroquery/jplhorizons/__init__.py @@ -229,12 +229,36 @@ class Conf(_config.ConfigNamespace): 'VX': ('vx', 'AU/d'), 'VY': ('vy', 'AU/d'), 'VZ': ('vz', 'AU/d'), + 'X_s': ('x_s', 'AU'), 'Y_s': ('y_s', 'AU'), 'Z_s': ('z_s', 'AU'), 'VX_s': ('vx_s', 'AU/d'), 'VY_s': ('vy_s', 'AU/d'), 'VZ_s': ('vz_s', 'AU/d'), + + 'A_s': ('a_s', 'AU'), + 'C_s': ('c_s', 'AU'), + 'N_s': ('n_s', 'AU'), + 'VA_s': ('va_s', 'AU/d'), + 'VC_s': ('vc_s', 'AU/d'), + 'VN_s': ('vn_s', 'AU/d'), + + 'R_s': ('r_s', 'AU'), + 'T_s': ('t_s', 'AU'), + 'N_s': ('n_s', 'AU'), + 'VR_s': ('vr_s', 'AU/d'), + 'VT_s': ('vt_s', 'AU/d'), + 'VN_s': ('vn_s', 'AU/d'), + + # Note: A_s is duplicated for POS (p) and ACN (a) uncertainties. It + # is up to the user to differentiate them! (They have the same units.) + 'D_s': ('d_s', 'AU'), + 'R_s': ('r_s', 'AU'), + 'VA_RA_s': ('va_ra_s', 'AU/d'), + 'VA_DEC_s': ('va_dec_s', 'AU/d'), + # VR_s is repeated here. + 'LT': ('lighttime', 'd'), 'RG': ('range', 'AU'), 'RR': ('range_rate', From b57093d2aa75809cd53f1b125db1c28d5440769b Mon Sep 17 00:00:00 2001 From: Ayush Garg Date: Mon, 24 Mar 2025 23:19:14 -0700 Subject: [PATCH 3/7] fixed repeat --- astroquery/jplhorizons/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroquery/jplhorizons/__init__.py b/astroquery/jplhorizons/__init__.py index e47b314e1a..1bdb658b7a 100644 --- a/astroquery/jplhorizons/__init__.py +++ b/astroquery/jplhorizons/__init__.py @@ -249,7 +249,7 @@ class Conf(_config.ConfigNamespace): 'N_s': ('n_s', 'AU'), 'VR_s': ('vr_s', 'AU/d'), 'VT_s': ('vt_s', 'AU/d'), - 'VN_s': ('vn_s', 'AU/d'), + # VN_s is repeated here. # Note: A_s is duplicated for POS (p) and ACN (a) uncertainties. It # is up to the user to differentiate them! (They have the same units.) From 9846200f00a6742cf3af15ff0a2fda13b7a3a76b Mon Sep 17 00:00:00 2001 From: Ayush Garg Date: Mon, 24 Mar 2025 23:21:37 -0700 Subject: [PATCH 4/7] fixed repeat --- astroquery/jplhorizons/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/astroquery/jplhorizons/__init__.py b/astroquery/jplhorizons/__init__.py index 1bdb658b7a..48875107c3 100644 --- a/astroquery/jplhorizons/__init__.py +++ b/astroquery/jplhorizons/__init__.py @@ -246,15 +246,15 @@ class Conf(_config.ConfigNamespace): 'R_s': ('r_s', 'AU'), 'T_s': ('t_s', 'AU'), - 'N_s': ('n_s', 'AU'), + # N_s is repeated here. 'VR_s': ('vr_s', 'AU/d'), 'VT_s': ('vt_s', 'AU/d'), - # VN_s is repeated here. + # VN_s is repeated here. # Note: A_s is duplicated for POS (p) and ACN (a) uncertainties. It # is up to the user to differentiate them! (They have the same units.) 'D_s': ('d_s', 'AU'), - 'R_s': ('r_s', 'AU'), + # R_s is repeated here. 'VA_RA_s': ('va_ra_s', 'AU/d'), 'VA_DEC_s': ('va_dec_s', 'AU/d'), # VR_s is repeated here. From 01d3f9a11bd68e6ec046a379f944afdd5ac2698b Mon Sep 17 00:00:00 2001 From: Ayush Garg Date: Tue, 25 Mar 2025 15:12:51 -0700 Subject: [PATCH 5/7] added duplicate support for column names --- astroquery/jplhorizons/__init__.py | 2 +- astroquery/jplhorizons/core.py | 28 +++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/astroquery/jplhorizons/__init__.py b/astroquery/jplhorizons/__init__.py index 48875107c3..8895ab1a7b 100644 --- a/astroquery/jplhorizons/__init__.py +++ b/astroquery/jplhorizons/__init__.py @@ -256,7 +256,7 @@ class Conf(_config.ConfigNamespace): 'D_s': ('d_s', 'AU'), # R_s is repeated here. 'VA_RA_s': ('va_ra_s', 'AU/d'), - 'VA_DEC_s': ('va_dec_s', 'AU/d'), + 'VD_DEC_s': ('va_dec_s', 'AU/d'), # VR_s is repeated here. 'LT': ('lighttime', 'd'), diff --git a/astroquery/jplhorizons/core.py b/astroquery/jplhorizons/core.py index 19bd770e41..cf83df5978 100644 --- a/astroquery/jplhorizons/core.py +++ b/astroquery/jplhorizons/core.py @@ -1416,6 +1416,20 @@ def _parse_result(self, response, verbose=None): # strip whitespaces from column labels headerline = [h.strip() for h in headerline] + # add numbers to duplicates + headerline_seen = {} # format - column_name: [headerline_idx, count] + dup_col_to_orig = {} # format - remapped_column_name: [original_column_name, index], used for later processing + for i, col in enumerate(headerline): + if col in headerline_seen: + headerline_seen[col][1] += 1 + headerline[headerline_seen[col][0]] = f"{col}_1" + dup_col_to_orig[f"{col}_1"] = [col, 1] + + headerline[i] = f"{col}_{headerline_seen[col][1]}" + dup_col_to_orig[headerline[i]] = [col, headerline_seen[col][1]] + else: + headerline_seen[col] = [i, 1] + # remove all 'Cut-off' messages raw_data = [line for line in src[data_start_idx:data_end_idx] if 'Cut-off' not in line] @@ -1481,14 +1495,22 @@ def _parse_result(self, response, verbose=None): # set column units rename = [] for col in data.columns: - data[col].unit = column_defs[col][1] - if data[col].name != column_defs[col][0]: + # fetch from original definition, not remapped + col_unit = column_defs[dup_col_to_orig[col][0] if col in dup_col_to_orig.keys() else col] + + data[col].unit = col_unit[1] + if data[col].name != col_unit[0]: rename.append(data[col].name) # rename columns for col in rename: try: - data.rename_column(data[col].name, column_defs[col][0]) + if col in dup_col_to_orig.keys(): # preserve index on duplicate columns + to_rename = f"{column_defs[dup_col_to_orig[col][0]][0]}_{dup_col_to_orig[col][1]}" + else: + to_rename = column_defs[col][0] + + data.rename_column(data[col].name, to_rename) except KeyError: pass From 9685fb8ec502e22c36eba943e7e7252721e3210f Mon Sep 17 00:00:00 2001 From: Ayush Garg Date: Sat, 29 Mar 2025 13:38:55 -0700 Subject: [PATCH 6/7] added tests --- .../jplhorizons/tests/test_jplhorizons.py | 1 + .../tests/test_jplhorizons_remote.py | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/astroquery/jplhorizons/tests/test_jplhorizons.py b/astroquery/jplhorizons/tests/test_jplhorizons.py index 61440e0db2..b7e17c714d 100644 --- a/astroquery/jplhorizons/tests/test_jplhorizons.py +++ b/astroquery/jplhorizons/tests/test_jplhorizons.py @@ -268,6 +268,7 @@ def test_vectors_query_payload(): ('TP_TYPE', 'ABSOLUTE'), ('VEC_LABELS', 'YES'), ('VEC_CORR', '"NONE"'), + ('VEC_TABLE', '3'), ('VEC_DELTA_T', 'NO'), ('OBJ_DATA', 'YES'), ('CENTER', "'500@10'"), diff --git a/astroquery/jplhorizons/tests/test_jplhorizons_remote.py b/astroquery/jplhorizons/tests/test_jplhorizons_remote.py index 518e50222f..806e391b49 100644 --- a/astroquery/jplhorizons/tests/test_jplhorizons_remote.py +++ b/astroquery/jplhorizons/tests/test_jplhorizons_remote.py @@ -353,6 +353,42 @@ def test_vectors_query(self): res['vz'], res['lighttime'], res['range'], res['range_rate']], rtol=1e-3) + + def test_vectors_query_two(self): + # check values of Ceres for a given epoch, with vec_table="2xarp" to get all possible information + # orbital uncertainty of Ceres is basically zero + res = jplhorizons.Horizons(id='Ceres', location='500@10', + id_type='smallbody', + epochs=2451544.5).vectors(vec_table="2xarp",)[0] + + assert res['targetname'] == "1 Ceres (A801 AA)" + assert res['datetime_str'] == "A.D. 2000-Jan-01 00:00:00.0000" + + assert_quantity_allclose( + [2451544.5, + -2.377530292832982E+00, 8.007772359639206E-01, + 4.628376133882323E-01, -3.605422228805115E-03, + -1.057883336698096E-02, 3.379790443661611E-04, + 1.69636278E-10, 4.38650236E-10, 1.97923277E-10, + 1.94851194E-12, 5.56918627E-13, 2.05288488E-12, + 4.69444042E-10, 2.17944530E-11, 1.98774780E-10, + 8.81447488E-14, 1.83081418E-12, 2.22745222E-12, + 2.54838011E-11, 4.69258226E-10, 1.98774780E-10, + 1.83140694E-12, 7.48243991E-14, 2.22745222E-12, + 4.68686492E-10, 2.00119134E-10, 2.54838011E-11, + 1.17091788E-13, 2.22563061E-12, 1.83140694E-12,], + [res['datetime_jd'], + res['x'], res['y'], + res['z'], res['vx'], + res['vy'], res['vz'], + res['x_s'], res['y_s'], res['z_s'], + res['vx_s'], res['vy_s'], res['vz_s'], + res['a_s_1'], res['c_s'], res['n_s_1'], + res['va_s'], res['vc_s'], res['vn_s_1'], + res['r_s_1'], res['t_s'], res['n_s_2'], + res['vr_s_1'], res['vt_s'], res['vn_s_2'], + res['a_s_2'], res['d_s'], res['r_s_2'], + res['va_ra_s'], res['va_dec_s'], res['vr_s_2'],], rtol=1e-3) def test_vectors_query_raw(self): # deprecated as of #2418 From 0e15737fa98c1ae35e4d1ab71edc1d3ffdd6b71f Mon Sep 17 00:00:00 2001 From: Ayush Garg Date: Wed, 30 Apr 2025 23:23:19 -0700 Subject: [PATCH 7/7] renamed to vector_table, fixed api link --- astroquery/jplhorizons/core.py | 8 ++++---- astroquery/jplhorizons/tests/test_jplhorizons_remote.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/astroquery/jplhorizons/core.py b/astroquery/jplhorizons/core.py index cf83df5978..4851a45852 100644 --- a/astroquery/jplhorizons/core.py +++ b/astroquery/jplhorizons/core.py @@ -959,7 +959,7 @@ def elements_async(self, *, get_query_payload=False, def vectors_async(self, *, get_query_payload=False, closest_apparition=False, no_fragments=False, get_raw_response=False, cache=True, - refplane='ecliptic', vec_table="3", + refplane='ecliptic', vector_table="3", aberrations='geometric', delta_T=False,): """ Query JPL Horizons for state vectors. @@ -1058,12 +1058,12 @@ def vectors_async(self, *, get_query_payload=False, See :ref:`Horizons Reference Frames ` in the astroquery documentation for details. - vec_table : string, optional + vector_table : string, optional Selects the table of vectors to be returned. Options are numbers 1-6, followed by any string of characters in the list [``'x'``, ``'a'``, ``'r'``, ``'p'``]. Default: ``'3'``. - See `Horizons User Manual `_ + See `Horizons User Manual `_ for details. aberrations : string, optional @@ -1164,7 +1164,7 @@ def vectors_async(self, *, get_query_payload=False, ('VEC_CORR', {'geometric': '"NONE"', 'astrometric': '"LT"', 'apparent': '"LT+S"'}[aberrations]), - ('VEC_TABLE', vec_table), + ('VEC_TABLE', vector_table), ('VEC_DELTA_T', {True: 'YES', False: 'NO'}[delta_T]), ('OBJ_DATA', 'YES')] ) diff --git a/astroquery/jplhorizons/tests/test_jplhorizons_remote.py b/astroquery/jplhorizons/tests/test_jplhorizons_remote.py index 806e391b49..7f9a65d37b 100644 --- a/astroquery/jplhorizons/tests/test_jplhorizons_remote.py +++ b/astroquery/jplhorizons/tests/test_jplhorizons_remote.py @@ -355,11 +355,11 @@ def test_vectors_query(self): res['range_rate']], rtol=1e-3) def test_vectors_query_two(self): - # check values of Ceres for a given epoch, with vec_table="2xarp" to get all possible information + # check values of Ceres for a given epoch, with vector_table="2xarp" to get all possible information # orbital uncertainty of Ceres is basically zero res = jplhorizons.Horizons(id='Ceres', location='500@10', id_type='smallbody', - epochs=2451544.5).vectors(vec_table="2xarp",)[0] + epochs=2451544.5).vectors(vector_table="2xarp",)[0] assert res['targetname'] == "1 Ceres (A801 AA)" assert res['datetime_str'] == "A.D. 2000-Jan-01 00:00:00.0000"