@@ -1344,22 +1344,26 @@ def _get_computed_obs(self, prop_sim_past, prop_sim_future, integ_body_idx):
13441344 raise ValueError ("Observer info length not recognized." )
13451345 nom_body = integ_body_idx == 0
13461346 if nom_body :
1347- self ._inflate_uncertainties (prop_sim_past , prop_sim_future )
1348- return computed_obs
1347+ computed_obs_dot = self ._inflate_uncertainties (prop_sim_past , prop_sim_future )
1348+ else :
1349+ computed_obs_dot = np .nan * np .ones ((len (self .obs ), 2 ))
1350+ return computed_obs , computed_obs_dot
13491351
13501352 def _inflate_uncertainties (self , prop_sim_past , prop_sim_future ):
13511353 """
13521354 Apply time uncertainties to the optical observations weights.
13531355
13541356 Parameters
13551357 ----------
1356- computed_obs_dot : array
1357- Computed optical observations dot.
1358+ prop_sim_past : libgrss.PropSimulation object
1359+ PropSimulation object for the past observations.
1360+ prop_sim_future : libgrss.PropSimulation object
1361+ PropSimulation object for the future observations.
13581362
13591363 Returns
13601364 -------
1361- None : NoneType
1362- None
1365+ computed_obs_dot : array
1366+ Computed optical observations time derivatives.
13631367 """
13641368 stations = self .obs .stn .values
13651369 sig_times = self .obs .sigTime .values
@@ -1446,7 +1450,7 @@ def _inflate_uncertainties(self, prop_sim_past, prop_sim_future):
14461450 self .obs_cov [i ] = cov
14471451 self .obs_weight [i ] = inv
14481452 self .obs .sigTime = sig_times
1449- return None
1453+ return computed_obs_dot
14501454
14511455 def _get_analytic_partials (self , prop_sim_past , prop_sim_future ):
14521456 """
@@ -1532,9 +1536,9 @@ def _get_numeric_partials(self, prop_sim_past, prop_sim_future, perturbation_inf
15321536 _ = list (self .x_nom .keys ())[i ]
15331537 _ , _ , _ , _ , _ , _ , fd_delta = perturbation_info [i ]
15341538 # get computed_obs for perturbed states
1535- computed_obs_plus = self ._get_computed_obs (prop_sim_past , prop_sim_future ,
1539+ computed_obs_plus , _ = self ._get_computed_obs (prop_sim_past , prop_sim_future ,
15361540 integ_body_idx = 2 * i + 1 )
1537- computed_obs_minus = self ._get_computed_obs (prop_sim_past , prop_sim_future ,
1541+ computed_obs_minus , _ = self ._get_computed_obs (prop_sim_past , prop_sim_future ,
15381542 integ_body_idx = 2 * i + 2 )
15391543 computed_obs_plus = self ._flatten_and_clean (computed_obs_plus )
15401544 computed_obs_minus = self ._flatten_and_clean (computed_obs_minus )
@@ -1586,10 +1590,13 @@ def _get_residuals_and_partials(self):
15861590 # get partials
15871591 partials = self ._get_partials (prop_sim_past , prop_sim_future , perturbation_info )
15881592 # get residuals
1589- computed_obs = self ._get_computed_obs (prop_sim_past , prop_sim_future , integ_body_idx = 0 )
1593+ computed_obs , computed_obs_dot = self ._get_computed_obs (prop_sim_past , prop_sim_future , integ_body_idx = 0 )
15901594 residuals = [None ]* len (self .obs )
15911595 ra_res = self .obs ['resRA' ].values
15921596 dec_res = self .obs ['resDec' ].values
1597+ at_res = [np .nan ]* len (ra_res )
1598+ at_sec_res = [np .nan ]* len (ra_res )
1599+ ct_res = [np .nan ]* len (ra_res )
15931600 delay_res = self .obs ['resDelay' ].values
15941601 doppler_res = self .obs ['resDoppler' ].values
15951602 fields = ['mode' , 'ra' , 'dec' , 'cosDec' , 'biasRA' , 'biasDec' , 'delay' , 'doppler' ]
@@ -1611,8 +1618,17 @@ def _get_residuals_and_partials(self):
16111618 ra_res [i ] = ra * 3600 * cos_dec - bias_ra - computed_obs [i , 0 ]
16121619 dec_res [i ] = dec * 3600 - bias_dec - computed_obs [i , 1 ]
16131620 residuals [i ] = np .array ([ra_res [i ], dec_res [i ]])
1621+ # rotation angle from RA/Dec to along/cross-track
1622+ obs_dot = computed_obs_dot [i , :]
1623+ rot_angle = np .arctan2 (obs_dot [1 ], obs_dot [0 ])
1624+ at_res [i ] = (ra_res [i ]* np .cos (rot_angle ) + dec_res [i ]* np .sin (rot_angle ))
1625+ ct_res [i ] = (- ra_res [i ]* np .sin (rot_angle ) + dec_res [i ]* np .cos (rot_angle ))
1626+ at_sec_res [i ] = at_res [i ]/ np .linalg .norm (obs_dot )* 86400.0 # convert to seconds
16141627 self .obs ['resRA' ] = ra_res
16151628 self .obs ['resDec' ] = dec_res
1629+ self .obs ['resAT' ] = at_res
1630+ self .obs ['resCT' ] = ct_res
1631+ self .obs ['resATsec' ] = at_sec_res
16161632 self .obs ['resDelay' ] = delay_res
16171633 self .obs ['resDoppler' ] = doppler_res
16181634 return residuals , partials
@@ -1656,11 +1672,9 @@ def _get_rms_and_reject_outliers(self, partials, residuals, start_rejecting):
16561672 "Default values are chi_reject=3.0 and chi_recover=2.8 "
16571673 "(Implemented as FitSimulation.reject_criteria=[3.0, 2.8])" )
16581674 full_cov = self .covariance
1659- rms_u = 0
1660- chi_sq = 0
16611675 j = 0
16621676 sel_ast = self .obs ['selAst' ].values
1663- res_chisq_vals = np .nan * np .ones (len (self .observer_info_lengths ))
1677+ outlier_chisq_vals = np .nan * np .ones (len (self .observer_info_lengths ))
16641678 for i , obs_info_len in enumerate (self .observer_info_lengths ):
16651679 if obs_info_len in {4 , 7 }:
16661680 size = 2
@@ -1680,20 +1694,34 @@ def _get_rms_and_reject_outliers(self, partials, residuals, start_rejecting):
16801694 resid_cov_det = resid_cov [0 ,0 ]* resid_cov [1 ,1 ] - resid_cov [0 ,1 ]* resid_cov [1 ,0 ]
16811695 resid_cov_inv = np .array ([[resid_cov [1 ,1 ], - resid_cov [0 ,1 ]],
16821696 [- resid_cov [1 ,0 ], resid_cov [0 ,0 ]]])/ resid_cov_det
1683- outlier_chisq = resid @ resid_cov_inv @ resid .T
1684- # outlier rejection, only reject RA/Dec measurements
1685- if abs (outlier_chisq ) > chi_reject ** 2 and sel_ast [i ] not in {'a' , 'd' }:
1697+ outlier_chisq_vals [i ] = resid @ resid_cov_inv @ resid .T
1698+ j += size
1699+ if start_rejecting :
1700+ idx_to_check = np .where ((sel_ast == 'A' ) | (sel_ast == 'a' ))[0 ]
1701+ if len (idx_to_check ) == 0 :
1702+ raise ValueError ("No accepted observations in fit." )
1703+ max_outlier_chisq = np .nanmax (outlier_chisq_vals [idx_to_check ])
1704+ rej_chisq_thresh = max (chi_reject ** 2 , 0.25 * max_outlier_chisq )
1705+ rec_chisq_thresh = chi_recover ** 2
1706+ # outlier rejection, only reject RA/Dec measurements
1707+ rms_u = 0
1708+ chi_sq = 0
1709+ res_chisq_vals = np .nan * np .ones (len (self .observer_info_lengths ))
1710+ for i in range (len (self .observer_info_lengths )):
1711+ if start_rejecting :
1712+ outlier_chisq = outlier_chisq_vals [i ]
1713+ if abs (outlier_chisq ) > rej_chisq_thresh and sel_ast [i ] not in {'a' , 'd' }:
16861714 if sel_ast [i ] == 'A' :
16871715 self .num_rejected += 1
16881716 sel_ast [i ] = 'D'
1689- elif abs (outlier_chisq ) < chi_recover ** 2 and sel_ast [i ] == 'D' :
1717+ elif abs (outlier_chisq ) < rec_chisq_thresh and sel_ast [i ] == 'D' :
16901718 sel_ast [i ] = 'A'
16911719 self .num_rejected -= 1
1720+ resid = residuals [i ]
16921721 res_chisq_vals [i ] = resid @ self .obs_weight [i ] @ resid .T
16931722 if sel_ast [i ] not in {'D' , 'd' }:
16941723 rms_u += resid @ resid .T
16951724 chi_sq += res_chisq_vals [i ]
1696- j += size
16971725 # # write res_chisq_vals to file if any values are negative
16981726 # if np.any(res_chisq_vals < 0):
16991727 # print("WARNING: Negative outlier rejection chi-squared values detected. "
@@ -1806,12 +1834,12 @@ def _get_lsq_state_correction(self, partials, residuals):
18061834 atwb += partials [j :j + size , :].T @ self .obs_weight [i ] @ residuals [i ]
18071835 j += size
18081836 self .info_mats .append (atwa .copy ())
1809- # use pseudo-inverse if the data arc is less than 7 days
1837+ # use pseudo-inverse if the information matrix is ill-conditioned
18101838 data_arc = self .obs .obsTimeMJD .max () - self .obs .obsTimeMJD .min ()
1811- if data_arc < 1.0 :
1812- self . covariance = np . linalg . pinv ( atwa , rcond = 1e-10 , hermitian = True )
1813- elif data_arc < 7.0 :
1814- self .covariance = np .linalg .pinv (atwa , rcond = 1e-20 , hermitian = True )
1839+ cond_num = np . linalg . cond ( atwa )
1840+ if data_arc < 10.0 and cond_num > 1e15 :
1841+ # print(f"Condition number of the information matrix: {cond_num:.2e}")
1842+ self .covariance = np .linalg .pinv (atwa , rcond = 1e3 / cond_num , hermitian = True )
18151843 else :
18161844 self .covariance = np .array (libgrss .matrix_inverse (atwa ))
18171845 delta_x = self .covariance @ atwb
@@ -2176,10 +2204,11 @@ def save(self, filename):
21762204 widths = {
21772205 'obsTime' : 29 , 'stn' : 7 , 'ra' : 16 , 'dec' : 18 ,
21782206 'sigRA' : 16 , 'sigDec' : 17 , 'resRA' : 16 , 'resDec' : 15 ,
2207+ 'resAT' : 16 , 'resCT' : 16 , 'resATsec' : 16
21792208 }
21802209 f .write (f'|{ "Observations" .center (69 )} |' )
21812210 f .write (f'{ "Sigmas" .center (32 )} |' )
2182- f .write (f'{ "Residuals" .center (29 )} |' )
2211+ f .write (f'{ "Residuals" .center (77 )} |' )
21832212 f .write ("\n " )
21842213 f .write (f'{ "Time [UTC]" .center (widths ["obsTime" ])} ' )
21852214 f .write (f'{ "Obs" .center (widths ["stn" ])} ' )
@@ -2189,6 +2218,9 @@ def save(self, filename):
21892218 f .write (f'{ "Dec[as]/Dop[Hz]" .center (widths ["sigDec" ])} ' )
21902219 f .write (f'{ f"RA[as]/Del[{ chr (0x00b5 )} s]" .center (widths ["resRA" ])} ' )
21912220 f .write (f'{ "Dec[as]/Dop[Hz]" .center (widths ["resDec" ])} ' )
2221+ f .write (f'{ "AT[as]" .center (widths ["resAT" ])} ' )
2222+ f .write (f'{ "CT[as]" .center (widths ["resCT" ])} ' )
2223+ f .write (f'{ "ATsec[s]" .center (widths ["resATsec" ])} ' )
21922224 f .write ("\n " )
21932225 for i , obs in self .obs [::- 1 ].iterrows ():
21942226 f .write (f'{ obs ["selAst" ]+ obs ["obsTime" ]:<{widths ["obsTime" ]}} ' )
@@ -2208,6 +2240,9 @@ def save(self, filename):
22082240 f .write (f'{ obs ["sigDec" ]:^{widths ["sigDec" ]}.4f} ' )
22092241 f .write (f'{ obs ["resRA" ]:^+{widths ["resRA" ]}.7f} ' )
22102242 f .write (f'{ obs ["resDec" ]:^+{widths ["resDec" ]}.7f} ' )
2243+ f .write (f'{ obs ["resAT" ]:^+{widths ["resAT" ]}.7f} ' )
2244+ f .write (f'{ obs ["resCT" ]:^+{widths ["resCT" ]}.7f} ' )
2245+ f .write (f'{ obs ["resATsec" ]:^+{widths ["resATsec" ]}.7f} ' )
22112246 f .write ("\n " )
22122247 f .write ("\n " )
22132248
0 commit comments