Skip to content

Commit

Permalink
Merge pull request #81 from rahil-makadia/dev
Browse files Browse the repository at this point in the history
update ca/impact b-plane analysis; minor OD updates
  • Loading branch information
rahil-makadia authored Oct 29, 2024
2 parents 0daf189 + ce2708e commit d015b93
Show file tree
Hide file tree
Showing 18 changed files with 1,041 additions and 666 deletions.
127 changes: 66 additions & 61 deletions grss/fit/fit_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
import datetime
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from astropy.time import Time

Expand Down Expand Up @@ -562,7 +563,7 @@ def __init__(self, x_init, obs_df, cov_init=None, n_iter_max=10,
self.fixed_propsim_params[key] = nongrav_info[key]
self.fixed_propsim_params['events'] = events if events is not None else []
self.prior_est = None
self.prior_sigs = None
self.prior_sig = None
self._priors_given = False
self._xbar0 = None
self._info0 = None
Expand All @@ -574,6 +575,7 @@ def __init__(self, x_init, obs_df, cov_init=None, n_iter_max=10,
num_auto_rejected = np.sum(sel_ast == 'D')
num_force_rejected = np.sum(sel_ast == 'd')
self.num_rejected = num_auto_rejected + num_force_rejected
self.info_mats = []
self.converged = False
return None

Expand Down Expand Up @@ -642,10 +644,10 @@ def _check_priors(self):
ValueError
If the prior estimates and sigmas do not have the same length.
"""
# check that the keys in self.x_init are the same as in self.prior_est and self.prior_sigs
if self.prior_est is not None and self.prior_sigs is not None:
# check that the keys in self.x_init are the same as in self.prior_est and self.prior_sig
if self.prior_est is not None and self.prior_sig is not None:
self._priors_given = True
if len(self.prior_est) != len(self.prior_sigs):
if len(self.prior_est) != len(self.prior_sig):
raise ValueError("Prior estimates and sigmas must have the same length.")
for key in self.prior_est.keys():
if key not in self.x_init:
Expand All @@ -654,7 +656,7 @@ def _check_priors(self):
self._xbar0 = np.zeros(self.n_fit)
for key in self.prior_est.keys():
idx = list(self.x_init.keys()).index(key)
self._info0[idx, idx] = 1/self.prior_sigs[key]**2
self._info0[idx, idx] = 1/self.prior_sig[key]**2
self._xbar0[idx] = self.prior_est[key] - self.x_init[key]
self._prior_constant = list(self.x_nom.values())+self._xbar0
return None
Expand Down Expand Up @@ -693,56 +695,60 @@ def _add_simulated_obs(self):
raise ValueError("Simulated observation data must have the same length.")
perm_id = self.obs['permID'][0]
prov_id = self.obs['provID'][0]
new_sim_obs = []
for i, time in enumerate(times):
idx = len(self.obs)
this_obs = {}
mode = modes[i]
if mode not in {'SIM_CCD', 'SIM_OCC', 'SIM_RAD_DEL', 'SIM_RAD_DOP'}:
raise ValueError(f"Unknown simulated observation mode {mode}.")
weight = weights[i]
if isinstance(perm_id, str):
self.obs.loc[idx, 'permID'] = f'SIM_{perm_id}'
this_obs['permID'] = f'SIM_{perm_id}'
if isinstance(prov_id, str):
self.obs.loc[idx, 'provID'] = f'SIM_{prov_id}'
self.obs.loc[idx, 'obsTime'] = f'{time.utc.isot}Z'
self.obs.loc[idx, 'obsTimeMJD'] = time.utc.mjd
self.obs.loc[idx, 'obsTimeMJDTDB'] = time.tdb.mjd
self.obs.loc[idx, 'mode'] = mode
this_obs['provID'] = f'SIM_{prov_id}'
this_obs['obsTime'] = f'{time.utc.isot}Z'
this_obs['obsTimeMJD'] = time.utc.mjd
this_obs['obsTimeMJDTDB'] = time.tdb.mjd
this_obs['mode'] = mode
if mode in {'SIM_CCD', 'SIM_OCC'}:
self.obs.loc[idx, 'stn'] = 'SIM'
self.obs.loc[idx, 'ra'] = np.nan
self.obs.loc[idx, 'rmsRA'] = weight[0]
self.obs.loc[idx, 'sigRA'] = weight[0]
self.obs.loc[idx, 'dec'] = np.nan
self.obs.loc[idx, 'rmsDec'] = weight[1]
self.obs.loc[idx, 'sigDec'] = weight[1]
self.obs.loc[idx, 'rmsCorr'] = weight[2]
self.obs.loc[idx, 'sigCorr'] = weight[2]
self.obs.loc[idx, 'cosDec'] = 1.0
this_obs['stn'] = 'SIM'
this_obs['ra'] = np.nan
this_obs['rmsRA'] = weight[0]
this_obs['sigRA'] = weight[0]
this_obs['dec'] = np.nan
this_obs['rmsDec'] = weight[1]
this_obs['sigDec'] = weight[1]
this_obs['rmsCorr'] = weight[2]
this_obs['sigCorr'] = weight[2]
this_obs['cosDec'] = 1.0
elif mode in {'SIM_RAD_DEL', 'SIM_RAD_DOP'}:
self.obs.loc[idx, 'trx'] = 'SIM'
self.obs.loc[idx, 'rcv'] = 'SIM'
self.obs.loc[idx, 'com'] = 1
this_obs['trx'] = 'SIM'
this_obs['rcv'] = 'SIM'
this_obs['com'] = 1
if mode == 'SIM_RAD_DEL':
self.obs.loc[idx, 'delay'] = np.nan
self.obs.loc[idx, 'rmsDelay'] = weight[0]
self.obs.loc[idx, 'sigDelay'] = weight[0]
self.obs.loc[idx, 'doppler'] = np.nan
self.obs.loc[idx, 'rmsDoppler'] = np.nan
self.obs.loc[idx, 'sigDoppler'] = np.nan
this_obs['delay'] = np.nan
this_obs['rmsDelay'] = weight[0]
this_obs['sigDelay'] = weight[0]
this_obs['doppler'] = np.nan
this_obs['rmsDoppler'] = np.nan
this_obs['sigDoppler'] = np.nan
else:
self.obs.loc[idx, 'delay'] = np.nan
self.obs.loc[idx, 'rmsDelay'] = np.nan
self.obs.loc[idx, 'sigDelay'] = np.nan
self.obs.loc[idx, 'doppler'] = np.nan
self.obs.loc[idx, 'rmsDoppler'] = weight[0]
self.obs.loc[idx, 'sigDoppler'] = weight[0]
self.obs.loc[idx, 'frq'] = 8560.0
self.obs.loc[idx, 'ctr'] = 399
self.obs.loc[idx, 'sys'] = 'ITRF'
self.obs.loc[idx, 'pos1'] = 0.0
self.obs.loc[idx, 'pos2'] = 0.0
self.obs.loc[idx, 'pos3'] = 0.0
self.obs.loc[idx, 'selAst'] = 'a'
this_obs['delay'] = np.nan
this_obs['rmsDelay'] = np.nan
this_obs['sigDelay'] = np.nan
this_obs['doppler'] = np.nan
this_obs['rmsDoppler'] = weight[0]
this_obs['sigDoppler'] = weight[0]
this_obs['frq'] = 8560.0
this_obs['ctr'] = 399
this_obs['sys'] = 'ITRF'
this_obs['pos1'] = 0.0
this_obs['pos2'] = 0.0
this_obs['pos3'] = 0.0
this_obs['selAst'] = 'a'
new_sim_obs.append(this_obs)
sim_obs_df = pd.DataFrame(new_sim_obs)
self.obs = pd.concat([self.obs, sim_obs_df], ignore_index=True)
return None

def _parse_observation_arrays(self, obs_df):
Expand Down Expand Up @@ -1635,33 +1641,29 @@ def _get_rms_and_reject_outliers(self, partials, residuals, start_rejecting):
raise ValueError("Observer info length not recognized.")
resid = residuals[i]
# calculate chi-squared for each residual if after the first iteration
if self.n_iter > 1:
if start_rejecting and size == 2:
obs_cov = self.obs_cov[i]
obs_partials = partials[j:j+size, :]
if sel_ast[i] in {'D', 'd'}:
resid_cov = obs_cov + obs_partials @ full_cov @ obs_partials.T
else:
resid_cov = obs_cov - obs_partials @ full_cov @ obs_partials.T
if size == 1:
resid_cov_inv = 1.0/resid_cov
else:
resid_cov_det = resid_cov[0,0]*resid_cov[1,1] - resid_cov[0,1]*resid_cov[1,0]
resid_cov_inv = np.array([[resid_cov[1,1], -resid_cov[0,1]],
[-resid_cov[1,0], resid_cov[0,0]]])/resid_cov_det
residual_chi_squared = resid @ resid_cov_inv @ resid.T
res_chisq_vals[i] = residual_chi_squared
# outlier rejection, only reject RA/Dec measurements
if start_rejecting and size == 2:
if abs(residual_chi_squared) > chi_reject**2 and sel_ast[i] not in {'a', 'd'}:
resid_cov_det = resid_cov[0,0]*resid_cov[1,1] - resid_cov[0,1]*resid_cov[1,0]
resid_cov_inv = np.array([[resid_cov[1,1], -resid_cov[0,1]],
[-resid_cov[1,0], resid_cov[0,0]]])/resid_cov_det
outlier_chisq = resid @ resid_cov_inv @ resid.T
# outlier rejection, only reject RA/Dec measurements
if abs(outlier_chisq) > chi_reject**2 and sel_ast[i] not in {'a', 'd'}:
if sel_ast[i] == 'A':
self.num_rejected += 1
sel_ast[i] = 'D'
elif abs(residual_chi_squared) < chi_recover**2 and sel_ast[i] == 'D':
elif abs(outlier_chisq) < chi_recover**2 and sel_ast[i] == 'D':
sel_ast[i] = 'A'
self.num_rejected -= 1
res_chisq_vals[i] = resid @ self.obs_weight[i] @ resid.T
if sel_ast[i] not in {'D', 'd'}:
rms_u += resid @ resid.T
chi_sq += resid @ self.obs_weight[i] @ resid.T
chi_sq += res_chisq_vals[i]
j += size
# # write res_chisq_vals to file if any values are negative
# if np.any(res_chisq_vals < 0):
Expand Down Expand Up @@ -1759,6 +1761,7 @@ def _get_lsq_state_correction(self, partials, residuals):
atwb = np.zeros(self.n_fit)
j = 0
sel_ast = self.obs['selAst'].values
self.info_mats = []
for i, obs_info_len in enumerate(self.observer_info_lengths):
if obs_info_len in {4, 7}:
size = 2
Expand All @@ -1768,10 +1771,12 @@ def _get_lsq_state_correction(self, partials, residuals):
raise ValueError("Observer info length not recognized.")
if sel_ast[i] in {'D', 'd'}:
j += size
self.info_mats.append(atwa.copy())
continue
atwa += partials[j:j+size, :].T @ self.obs_weight[i] @ partials[j:j+size, :]
atwb += partials[j:j+size, :].T @ self.obs_weight[i] @ residuals[i]
j += size
self.info_mats.append(atwa.copy())
# use pseudo-inverse if the data arc is less than 7 days
if self.obs.obsTimeMJD.max() - self.obs.obsTimeMJD.min() < 7.0:
self.covariance = np.linalg.pinv(atwa, rcond=1e-20, hermitian=True)
Expand Down Expand Up @@ -2074,7 +2079,7 @@ def save(self, filename):
f.write(f" {units_dict[key[:2]]}")
if self._priors_given and key in self.prior_est:
p_val = self.prior_est[key]
p_sig = self.prior_sigs[key]
p_sig = self.prior_sig[key]
if key in ['om', 'w', 'i']:
p_val *= 180/np.pi
p_sig *= 180/np.pi
Expand Down Expand Up @@ -2114,7 +2119,7 @@ def save(self, filename):
f.write(f" {units_dict[key[:2]]}")
if self._priors_given and key in self.prior_est:
p_val = self.prior_est[key]
p_sig = self.prior_sigs[key]
p_sig = self.prior_sig[key]
if key in ['om', 'w', 'i']:
p_val *= 180/np.pi
p_sig *= 180/np.pi
Expand Down Expand Up @@ -2211,7 +2216,7 @@ def save(self, filename):
f.write(f'{final_val:18.11e}{half_tab}')
f.write(f'{final_unc:18.11e}{half_tab}')
f.write(f'{final_val-init_val:+18.11e}{half_tab}')
f.write(f'{(final_val-init_val)/init_unc:+10.3f}\n')
f.write(f'{(final_val-init_val)/final_unc:+10.3f}\n')
f.write("\n")
if itrn.iter_number <= self.n_iter:
f.write(subsection_full + '\n')
Expand Down
32 changes: 25 additions & 7 deletions grss/prop/prop_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,27 +536,48 @@ def plot_bplane(ca_list, plot_offset=False, scale_coords=False, n_std=3, units_k
lon = None
lat = None
impact_any = lon is not None and lat is not None
if kizner_nan and opik_nan and scaled_nan and not mtp_nan:
print("WARNING: Close approaches have some invalid B-planes but do have MTP. "
"Object might be captured.")
if len(ca_list) >= 100 or sigma_points is not None:
# remove nans before converting data to ellipse
if not kizner_nan:
kizner_data = data_to_ellipse(kizner_x, kizner_y, n_std, 'kizner',
print_ellipse_params, units, sigma_points)
else:
kizner_data = None
print(f"Kizner B-plane nan count: {np.sum(np.isnan(kizner_x))}")
non_nan_idx = np.where(~np.isnan(kizner_x) & ~np.isnan(kizner_y))[0]
kizner_data = data_to_ellipse(kizner_x[non_nan_idx], kizner_y[non_nan_idx],
n_std, 'kizner',
print_ellipse_params, units, sigma_points)
if not opik_nan:
opik_data = data_to_ellipse(opik_x, opik_y, n_std, 'opik',
print_ellipse_params, units, sigma_points)
else:
opik_data = None
print(f"Opik B-plane nan count: {np.sum(np.isnan(opik_x))}")
non_nan_idx = np.where(~np.isnan(opik_x) & ~np.isnan(opik_y))[0]
opik_data = data_to_ellipse(opik_x[non_nan_idx], opik_y[non_nan_idx],
n_std, 'opik',
print_ellipse_params, units, sigma_points)
if not scaled_nan:
scaled_data = data_to_ellipse(scaled_x, scaled_y, n_std, 'scaled',
print_ellipse_params, units, sigma_points)
else:
scaled_data = None
print(f"Scaled B-plane nan count: {np.sum(np.isnan(scaled_x))}")
non_nan_idx = np.where(~np.isnan(scaled_x) & ~np.isnan(scaled_y))[0]
scaled_data = data_to_ellipse(scaled_x[non_nan_idx], scaled_y[non_nan_idx],
n_std, 'scaled',
print_ellipse_params, units, sigma_points)
if not mtp_nan:
mtp_data = data_to_ellipse(mtp_x, mtp_y, n_std, 'mtp',
print_ellipse_params, units, sigma_points)
else:
mtp_data = None
print(f"MTP B-plane nan count: {np.sum(np.isnan(mtp_x))}")
non_nan_idx = np.where(~np.isnan(mtp_x) & ~np.isnan(mtp_y))[0]
mtp_data = data_to_ellipse(mtp_x[non_nan_idx], mtp_y[non_nan_idx],
n_std, 'mtp',
print_ellipse_params, units, sigma_points)

elif len(ca_list[0].xRel) >= 42 and analytic_info is not None:
all_data = partials_to_ellipse(ca_list[0], au2units, n_std,
print_ellipse_params, units, analytic_info)
Expand All @@ -566,9 +587,6 @@ def plot_bplane(ca_list, plot_offset=False, scale_coords=False, n_std=3, units_k
opik_data = None
scaled_data = None
mtp_data = None
if kizner_nan and opik_nan and scaled_nan and not mtp_nan:
print("WARNING: Close approaches have no valid B-planes but do have MTP. "
"Object might be captured.")
fig, axes = plt.subplots(2, 2, figsize=(9, 9), dpi=250)
plot_single_bplane(axes[0,0], kizner_x, kizner_y, kizner_data, 'kizner',
focus_factor, show_central_body, plot_offset, scale_coords,
Expand Down
4 changes: 2 additions & 2 deletions grss/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
grss_path = os.path.dirname(os.path.abspath(__file__))
default_kernel_path = f'{grss_path}/kernels/'

def _connected_to_internet(url='http://www.google.com/', timeout=5):
def _connected_to_internet(url='http://www.google.com/', timeout=30):
try:
_ = requests.head(url, timeout=timeout)
return True
except requests.ConnectionError:
except (requests.ConnectionError, requests.Timeout):
print("No internet connection available.")
return False

Expand Down
2 changes: 1 addition & 1 deletion grss/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.3.2
4.3.4
6 changes: 6 additions & 0 deletions include/approach.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ void impact_r_calc(PropSimulation *propSim, const size_t &i, const size_t &j,
std::vector<real> get_rel_state(PropSimulation *propSim, const size_t &i,
const size_t &j, const real &t);

/**
* @brief Compute partials of B-plane parameters.
*/
void get_bplane_partials(PropSimulation *propSim, CloseApproachParameters *ca,
const real &mu, const real &radius);

/**
* @brief Compute the time of close approach or impact using Brent's method
* for root finding in a bracketed interval.
Expand Down
4 changes: 3 additions & 1 deletion include/grss.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ void PropSimulation::integrate() {
this->map_ephemeris();
this->preprocess();
gr15(this);
this->unmap_ephemeris();
if (!this->unsafePersistentMemoryMap) {
this->unmap_ephemeris();
}

// flip vectors if integration is backwards in time
if (this->integParams.t0 > this->integParams.tf) {
Expand Down
Loading

0 comments on commit d015b93

Please sign in to comment.