Skip to content

Commit

Permalink
Merge pull request #20 from rahil-makadia/dev
Browse files Browse the repository at this point in the history
added mapped SPKs; Gaia astrometry
  • Loading branch information
rahil-makadia authored Aug 11, 2023
2 parents 7314bd9 + 18faf6c commit 16c05f3
Show file tree
Hide file tree
Showing 27 changed files with 1,116 additions and 1,131 deletions.
1 change: 1 addition & 0 deletions .github/workflows/cpp_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ jobs:
run: |
cd tests/cpp/prop
./didymos.out
./spk_map.out
124 changes: 112 additions & 12 deletions grss/fit_optical.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
import os
from astropy.time import Time
from astroquery.mpc import MPC
from astroquery.gaia import Gaia
import healpy as hp
import numpy as np
import pandas as pd
import spiceypy as spice

from .fit_utils import get_ra_from_hms, get_dec_from_dms, radec2icrf, icrf2radec, mjd2et
from .fit_utils import get_ra_from_hms, get_dec_from_dms, radec2icrf, icrf2radec, rec2lat

__all__ = [ 'get_ades_optical_obs_array',
'get_optical_obs_array',
'get_gaia_optical_obs_array',
'get_mpc_optical_obs_array',
]

def get_optical_data(body_id, optical_obs_file=None, t_min_tdb=None, t_max_tdb=None, verbose=False):
Expand Down Expand Up @@ -126,7 +128,7 @@ def get_optical_data(body_id, optical_obs_file=None, t_min_tdb=None, t_max_tdb=N
return (obs_array_optical, star_catalog_codes,
observer_codes_optical, observation_type_codes, observer_program_codes)

def get_ades_optical_obs_array(psv_obs_file, occultation_obs=False, de_kernel_path=None):
def get_ades_optical_obs_array(psv_obs_file, de_kernel_path, occultation_obs=False):
"""
Assemble the optical observations for a given body into an array for an orbit fit,
from the ADES PSV observation file.
Expand All @@ -135,10 +137,10 @@ def get_ades_optical_obs_array(psv_obs_file, occultation_obs=False, de_kernel_pa
----------
psv_obs_file : str
Path to the ADES PSV observation file
de_kernel_path : str
Path to the DE kernel file
occultation_obs : bool, optional
Flag for whether file is made of occultation measurement data, by default False
de_kernel_path : str, optional
Path to the DE kernel file, by default None
Returns
-------
Expand Down Expand Up @@ -177,12 +179,7 @@ def get_ades_optical_obs_array(psv_obs_file, occultation_obs=False, de_kernel_pa
pos_x = float(obs['pos1'])
pos_y = float(obs['pos2'])
pos_z = float(obs['pos3'])
rot_mat = spice.pxform('J2000', 'ITRF93', mjd2et(obs_times[i].tdb.mjd))
observer_pos_j2000 = np.array([pos_x, pos_y, pos_z])
rho = np.linalg.norm(observer_pos_j2000)
observer_pos_itrf93 = np.dot(rot_mat, observer_pos_j2000)
lon = np.arctan2(observer_pos_itrf93[1], observer_pos_itrf93[0])
lat = np.arcsin(observer_pos_itrf93[2]/np.linalg.norm(observer_pos_itrf93))
lon, lat, rho = rec2lat(obs_times[i].tdb.mjd, pos_x, pos_y, pos_z)
observer_codes_optical.append((str(obs['stn']), lon, lat, 1e3*rho))
# pos_itrf_x = rho*np.cos(lat)*np.cos(lon)
# pos_itrf_y = rho*np.cos(lat)*np.sin(lon)
Expand All @@ -200,6 +197,109 @@ def get_ades_optical_obs_array(psv_obs_file, occultation_obs=False, de_kernel_pa
spice.kclear()
return obs_array_optical, tuple(observer_codes_optical)

def _get_gaia_query_results(body_id, release='gaiadr3', verbose=False):
"""
Submit a Gaia archive query for a given body ID from a specific
Gaia data release.
Parameters
----------
body_id : str/int
Target id, numbers are interpreted as asteroids,
append 'P' for comets, start with comet type and a '/' for comet designations
release : str, optional
Gaia data release version database name, by default 'gaiadr3'
verbose : bool, optional
Flag to print out information about the observations, by default False
Returns
-------
res : astropy.table.Table
Query results
"""
table = 'sso_observation'
if body_id.isdigit():
match_str = f"WHERE ({release}.{table}.number_mp={body_id})"
else:
match_str = f"WHERE ({release}.{table}.denomination='{body_id.lower()}')"
query = (
"SELECT transit_id,denomination,number_mp,epoch_utc,epoch_err,"
+ "ra,dec,"
+ "ra_error_systematic,dec_error_systematic,ra_dec_correlation_systematic,"
+ "ra_error_random,dec_error_random,ra_dec_correlation_random,"
+ "x_gaia_geocentric,y_gaia_geocentric,z_gaia_geocentric,"
+ "vx_gaia_geocentric,vy_gaia_geocentric,vz_gaia_geocentric"
+ f" FROM {release}.{table} {match_str} ORDER BY epoch_utc ASC"
)
job = Gaia.launch_job_async(query, dump_to_file=False,background=True)
res = job.get_results()
res.sort('epoch_utc')
if verbose:
print(f"Found {len(res)} observations from Gaia DR3.")
return res

def get_gaia_optical_obs_array(body_id, de_kernel_path, t_min_tdb=None, t_max_tdb=None, verbose=False):
"""
Assemble the optical observations for a given body from Gaia DR3.
Parameters
----------
body_id : str/int
Target id, numbers are interpreted as asteroids,
append 'P' for comets, start with comet type and a '/' for comet designations
de_kernel_path : str
Path to the DE kernel file
t_min_tdb : float, optional
Minimum time (MJD TDB) for observations to be included, by default None
t_max_tdb : float, optional
Maximum time (MJD TDB) for observations to be included, by default None
verbose : bool, optional
Flag to print out information about the observations, by default False
Returns
-------
obs_array_optical : array
Optical observation data for the given body
observer_codes_optical : tuple
Observer locations for each observation in obs_array_optical
"""
if t_min_tdb is None:
t_min_tdb = -np.inf
if t_max_tdb is None:
t_max_tdb = np.inf
# get gaia query results
res = _get_gaia_query_results(body_id, verbose=verbose)
spice.furnsh(de_kernel_path)
au2km = 149597870.7
obs_array = np.nan*np.ones((len(res), 6))
observer_codes = []
for i, data in enumerate(res):
if i > 0 and res[i]['transit_id'] == res[i-1]['transit_id']:
continue
obs_time = Time(data['epoch_utc'] + 55197.0, format='mjd', scale='utc')
if obs_time.tdb.mjd < t_min_tdb or obs_time.tdb.mjd > t_max_tdb:
continue
cosdec = np.cos(data['dec']*np.pi/180)
obs_array[i, 0] = obs_time.utc.mjd
obs_array[i, 1] = data['ra']*3600
obs_array[i, 1]-= data['ra_error_systematic']/cosdec/1000
obs_array[i, 2] = data['dec']*3600
obs_array[i, 2]-= data['dec_error_systematic']/1000
obs_array[i, 3] = data['ra_error_random']/cosdec/1000
obs_array[i, 4] = data['dec_error_random']/1000
obs_array[i, 5] = data['ra_dec_correlation_random']
pos_x = data['x_gaia_geocentric']*au2km
pos_y = data['y_gaia_geocentric']*au2km
pos_z = data['z_gaia_geocentric']*au2km
lon, lat, rho = rec2lat(obs_time.tdb.mjd, pos_x, pos_y, pos_z)
observer_codes.append(('258', lon, lat, rho*1e3))
spice.kclear()
non_nan_idx = ~np.isnan(obs_array[:, 0])
obs_array = obs_array[non_nan_idx, :]
if verbose:
print(f"\t Added {len(obs_array)} of those observations.")
return obs_array, tuple(observer_codes)

def debias_obs(r_asc, dec, epoch, catalog, biasdf, nside=256):
"""
Calculate the bias on an optical observation given the epoch and the star catalog.
Expand Down Expand Up @@ -750,7 +850,7 @@ def eliminate_obs(obs_array_optical, star_catalog_codes, observer_codes_optical,
"as part of elimination scheme.")
return obs_array_eliminated, tuple(catalog_eliminated), tuple(observer_loc_eliminated)

def get_optical_obs_array(body_id, optical_obs_file=None, t_min_tdb=None, t_max_tdb=None,
def get_mpc_optical_obs_array(body_id, optical_obs_file=None, t_min_tdb=None, t_max_tdb=None,
debias=True, debias_lowres=False, deweight=True, eliminate=False,
num_obs_per_night=5, verbose=False):
"""
Expand Down
27 changes: 18 additions & 9 deletions grss/fit_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,13 @@ def get_computed_obs(self, prop_sim_past, prop_sim_future, integ_body_idx):
obs_info_len = len(observer_info[i])
if obs_info_len in {4, 7}:
computed_obs[i, :] = get_radec(apparent_states[i])
# if self.obs_array[i, 3] < 10e-3 or self.obs_array[i, 4] < 10e-3:
# dist = np.linalg.norm(apparent_states[i, :3])*1.495978707e11
# ang_diam = 2*np.arctan(self.fixed_propsim_params['radius']/dist)
# ang_diam *= 180/np.pi*3600
# print(f'Angular diameter at {self.obs_array[i, 0]} is {ang_diam} arcsec. ',
# f'RA uncertainty fraction is {self.obs_array[i, 3]/ang_diam}. ',
# f'Dec uncertainty fraction is {self.obs_array[i, 4]/ang_diam}.')
elif obs_info_len == 9: # delay measurement
computed_obs[i, 0] = radar_observations[i]
elif obs_info_len == 10: # dopper measurement
Expand Down Expand Up @@ -1779,7 +1786,7 @@ def _generate_simulated_obs(ref_sol, ref_cov, ref_ng_info, events, modified_obs_
or cometary state.
"""
obs_sigma_dict = { 'astrometry': 1, # arcsec
'occultation': 2.2e-3, # arcsec
'occultation': 0.25, # fraction of body angular diameter
'delay': 2, # microseconds
# 15 meter (conservative since it is 1m random + 10m systematic)
'delay_hera': 15*2 /299792458*1e6, # microseconds
Expand Down Expand Up @@ -1929,6 +1936,12 @@ def _generate_simulated_obs(ref_sol, ref_cov, ref_ng_info, events, modified_obs_
obs_array_optical[optical_idx, 2] = dec
obs_array_optical[optical_idx, 3] = obs_sigma_dict[typ]
obs_array_optical[optical_idx, 4] = obs_sigma_dict[typ]
if typ == 'occultation':
dist = np.linalg.norm(apparent_states[idx, :3])*1.495978707e11
ang_diam = 2*np.arctan(ref_sol['radius'] / dist)
ang_diam *= 180/np.pi*3600
obs_array_optical[optical_idx, 3] *= ang_diam
obs_array_optical[optical_idx, 4] *= ang_diam
obs_array_optical[optical_idx, 5] = 0.0
if noise:
ra_noise = np.random.normal(0, obs_array_optical[optical_idx, 3])
Expand Down Expand Up @@ -2033,10 +2046,8 @@ def create_simulated_obs_arrays(simulated_traj_info, real_obs_arrays, simulated_
obs_array_optical = obs_array_optical[sort_idx]
observer_codes_optical = tuple(observer_codes_optical[i] for i in sort_idx)
optical_obs_types = tuple(optical_obs_types[i] for i in sort_idx)
simulated_optical_obs_idx = []
for i, typ in enumerate(optical_obs_types):
if typ != 'actual_obs_optical':
simulated_optical_obs_idx.append(i)
simulated_optical_obs_idx = [i for i, typ in enumerate(optical_obs_types)
if typ != 'actual_obs_optical']
simulated_radar_obs_idx = np.where(radar_obs_times >= simulated_obs_start_time)[0]
simulated_radar_obs_times = tuple(radar_obs_times[simulated_radar_obs_idx])
radar_obs_types = ['actual_obs_radar']*(len(radar_obs_times)-len(simulated_radar_obs_times))
Expand Down Expand Up @@ -2071,10 +2082,8 @@ def create_simulated_obs_arrays(simulated_traj_info, real_obs_arrays, simulated_
obs_array_radar = obs_array_radar[sort_idx]
observer_codes_radar = tuple(observer_codes_radar[i] for i in sort_idx)
radar_obs_types = tuple(radar_obs_types[i] for i in sort_idx)
simulated_radar_obs_idx = []
for i, typ in enumerate(radar_obs_types):
if typ != 'actual_obs_radar':
simulated_radar_obs_idx.append(i)
simulated_radar_obs_idx = [i for i, typ in enumerate(radar_obs_types)
if typ != 'actual_obs_radar']
simulated_obs_ref_sol = x_nom.copy()
simulated_obs_ref_sol['mass'] = 0.0
simulated_obs_ref_sol['radius'] = target_radius
Expand Down
41 changes: 39 additions & 2 deletions grss/fit_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from requests import request
import numpy as np
from numba import jit
import spiceypy as spice

__all__ = [ 'get_ra_from_hms',
'get_dec_from_dms',
Expand Down Expand Up @@ -326,8 +327,11 @@ def get_observer_info(observer_codes):
if len(code) == 3: # add tranmission frequency if dopppler observation
freq = code[2]
info_list.append(freq)
# for geocentric occultations code is a tuple but needs to be decomposed
elif isinstance(code, tuple) and code[0] in {'275', 'S/C'}:
# code is a tuple but needs to be decomposed for the follwoing obs types:
# Gaia spacecraft (258)
# Occultations (275)
# generic spacecraft measurements (S/C)
elif isinstance(code, tuple) and code[0] in {'258', '275', 'S/C'}:
info_list.extend((body_id, code[1], code[2], code[3]))
# info_list.extend((500, code[1], code[2], code[3], code[4], code[5], code[6]))
else:
Expand Down Expand Up @@ -517,6 +521,39 @@ def radec2icrf(r_asc, dec, deg=False):
pos_z = np.sin(delta)
return np.array([pos_x, pos_y, pos_z])

def rec2lat(time, x_j2000, y_j2000, z_j2000):
"""
Convert rectangular coordinates (geocentric J2000) to latitudinal
coordinates (geocentric ITRF93).
Parameters
----------
time : float
Time in MJD TDB
x_j2000 : float
x-coordinate in geocentric J2000 frame
y_j2000 : float
y-coordinate in geocentric J2000 frame
z_j2000 : float
z-coordinate in geocentric J2000 frame
Returns
-------
lon : float
Longitude in radians
lat : float
Latitude in radians
rho : float
Distance from center of Earth in km
"""
rot_mat = spice.pxform('J2000', 'ITRF93', mjd2et(time))
observer_pos_j2000 = np.array([x_j2000, y_j2000, z_j2000])
rho = np.linalg.norm(observer_pos_j2000)
observer_pos_itrf93 = np.dot(rot_mat, observer_pos_j2000)
lon = np.arctan2(observer_pos_itrf93[1], observer_pos_itrf93[0])
lat = np.arcsin(observer_pos_itrf93[2]/np.linalg.norm(observer_pos_itrf93))
return lon, lat, rho

def get_similarity_stats(sol_1, cov_1, sol_2, cov_2):
"""
Get similarity statistics between two solutions. This includes the
Expand Down
15 changes: 14 additions & 1 deletion grss/kernels/get_kernels.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

GRSS_SITE = 'https://github.com/rahil-makadia/grss/raw/dev/grss/kernels'
NAIF_SITE = 'https://naif.jpl.nasa.gov/pub/naif/generic_kernels'
SSD_SITE = 'https://ssd.jpl.nasa.gov/ftp'
# get the custom spice kernels if they are not already present
# de431 planets + big16 1950-2350
os.system((f'wget --no-verbose --no-clobber {GRSS_SITE}/planets_big16_de431_1950_2350.bsp '
Expand All @@ -20,6 +21,18 @@
os.system((f'wget --no-verbose --no-clobber {GRSS_SITE}/planets_big16_de441_1950_2350.tm '
f'-O {script_dir}/planets_big16_de441_1950_2350.tm'))

# get the generic spice kernels if they are not already present
# de430 planets + de431 big16
os.system((f'wget --no-verbose --no-clobber {NAIF_SITE}/spk/planets/de430.bsp '
f'-O {script_dir}/de430.bsp'))
os.system((f'wget --no-verbose --no-clobber {SSD_SITE}/xfr/sb431-n16s.bsp '
f'-O {script_dir}/sb431-n16s.bsp'))
# de440 planets + de441 big16
os.system((f'wget --no-verbose --no-clobber {NAIF_SITE}/spk/planets/de440.bsp '
f'-O {script_dir}/de440.bsp'))
os.system((f'wget --no-verbose --no-clobber {SSD_SITE}/xfr/sb441-n16s.bsp '
f'-O {script_dir}/sb441-n16s.bsp'))

# get the latest spice leap second kernel
os.system((f'wget --no-verbose --no-clobber {NAIF_SITE}/lsk/latest_leapseconds.tls '
f'-O {script_dir}/latest_leapseconds.tls'))
Expand Down Expand Up @@ -77,7 +90,7 @@
num_chunks = 1
lines[i] = f" PATH_VALUES = ( '{script_dir}" + "' )\n"
if 'PATH_SYMBOLS' in line and "'GRSS'" in line and num_chunks > 1:
# replace PATH_SYMBOLS = ( 'GRSS' ) with PATH_SYMBOLS = ( 'GRSS_1', 'GRSS_2', ... )
# replace PATH_SYMBOLS = ( 'GRSS' ) with PATH_SYMBOLS = ( 'GRSS_1', ... )
lines[i] = " PATH_SYMBOLS = ( 'GRSS1',\n"
for j in range(2, num_chunks+1):
end_char = " )" if j == num_chunks else ","
Expand Down
2 changes: 2 additions & 0 deletions grss/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def default_kernel_path(kernel_version=0):
"""
if kernel_version == 0:
return f'{grss_kernel_path}/planets_big16_de441_1950_2350.tm'
if kernel_version in {430, 440}:
kernel_version += 1
file = f'{grss_kernel_path}/planets_big16_de{kernel_version}_1950_2350.tm'
if os.path.isfile(file):
return file
Expand Down
2 changes: 1 addition & 1 deletion grss/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.8.5
0.9.3
21 changes: 2 additions & 19 deletions include/force.h
Original file line number Diff line number Diff line change
@@ -1,26 +1,9 @@
#ifndef FORCE_H
#define FORCE_H
#include "utilities.h"

struct ForceParameters {
std::vector<real> masses;
std::vector<real> radii;
std::vector<int> spiceIdList;
std::vector<NongravParamaters> ngParamsList;
std::vector<bool> isPPNList;
std::vector<bool> isJ2List;
std::vector<real> J2List;
std::vector<real> poleRAList;
std::vector<real> poleDecList;
std::vector<bool> isNongravList;
std::vector<bool> isMajorList;
std::vector<bool> isThrustingList;
};
#include "simulation.h"

std::vector<real> get_state_der(const real &t, const std::vector<real> &xInteg,
const ForceParameters &forceParams,
const IntegrationParameters &integParams,
const Constants &consts);
propSimulation *propSim);
void force_newton(const std::vector<real> &posAll, std::vector<real> &xDotInteg,
const ForceParameters &forceParams,
const IntegrationParameters &integParams,
Expand Down
4 changes: 1 addition & 3 deletions include/gr15.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ const real cMat[8][8] = {
};

real get_initial_timestep(const real &t, const std::vector<real> &xInteg0,
const ForceParameters &forceParams,
IntegrationParameters &integParams,
const Constants &consts);
propSimulation *propSim);
void compute_g_and_b(const std::vector<std::vector<real> > &AccIntegArr,
const size_t &hIdx, std::vector<std::vector<real> > &g,
std::vector<std::vector<real> > &b, const size_t &dim);
Expand Down
Loading

0 comments on commit 16c05f3

Please sign in to comment.