grss 3.3.0__tar.gz → 3.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {grss-3.3.0/grss.egg-info → grss-3.4.2}/PKG-INFO +4 -3
- {grss-3.3.0 → grss-3.4.2}/README.md +3 -2
- {grss-3.3.0 → grss-3.4.2}/grss/fit/fit_simulation.py +118 -133
- {grss-3.3.0 → grss-3.4.2}/grss/prop/prop_parallel.py +1 -0
- {grss-3.3.0 → grss-3.4.2}/grss/prop/prop_utils.py +78 -39
- grss-3.4.2/grss/version.txt +1 -0
- {grss-3.3.0 → grss-3.4.2/grss.egg-info}/PKG-INFO +4 -3
- {grss-3.3.0 → grss-3.4.2}/include/utilities.h +4 -4
- {grss-3.3.0 → grss-3.4.2}/src/approach.cpp +1 -1
- {grss-3.3.0 → grss-3.4.2}/src/elements.cpp +84 -0
- {grss-3.3.0 → grss-3.4.2}/src/grss.cpp +25 -0
- {grss-3.3.0 → grss-3.4.2}/src/simulation.cpp +5 -11
- {grss-3.3.0 → grss-3.4.2}/src/utilities.cpp +15 -17
- grss-3.3.0/grss/version.txt +0 -1
- {grss-3.3.0 → grss-3.4.2}/CMakeLists.txt +0 -0
- {grss-3.3.0 → grss-3.4.2}/LICENSE +0 -0
- {grss-3.3.0 → grss-3.4.2}/MANIFEST.in +0 -0
- {grss-3.3.0 → grss-3.4.2}/build_cpp.sh +0 -0
- {grss-3.3.0 → grss-3.4.2}/docs/source/conf.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/extern/get_cspice.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/__init__.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/debias/get_debiasing_data.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/fit/__init__.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/fit/fit_optical.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/fit/fit_radar.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/fit/fit_utils.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/kernels/create_kernel_431.txt +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/kernels/create_kernel_441.txt +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/kernels/get_kernels.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/kernels/pck00011.tpc +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/kernels/planets_big16_de431_1950_2350.log +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/kernels/planets_big16_de431_1950_2350.tm +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/kernels/planets_big16_de441_1950_2350.log +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/kernels/planets_big16_de441_1950_2350.tm +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/prop/__init__.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/prop/prop_unscented.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss/utils.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss.egg-info/SOURCES.txt +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss.egg-info/dependency_links.txt +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss.egg-info/requires.txt +0 -0
- {grss-3.3.0 → grss-3.4.2}/grss.egg-info/top_level.txt +0 -0
- {grss-3.3.0 → grss-3.4.2}/include/approach.h +0 -0
- {grss-3.3.0 → grss-3.4.2}/include/elements.h +0 -0
- {grss-3.3.0 → grss-3.4.2}/include/force.h +0 -0
- {grss-3.3.0 → grss-3.4.2}/include/gr15.h +0 -0
- {grss-3.3.0 → grss-3.4.2}/include/grss.h +0 -0
- {grss-3.3.0 → grss-3.4.2}/include/interpolate.h +0 -0
- {grss-3.3.0 → grss-3.4.2}/include/parallel.h +0 -0
- {grss-3.3.0 → grss-3.4.2}/include/simulation.h +0 -0
- {grss-3.3.0 → grss-3.4.2}/include/spk.h +0 -0
- {grss-3.3.0 → grss-3.4.2}/include/stm.h +0 -0
- {grss-3.3.0 → grss-3.4.2}/pyproject.toml +0 -0
- {grss-3.3.0 → grss-3.4.2}/requirements.txt +0 -0
- {grss-3.3.0 → grss-3.4.2}/setup.cfg +0 -0
- {grss-3.3.0 → grss-3.4.2}/setup.py +0 -0
- {grss-3.3.0 → grss-3.4.2}/src/CMakeLists.txt +0 -0
- {grss-3.3.0 → grss-3.4.2}/src/force.cpp +0 -0
- {grss-3.3.0 → grss-3.4.2}/src/gr15.cpp +0 -0
- {grss-3.3.0 → grss-3.4.2}/src/interpolate.cpp +0 -0
- {grss-3.3.0 → grss-3.4.2}/src/parallel.cpp +0 -0
- {grss-3.3.0 → grss-3.4.2}/src/spk.cpp +0 -0
- {grss-3.3.0 → grss-3.4.2}/src/stm.cpp +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/cpp/prop/CMakeLists.txt +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/cpp/prop/apophis.cpp +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/cpp/prop/didymos.cpp +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/cpp/prop/spk_map.cpp +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/python/fit/chesley.ipynb +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/python/fit/eggl.ipynb +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/python/fit/farnocchia.ipynb +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/python/fit/shantanunaidu.ipynb +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/python/prop/apophis.ipynb +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/python/prop/didymos.ipynb +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/python/prop/icarus.ipynb +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/python/prop/moshup.ipynb +0 -0
- {grss-3.3.0 → grss-3.4.2}/tests/python/run_tests.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: grss
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.2
|
|
4
4
|
Summary: GRSS: Gauss-Radau Small-body Simulator
|
|
5
5
|
Author: Rahil Makadia
|
|
6
6
|
Author-email: makadia2@illinois.edu
|
|
@@ -74,9 +74,10 @@ Requires-Dist: spiceypy>=5.0.0
|
|
|
74
74
|
|
|
75
75
|
**GRSS** (pronounced "grass"), the *Gauss-Radau Small-body Simulator* is a Python package with a C++ binding for propagating and fitting the orbits of small bodies in the solar system, such as asteroids and comets.
|
|
76
76
|
|
|
77
|
-
If you use GRSS in your research, please cite the following
|
|
77
|
+
If you use GRSS in your research, please cite at least one of the following:
|
|
78
78
|
|
|
79
|
-
Makadia et al. (2024), "Measurability of the Heliocentric Momentum Enhancement from a Kinetic Impact: The Double Asteroid Redirection Test (DART) Mission", [Planetary Science Journal, 5, 38.](https://doi.org/10.3847/PSJ/ad1bce)
|
|
79
|
+
* Makadia et al. (2024), "Measurability of the Heliocentric Momentum Enhancement from a Kinetic Impact: The Double Asteroid Redirection Test (DART) Mission", [Planetary Science Journal, 5, 38.](https://doi.org/10.3847/PSJ/ad1bce)
|
|
80
|
+
* Makadia et al. (2023), "GRSS: An open-source small-body science tool for planetary defense", [55th Annual Meeting of the Division for Planetary Sciences.](https://ui.adsabs.harvard.edu/abs/2023DPS....5540502M/abstract)
|
|
80
81
|
|
|
81
82
|
## Getting Started
|
|
82
83
|
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
**GRSS** (pronounced "grass"), the *Gauss-Radau Small-body Simulator* is a Python package with a C++ binding for propagating and fitting the orbits of small bodies in the solar system, such as asteroids and comets.
|
|
10
10
|
|
|
11
|
-
If you use GRSS in your research, please cite the following
|
|
11
|
+
If you use GRSS in your research, please cite at least one of the following:
|
|
12
12
|
|
|
13
|
-
Makadia et al. (2024), "Measurability of the Heliocentric Momentum Enhancement from a Kinetic Impact: The Double Asteroid Redirection Test (DART) Mission", [Planetary Science Journal, 5, 38.](https://doi.org/10.3847/PSJ/ad1bce)
|
|
13
|
+
* Makadia et al. (2024), "Measurability of the Heliocentric Momentum Enhancement from a Kinetic Impact: The Double Asteroid Redirection Test (DART) Mission", [Planetary Science Journal, 5, 38.](https://doi.org/10.3847/PSJ/ad1bce)
|
|
14
|
+
* Makadia et al. (2023), "GRSS: An open-source small-body science tool for planetary defense", [55th Annual Meeting of the Division for Planetary Sciences.](https://ui.adsabs.harvard.edu/abs/2023DPS....5540502M/abstract)
|
|
14
15
|
|
|
15
16
|
## Getting Started
|
|
16
17
|
|
|
@@ -17,8 +17,8 @@ class IterationParams:
|
|
|
17
17
|
orbit determination process. It is also used for plotting the residuals
|
|
18
18
|
and chi-squared values for each iteration.
|
|
19
19
|
"""
|
|
20
|
-
def __init__(self, iter_number,
|
|
21
|
-
|
|
20
|
+
def __init__(self, iter_number, residuals, rms_u, rms_w, residual_chi_squared,
|
|
21
|
+
x_nom, covariance, obs_array, rejection_flags):
|
|
22
22
|
"""
|
|
23
23
|
Constructor for the IterationParams class
|
|
24
24
|
|
|
@@ -26,18 +26,20 @@ class IterationParams:
|
|
|
26
26
|
----------
|
|
27
27
|
iter_number : int
|
|
28
28
|
Iteration number
|
|
29
|
+
residuals : array
|
|
30
|
+
Residuals at the current iteration
|
|
31
|
+
rms_u : float
|
|
32
|
+
Unweighted RMS of the residuals.
|
|
33
|
+
rms_w : float
|
|
34
|
+
Weighted RMS of the residuals.
|
|
35
|
+
residual_chi_squared : list
|
|
36
|
+
List of the residual chi-squared values for each observation.
|
|
29
37
|
x_nom : dict
|
|
30
38
|
Dictionary of nominal state vector values at the current iteration
|
|
31
39
|
covariance : array
|
|
32
40
|
Covariance matrix at the current iteration
|
|
33
|
-
residuals : array
|
|
34
|
-
Residuals at the current iteration
|
|
35
41
|
obs_array : array
|
|
36
42
|
Observation array for the orbit fit
|
|
37
|
-
obs_weight_rej : array
|
|
38
|
-
Observation weight matrix after outlier rejection
|
|
39
|
-
observer_codes : tuple
|
|
40
|
-
Observer locations for each observation in obs_array
|
|
41
43
|
rejection_flags : list
|
|
42
44
|
List of rejection flags for each observation in obs_array
|
|
43
45
|
"""
|
|
@@ -48,12 +50,18 @@ class IterationParams:
|
|
|
48
50
|
self.variance = dict(zip([f'var_{k}' for k in self.x_nom.keys()], variance))
|
|
49
51
|
self.residuals = residuals
|
|
50
52
|
self.obs_array = obs_array
|
|
51
|
-
self.observer_codes = observer_codes
|
|
52
53
|
self.is_accepted = np.where(np.logical_not(rejection_flags))[0]
|
|
53
54
|
self.is_rejected = np.where(rejection_flags)[0]
|
|
55
|
+
# optical_idx is where neither the RA nor dec residuals are NaN
|
|
56
|
+
self.optical_idx = np.where(~np.isnan(self.residuals[:, 0]) &
|
|
57
|
+
~np.isnan(self.residuals[:, 1]))[0]
|
|
58
|
+
# radar_idx is where either the RA or dec residuals are NaN
|
|
59
|
+
self.radar_idx = np.where(np.isnan(self.residuals[:, 0]) |
|
|
60
|
+
np.isnan(self.residuals[:, 1]))[0]
|
|
54
61
|
self.sigmas = obs_array[:, 3:5]
|
|
55
|
-
self.
|
|
56
|
-
self.
|
|
62
|
+
self.unweighted_rms = rms_u
|
|
63
|
+
self.weighted_rms = rms_w
|
|
64
|
+
self.residual_chi_squared = residual_chi_squared
|
|
57
65
|
self._calculate_chis()
|
|
58
66
|
self._assemble_info()
|
|
59
67
|
return None
|
|
@@ -76,21 +84,6 @@ class IterationParams:
|
|
|
76
84
|
arr = arr[~np.isnan(arr)]
|
|
77
85
|
return arr
|
|
78
86
|
|
|
79
|
-
def _calculate_rms(self):
|
|
80
|
-
"""
|
|
81
|
-
Calculate the weighted and unweighted RMS values for the residuals
|
|
82
|
-
|
|
83
|
-
Returns
|
|
84
|
-
-------
|
|
85
|
-
None : NoneType
|
|
86
|
-
None
|
|
87
|
-
"""
|
|
88
|
-
resid_arr = self._flatten_and_clean(self.residuals[self.is_accepted])
|
|
89
|
-
n_obs = len(resid_arr)
|
|
90
|
-
self.unweighted_rms = float(np.sqrt(resid_arr.T @ resid_arr/n_obs))
|
|
91
|
-
self.weighted_rms = float(np.sqrt(resid_arr.T @ self.obs_weight_rej @ resid_arr/n_obs))
|
|
92
|
-
return None
|
|
93
|
-
|
|
94
87
|
def _calculate_chis(self):
|
|
95
88
|
"""
|
|
96
89
|
Calculates the chi and chi-squared values for the residuals
|
|
@@ -119,12 +112,8 @@ class IterationParams:
|
|
|
119
112
|
None
|
|
120
113
|
"""
|
|
121
114
|
# sourcery skip: extract-duplicate-method
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
~np.isnan(self.residuals[:, 1]))[0]
|
|
125
|
-
# radar_idx is where either the RA or dec residuals are NaN
|
|
126
|
-
radar_idx = np.where(np.isnan(self.residuals[:, 0]) |
|
|
127
|
-
np.isnan(self.residuals[:, 1]))[0]
|
|
115
|
+
optical_idx = self.optical_idx
|
|
116
|
+
radar_idx = self.radar_idx
|
|
128
117
|
optical_info = self.obs_array.copy()
|
|
129
118
|
optical_info[radar_idx, :] = np.nan
|
|
130
119
|
ra_obs = optical_info[:, 1]
|
|
@@ -384,12 +373,8 @@ class IterationParams:
|
|
|
384
373
|
"""
|
|
385
374
|
is_rejected = self.is_rejected
|
|
386
375
|
is_accepted = self.is_accepted
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
~np.isnan(self.residuals[:, 1]))[0]
|
|
390
|
-
# radar_idx is where either the RA or dec residuals are NaN
|
|
391
|
-
radar_idx = np.where(np.isnan(self.residuals[:, 0]) |
|
|
392
|
-
np.isnan(self.residuals[:, 1]))[0]
|
|
376
|
+
optical_idx = self.optical_idx
|
|
377
|
+
radar_idx = self.radar_idx
|
|
393
378
|
# plot chi values
|
|
394
379
|
factor = 3 if plot_chi_squared else 1
|
|
395
380
|
plt.figure(figsize=(factor*7,6), dpi=150)
|
|
@@ -610,7 +595,6 @@ class FitSimulation:
|
|
|
610
595
|
self.fit_events = False
|
|
611
596
|
self.reject_outliers = True
|
|
612
597
|
self.reject_criteria = [3.0, 2.8]
|
|
613
|
-
self.residual_chi_squared = [None]*len(self.obs_array)
|
|
614
598
|
self.num_rejected = 0
|
|
615
599
|
self.converged = False
|
|
616
600
|
return None
|
|
@@ -809,7 +793,7 @@ class FitSimulation:
|
|
|
809
793
|
sigma_corr = self.obs_array[:, 5]
|
|
810
794
|
obs_cov = np.zeros((self.sigmas.size, self.sigmas.size))
|
|
811
795
|
obs_wgt = np.zeros((self.sigmas.size, self.sigmas.size))
|
|
812
|
-
|
|
796
|
+
idx_to_keep = []
|
|
813
797
|
for i in range(self.obs_array.shape[0]):
|
|
814
798
|
obs_info_len = len(self.observer_info[i])
|
|
815
799
|
if obs_info_len in {4, 7}:
|
|
@@ -831,20 +815,22 @@ class FitSimulation:
|
|
|
831
815
|
obs_wgt[2*i:2*i+2, 2*i:2*i+2] = np.linalg.inv(sub_cov)
|
|
832
816
|
else:
|
|
833
817
|
obs_wgt[2*i:2*i+2, 2*i:2*i+2] = np.diag([1.0/sig_1**2, 1.0/sig_2**2])
|
|
834
|
-
if sig_1_nan:
|
|
835
|
-
|
|
836
|
-
if sig_2_nan:
|
|
837
|
-
|
|
818
|
+
if not sig_1_nan:
|
|
819
|
+
idx_to_keep.append(2*i)
|
|
820
|
+
if not sig_2_nan:
|
|
821
|
+
idx_to_keep.append(2*i+1)
|
|
838
822
|
if sig_1_nan and sig_2_nan:
|
|
839
823
|
raise ValueError("Both sigmas cannot be NaN.")
|
|
840
|
-
#
|
|
841
|
-
obs_cov =
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
824
|
+
# these next two lines were VERY VERY slow, needed to find a better way
|
|
825
|
+
# self.obs_cov = obs_cov[idx_to_keep, :][:, idx_to_keep]
|
|
826
|
+
# self.obs_weight = obs_wgt[idx_to_keep, :][:, idx_to_keep]
|
|
827
|
+
cov_post = np.zeros((len(idx_to_keep), len(idx_to_keep)))
|
|
828
|
+
wgt_post = np.zeros((len(idx_to_keep), len(idx_to_keep)))
|
|
829
|
+
for i, idx in enumerate(idx_to_keep):
|
|
830
|
+
cov_post[i, i:i+2] = obs_cov[idx, idx:idx+2]
|
|
831
|
+
wgt_post[i, i:i+2] = obs_wgt[idx, idx:idx+2]
|
|
832
|
+
self.obs_cov = cov_post
|
|
833
|
+
self.obs_weight = wgt_post
|
|
848
834
|
return None
|
|
849
835
|
|
|
850
836
|
def _sort_array_by_another(self, array, sort_by):
|
|
@@ -909,10 +895,14 @@ class FitSimulation:
|
|
|
909
895
|
prop_sim_past : prop.propSimulation object
|
|
910
896
|
propSim object for the past observations.
|
|
911
897
|
"""
|
|
898
|
+
# pylint: disable=no-member
|
|
912
899
|
t_eval_past = self.obs_array[self.past_obs_idx, 0]
|
|
913
900
|
tf_past = np.min(t_eval_past)
|
|
914
901
|
prop_sim_past = prop.propSimulation(name, self.t_sol, self.de_kernel, self.de_kernel_path)
|
|
915
902
|
prop_sim_past.tEvalMargin = 1.0
|
|
903
|
+
# flip t_eval_past and observer_info to go in reverse time order
|
|
904
|
+
t_eval_past = t_eval_past[::-1]
|
|
905
|
+
observer_info = tuple(observer_info[::-1])
|
|
916
906
|
prop_sim_past.set_integration_parameters(tf_past, t_eval_past, t_eval_utc,
|
|
917
907
|
eval_apparent_state, converged_light_time,
|
|
918
908
|
observer_info)
|
|
@@ -942,6 +932,7 @@ class FitSimulation:
|
|
|
942
932
|
prop_sim_future : prop.propSimulation object
|
|
943
933
|
propSim object for the future observations.
|
|
944
934
|
"""
|
|
935
|
+
# pylint: disable=no-member
|
|
945
936
|
t_eval_future = self.obs_array[self.future_obs_idx, 0]
|
|
946
937
|
tf_future = np.max(t_eval_future)
|
|
947
938
|
prop_sim_future = prop.propSimulation(name, self.t_sol, self.de_kernel, self.de_kernel_path)
|
|
@@ -972,7 +963,7 @@ class FitSimulation:
|
|
|
972
963
|
eval_apparent_state = True
|
|
973
964
|
converged_light_time = True
|
|
974
965
|
observer_info = np.array(self.observer_info, dtype=tuple)
|
|
975
|
-
observer_info_past =
|
|
966
|
+
observer_info_past = observer_info[self.past_obs_idx]
|
|
976
967
|
observer_info_future = tuple(observer_info[self.future_obs_idx])
|
|
977
968
|
prop_sim_past = None
|
|
978
969
|
prop_sim_future = None
|
|
@@ -1042,6 +1033,7 @@ class FitSimulation:
|
|
|
1042
1033
|
nongrav_params : prop.NongravParamaters object
|
|
1043
1034
|
Non-gravitational parameters for the fitted body.
|
|
1044
1035
|
"""
|
|
1036
|
+
# pylint: disable=no-member
|
|
1045
1037
|
nongrav_params = prop.NongravParamaters()
|
|
1046
1038
|
a1_val = x_dict['a1'] if 'a1' in x_dict.keys() else self.fixed_propsim_params['a1']
|
|
1047
1039
|
a2_val = x_dict['a2'] if 'a2' in x_dict.keys() else self.fixed_propsim_params['a2']
|
|
@@ -1206,6 +1198,7 @@ class FitSimulation:
|
|
|
1206
1198
|
propagated propSimulation object for the future.
|
|
1207
1199
|
"""
|
|
1208
1200
|
# sourcery skip: low-code-quality
|
|
1201
|
+
# pylint: disable=no-member
|
|
1209
1202
|
# get propagated states
|
|
1210
1203
|
prop_sim_past, prop_sim_future = self._get_prop_sims("orbit_fit_sim")
|
|
1211
1204
|
# create nominal integ_body object
|
|
@@ -1304,30 +1297,24 @@ class FitSimulation:
|
|
|
1304
1297
|
If the observer information is not well-defined.
|
|
1305
1298
|
"""
|
|
1306
1299
|
if self.past_obs_exist and self.future_obs_exist:
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
optical_obs = np.vstack((optical_obs_past, optical_obs_future))
|
|
1310
|
-
radar_obs_past = np.array(prop_sim_past.radarObs)
|
|
1311
|
-
radar_obs_future = np.array(prop_sim_future.radarObs)
|
|
1312
|
-
radar_obs = np.vstack((radar_obs_past, radar_obs_future))
|
|
1300
|
+
optical_obs = np.vstack((prop_sim_past.opticalObs, prop_sim_future.opticalObs))
|
|
1301
|
+
radar_obs = np.vstack((prop_sim_past.radarObs, prop_sim_future.radarObs))
|
|
1313
1302
|
elif self.past_obs_exist:
|
|
1314
|
-
optical_obs =
|
|
1315
|
-
radar_obs =
|
|
1303
|
+
optical_obs = prop_sim_past.opticalObs
|
|
1304
|
+
radar_obs = prop_sim_past.radarObs
|
|
1316
1305
|
elif self.future_obs_exist:
|
|
1317
|
-
optical_obs =
|
|
1318
|
-
radar_obs =
|
|
1319
|
-
optical_obs = optical_obs[:,2*integ_body_idx:2*integ_body_idx+2]
|
|
1320
|
-
radar_obs = radar_obs[:,integ_body_idx]
|
|
1306
|
+
optical_obs = prop_sim_future.opticalObs
|
|
1307
|
+
radar_obs = prop_sim_future.radarObs
|
|
1321
1308
|
measured_obs = self.obs_array[:, 1:3]
|
|
1322
1309
|
computed_obs = np.nan*np.ones_like(measured_obs)
|
|
1323
1310
|
for i in range(len(self.obs_array)):
|
|
1324
1311
|
obs_info_len = len(self.observer_info[i])
|
|
1325
1312
|
if obs_info_len in {4, 7}:
|
|
1326
|
-
computed_obs[i, :] = optical_obs[i]
|
|
1313
|
+
computed_obs[i, :] = optical_obs[i][2*integ_body_idx:2*integ_body_idx+2]
|
|
1327
1314
|
elif obs_info_len == 9: # delay measurement
|
|
1328
|
-
computed_obs[i, 0] = radar_obs[i]
|
|
1315
|
+
computed_obs[i, 0] = radar_obs[i][integ_body_idx]
|
|
1329
1316
|
elif obs_info_len == 10: # dopper measurement
|
|
1330
|
-
computed_obs[i, 1] = radar_obs[i]
|
|
1317
|
+
computed_obs[i, 1] = radar_obs[i][integ_body_idx]
|
|
1331
1318
|
else:
|
|
1332
1319
|
raise ValueError("Observer info length not recognized.")
|
|
1333
1320
|
return computed_obs
|
|
@@ -1481,7 +1468,7 @@ class FitSimulation:
|
|
|
1481
1468
|
partials = self._get_partials(prop_sim_past, prop_sim_future, perturbation_info)
|
|
1482
1469
|
return residuals, partials
|
|
1483
1470
|
|
|
1484
|
-
def
|
|
1471
|
+
def _get_rms_and_reject_outliers(self, partials, residuals, start_rejecting):
|
|
1485
1472
|
# sourcery skip: low-code-quality
|
|
1486
1473
|
"""
|
|
1487
1474
|
Outlier rejection algorithm for the residuals.
|
|
@@ -1491,22 +1478,19 @@ class FitSimulation:
|
|
|
1491
1478
|
partials : array
|
|
1492
1479
|
The partials of the observations with respect to the
|
|
1493
1480
|
initial nominal state.
|
|
1494
|
-
weights : array
|
|
1495
|
-
The weights of the observations.
|
|
1496
1481
|
residuals : array
|
|
1497
1482
|
The residuals of the observations
|
|
1483
|
+
start_rejecting : bool
|
|
1484
|
+
Flag for whether to start rejecting outliers.
|
|
1498
1485
|
|
|
1499
1486
|
Returns
|
|
1500
1487
|
-------
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
The residuals of the observations after outlier rejection.
|
|
1508
|
-
residual_chi_squared : array
|
|
1509
|
-
The chi squared values of the residuals after outlier rejection.
|
|
1488
|
+
rms_u : float
|
|
1489
|
+
Unweighted RMS of the residuals.
|
|
1490
|
+
rms_w : float
|
|
1491
|
+
Weighted RMS of the residuals.
|
|
1492
|
+
residual_chi_squared : list
|
|
1493
|
+
List of the residual chi-squared values for each observation.
|
|
1510
1494
|
|
|
1511
1495
|
Raises
|
|
1512
1496
|
------
|
|
@@ -1520,73 +1504,75 @@ class FitSimulation:
|
|
|
1520
1504
|
"Use default values if unsure. "
|
|
1521
1505
|
"Default values are chi_reject=3.0 and chi_recover=2.8 "
|
|
1522
1506
|
"(Implemented as FitSimulation.reject_criteria=[3.0, 2.8])")
|
|
1523
|
-
full_cov =
|
|
1507
|
+
full_cov = self.covariance
|
|
1508
|
+
residual_chi_squared = [None]*len(self.obs_array)
|
|
1509
|
+
self.num_rejected = 0
|
|
1510
|
+
rms_u = 0
|
|
1511
|
+
rms_w = 0
|
|
1524
1512
|
j = 0
|
|
1525
|
-
residual_chi_squared = np.zeros(len(self.obs_array))
|
|
1526
|
-
rejected_indices = []
|
|
1527
1513
|
for i in range(len(self.obs_array)):
|
|
1528
1514
|
obs_info_len = len(self.observer_info[i])
|
|
1529
1515
|
if obs_info_len in {4, 7}:
|
|
1530
1516
|
size = 2
|
|
1531
1517
|
elif obs_info_len in {9, 10}:
|
|
1532
1518
|
size = 1
|
|
1533
|
-
j += size
|
|
1534
|
-
continue
|
|
1535
1519
|
else:
|
|
1536
1520
|
raise ValueError("Observer info length not recognized.")
|
|
1537
|
-
# calculate chi-squared for each residual
|
|
1538
1521
|
resid = residuals[j:j+size].reshape((1, size))
|
|
1522
|
+
# calculate chi-squared for each residual
|
|
1539
1523
|
obs_cov = self.obs_cov[j:j+size, j:j+size]
|
|
1540
1524
|
obs_partials = partials[j:j+size, :]
|
|
1541
1525
|
if self.rejection_flag[i]:
|
|
1542
1526
|
resid_cov = obs_cov + obs_partials @ full_cov @ obs_partials.T
|
|
1543
1527
|
else:
|
|
1544
1528
|
resid_cov = obs_cov - obs_partials @ full_cov @ obs_partials.T
|
|
1545
|
-
residual_chi_squared[i] = resid @ np.linalg.inv(resid_cov) @ resid.T
|
|
1529
|
+
residual_chi_squared[i] = (resid @ np.linalg.inv(resid_cov) @ resid.T)[0,0]
|
|
1546
1530
|
# outlier rejection, only reject RA/Dec measurements
|
|
1547
|
-
# but not
|
|
1548
|
-
if isinstance(self.observer_codes[i], str):
|
|
1549
|
-
if residual_chi_squared[i] > chi_reject**2
|
|
1531
|
+
# but not Gaia/radar measurements
|
|
1532
|
+
if start_rejecting and size == 2 and isinstance(self.observer_codes[i], str):
|
|
1533
|
+
if residual_chi_squared[i] > chi_reject**2:
|
|
1550
1534
|
self.rejection_flag[i] = True
|
|
1551
|
-
|
|
1535
|
+
self.num_rejected += 1
|
|
1536
|
+
elif self.rejection_flag[i] and residual_chi_squared[i] < chi_recover**2:
|
|
1552
1537
|
self.rejection_flag[i] = False
|
|
1553
|
-
|
|
1554
|
-
|
|
1538
|
+
self.num_rejected -= 1
|
|
1539
|
+
if not self.rejection_flag[i]:
|
|
1540
|
+
rms_u += (resid @ resid.T)[0,0]
|
|
1541
|
+
rms_w += (resid @ self.obs_weight[j:j+size, j:j+size] @ resid.T)[0,0]
|
|
1555
1542
|
j += size
|
|
1556
|
-
# remove rejected residuals from partials, weights, and residuals
|
|
1557
|
-
partials = np.delete(partials, rejected_indices, axis=0)
|
|
1558
|
-
weights = np.delete(weights, rejected_indices, axis=0)
|
|
1559
|
-
weights = np.delete(weights, rejected_indices, axis=1)
|
|
1560
|
-
residuals = np.delete(residuals, rejected_indices, axis=0)
|
|
1561
|
-
# print(rejected_indices)
|
|
1562
|
-
self.num_rejected = len(rejected_indices)//2
|
|
1563
1543
|
rejected_fraction = self.num_rejected/len(self.obs_array)
|
|
1564
1544
|
if rejected_fraction > 0.25:
|
|
1565
1545
|
print("WARNING: More than 25% of observations rejected. Consider changing chi_reject",
|
|
1566
1546
|
"and chi_recover values, or turning off outlier rejection altogether.")
|
|
1567
|
-
|
|
1568
|
-
|
|
1547
|
+
rms_u = np.sqrt(rms_u/self.n_obs)
|
|
1548
|
+
rms_w = np.sqrt(rms_w/self.n_obs)
|
|
1549
|
+
return rms_u, rms_w, residual_chi_squared
|
|
1569
1550
|
|
|
1570
|
-
def _add_iteration(self, iter_number, residuals,
|
|
1551
|
+
def _add_iteration(self, iter_number, residuals, rms_u, rms_w, residual_chi_squared):
|
|
1571
1552
|
"""
|
|
1572
1553
|
Adds an iteration to the list of iterations in the FitSimulation object.
|
|
1573
1554
|
|
|
1574
1555
|
Parameters
|
|
1575
1556
|
----------
|
|
1576
1557
|
iter_number : int
|
|
1577
|
-
|
|
1558
|
+
Iteration number.
|
|
1578
1559
|
residuals : array
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1560
|
+
Residuals of the observations.
|
|
1561
|
+
rms_u : float
|
|
1562
|
+
Unweighted RMS of the residuals.
|
|
1563
|
+
rms_w : float
|
|
1564
|
+
Weighted RMS of the residuals.
|
|
1565
|
+
residual_chi_squared : list
|
|
1566
|
+
List of the residual chi-squared values for each observation.
|
|
1582
1567
|
|
|
1583
1568
|
Returns
|
|
1584
1569
|
-------
|
|
1585
1570
|
None : NoneType
|
|
1586
1571
|
None
|
|
1587
1572
|
"""
|
|
1588
|
-
self.iters.append(IterationParams(iter_number,
|
|
1589
|
-
|
|
1573
|
+
self.iters.append(IterationParams(iter_number, residuals, rms_u, rms_w,
|
|
1574
|
+
residual_chi_squared, self.x_nom,
|
|
1575
|
+
self.covariance, self.obs_array,
|
|
1590
1576
|
self.rejection_flag))
|
|
1591
1577
|
return None
|
|
1592
1578
|
|
|
@@ -1600,7 +1586,7 @@ class FitSimulation:
|
|
|
1600
1586
|
None
|
|
1601
1587
|
"""
|
|
1602
1588
|
if self.n_iter > 1:
|
|
1603
|
-
del_rms_convergence = 1e-
|
|
1589
|
+
del_rms_convergence = 1e-4
|
|
1604
1590
|
curr_rms = self.iters[-1].weighted_rms
|
|
1605
1591
|
prev_rms = self.iters[-2].weighted_rms
|
|
1606
1592
|
del_rms = abs(prev_rms - curr_rms)/prev_rms
|
|
@@ -1608,7 +1594,7 @@ class FitSimulation:
|
|
|
1608
1594
|
self.converged = True
|
|
1609
1595
|
return None
|
|
1610
1596
|
|
|
1611
|
-
def _get_lsq_state_correction(self, partials,
|
|
1597
|
+
def _get_lsq_state_correction(self, partials, residuals):
|
|
1612
1598
|
"""
|
|
1613
1599
|
Get the state correction using least-squares.
|
|
1614
1600
|
|
|
@@ -1617,8 +1603,6 @@ class FitSimulation:
|
|
|
1617
1603
|
partials : array
|
|
1618
1604
|
The partials of the observations with respect to the
|
|
1619
1605
|
initial nominal state.
|
|
1620
|
-
weights : array
|
|
1621
|
-
The weights of the observations.
|
|
1622
1606
|
residuals : array
|
|
1623
1607
|
The residuals of the observations
|
|
1624
1608
|
|
|
@@ -1631,8 +1615,6 @@ class FitSimulation:
|
|
|
1631
1615
|
atwb = np.zeros((self.n_fit, 1))
|
|
1632
1616
|
j = 0
|
|
1633
1617
|
for i in range(len(self.obs_array)):
|
|
1634
|
-
if j >= len(residuals):
|
|
1635
|
-
break
|
|
1636
1618
|
obs_info_len = len(self.observer_info[i])
|
|
1637
1619
|
if obs_info_len in {4, 7}:
|
|
1638
1620
|
size = 2
|
|
@@ -1643,13 +1625,16 @@ class FitSimulation:
|
|
|
1643
1625
|
if self.rejection_flag[i]:
|
|
1644
1626
|
j += size
|
|
1645
1627
|
continue
|
|
1646
|
-
atwa += partials[j:j+size, :].T @
|
|
1647
|
-
|
|
1628
|
+
atwa += (partials[j:j+size, :].T @ self.obs_weight[j:j+size, j:j+size]
|
|
1629
|
+
@ partials[j:j+size, :])
|
|
1630
|
+
atwb += (partials[j:j+size, :].T @ self.obs_weight[j:j+size, j:j+size]
|
|
1648
1631
|
@ residuals[j:j+size].reshape((size, 1)))
|
|
1649
1632
|
j += size
|
|
1650
|
-
#
|
|
1651
|
-
|
|
1652
|
-
|
|
1633
|
+
# use pseudo-inverse if the data arc is less than 7 days
|
|
1634
|
+
if self.obs_array[-1,0]-self.obs_array[0,0] < 7.0:
|
|
1635
|
+
cov = np.linalg.pinv(atwa, rcond=1e-20, hermitian=True)
|
|
1636
|
+
else:
|
|
1637
|
+
cov = np.array(prop.matrix_inverse(atwa))
|
|
1653
1638
|
delta_x = cov @ atwb
|
|
1654
1639
|
return delta_x.ravel(), cov
|
|
1655
1640
|
|
|
@@ -1675,29 +1660,28 @@ class FitSimulation:
|
|
|
1675
1660
|
self.n_iter = i+1
|
|
1676
1661
|
# get residuals and partials
|
|
1677
1662
|
residuals, partials = self._get_residuals_and_partials()
|
|
1678
|
-
|
|
1663
|
+
clean_residuals = self._flatten_and_clean(residuals)
|
|
1664
|
+
# calculate rms and reject outliers here if desired
|
|
1665
|
+
rms_u, rms_w, res_chi_sq = self._get_rms_and_reject_outliers(partials, clean_residuals,
|
|
1666
|
+
start_rejecting)
|
|
1679
1667
|
if i == 0:
|
|
1680
1668
|
# add prefit iteration
|
|
1681
|
-
|
|
1682
|
-
self._add_iteration(0, prefit_residuals, weights)
|
|
1683
|
-
clean_residuals = self._flatten_and_clean(residuals)
|
|
1684
|
-
# reject outliers here
|
|
1685
|
-
if self.reject_outliers and start_rejecting:
|
|
1686
|
-
a_rej, w_rej, b_rej, _ = self._apply_outlier_rejection(partials, weights,
|
|
1687
|
-
clean_residuals)
|
|
1688
|
-
else:
|
|
1689
|
-
a_rej, w_rej, b_rej = partials, weights, clean_residuals
|
|
1669
|
+
self._add_iteration(0, residuals, rms_u, rms_w, res_chi_sq)
|
|
1690
1670
|
# get initial guess
|
|
1691
1671
|
curr_state = np.array(list(self.x_nom.values()))
|
|
1692
1672
|
# get state correction
|
|
1693
|
-
delta_x, cov = self._get_lsq_state_correction(
|
|
1673
|
+
delta_x, cov = self._get_lsq_state_correction(partials, clean_residuals)
|
|
1694
1674
|
# get new state
|
|
1695
1675
|
next_state = curr_state + delta_x
|
|
1676
|
+
if self.fit_cometary and next_state[0] < 0.0:
|
|
1677
|
+
next_state[0] = 0.0
|
|
1678
|
+
print("WARNING: Eccentricity is negative per least squares state correction. "
|
|
1679
|
+
"Setting to 0.0. This solution may not be trustworthy.")
|
|
1696
1680
|
self.x_nom = dict(zip(self.x_nom.keys(), next_state))
|
|
1697
1681
|
# get new covariance
|
|
1698
1682
|
self.covariance = cov
|
|
1699
1683
|
# add iteration
|
|
1700
|
-
self._add_iteration(i+1, residuals,
|
|
1684
|
+
self._add_iteration(i+1, residuals, rms_u, rms_w, res_chi_sq)
|
|
1701
1685
|
if verbose:
|
|
1702
1686
|
print(f"{self.iters[-1].iter_number}\t\t\t",
|
|
1703
1687
|
f"{self.iters[-1].unweighted_rms:.3f}\t\t\t",
|
|
@@ -1749,7 +1733,7 @@ class FitSimulation:
|
|
|
1749
1733
|
print("--------------------------------------------------------------")
|
|
1750
1734
|
print(f"Solution Time: MJD {self.t_sol:0.3f} TDB =",
|
|
1751
1735
|
f"{Time(self.t_sol, format='mjd', scale='tdb').iso} TDB")
|
|
1752
|
-
print(f"Solution Observation Arc: {arc:0.2f} days")
|
|
1736
|
+
print(f"Solution Observation Arc: {arc:0.2f} days ({arc/365.25:0.2f} years)")
|
|
1753
1737
|
print("--------------------------------------------------------------")
|
|
1754
1738
|
print("Fitted Variable\t\tInitial Value\t\t\tUncertainty\t\t\tFitted Value",
|
|
1755
1739
|
"\t\t\tUncertainty\t\t\tChange\t\t\t\tChange (sigma)")
|
|
@@ -1885,6 +1869,7 @@ def _generate_simulated_obs(ref_sol, ref_cov, ref_ng_info, events, modified_obs_
|
|
|
1885
1869
|
If the reference solution does not contain a full cartesian
|
|
1886
1870
|
or cometary state.
|
|
1887
1871
|
"""
|
|
1872
|
+
# pylint: disable=no-member
|
|
1888
1873
|
obs_sigma_dict = { 'astrometry': 1, # arcsec
|
|
1889
1874
|
'occultation': 0.25, # fraction of body angular diameter
|
|
1890
1875
|
'delay': 2, # microseconds
|