grss 4.3.2__tar.gz → 4.3.6__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.
Files changed (79) hide show
  1. {grss-4.3.2/grss.egg-info → grss-4.3.6}/PKG-INFO +1 -1
  2. {grss-4.3.2 → grss-4.3.6}/build_cpp.sh +2 -0
  3. {grss-4.3.2 → grss-4.3.6}/grss/fit/fit_simulation.py +66 -61
  4. {grss-4.3.2 → grss-4.3.6}/grss/prop/prop_utils.py +118 -7
  5. {grss-4.3.2 → grss-4.3.6}/grss/utils.py +2 -2
  6. grss-4.3.6/grss/version.txt +1 -0
  7. {grss-4.3.2 → grss-4.3.6/grss.egg-info}/PKG-INFO +1 -1
  8. {grss-4.3.2 → grss-4.3.6}/include/approach.h +6 -0
  9. {grss-4.3.2 → grss-4.3.6}/include/grss.h +3 -1
  10. {grss-4.3.2 → grss-4.3.6}/include/simulation.h +23 -3
  11. {grss-4.3.2 → grss-4.3.6}/include/utilities.h +10 -0
  12. {grss-4.3.2 → grss-4.3.6}/pyproject.toml +3 -0
  13. {grss-4.3.2 → grss-4.3.6}/setup.py +5 -2
  14. {grss-4.3.2 → grss-4.3.6}/src/approach.cpp +508 -466
  15. {grss-4.3.2 → grss-4.3.6}/src/force.cpp +124 -5
  16. {grss-4.3.2 → grss-4.3.6}/src/grss.cpp +64 -5
  17. {grss-4.3.2 → grss-4.3.6}/src/interpolate.cpp +2 -2
  18. {grss-4.3.2 → grss-4.3.6}/src/pck.cpp +12 -1
  19. {grss-4.3.2 → grss-4.3.6}/src/simulation.cpp +40 -7
  20. grss-4.3.6/tests/python/fit/chesley.ipynb +275 -0
  21. {grss-4.3.2 → grss-4.3.6}/tests/python/fit/eggl.ipynb +17 -17
  22. grss-4.3.6/tests/python/fit/farnocchia.ipynb +275 -0
  23. grss-4.3.6/tests/python/fit/shantanunaidu.ipynb +277 -0
  24. grss-4.3.2/grss/version.txt +0 -1
  25. grss-4.3.2/tests/python/fit/chesley.ipynb +0 -275
  26. grss-4.3.2/tests/python/fit/farnocchia.ipynb +0 -281
  27. grss-4.3.2/tests/python/fit/shantanunaidu.ipynb +0 -277
  28. {grss-4.3.2 → grss-4.3.6}/CMakeLists.txt +0 -0
  29. {grss-4.3.2 → grss-4.3.6}/LICENSE +0 -0
  30. {grss-4.3.2 → grss-4.3.6}/MANIFEST.in +0 -0
  31. {grss-4.3.2 → grss-4.3.6}/README.md +0 -0
  32. {grss-4.3.2 → grss-4.3.6}/docs/source/conf.py +0 -0
  33. {grss-4.3.2 → grss-4.3.6}/extern/get_cspice.py +0 -0
  34. {grss-4.3.2 → grss-4.3.6}/grss/__init__.py +0 -0
  35. {grss-4.3.2 → grss-4.3.6}/grss/debias/get_debiasing_data.py +0 -0
  36. {grss-4.3.2 → grss-4.3.6}/grss/fit/__init__.py +0 -0
  37. {grss-4.3.2 → grss-4.3.6}/grss/fit/fit_ades.py +0 -0
  38. {grss-4.3.2 → grss-4.3.6}/grss/fit/fit_optical.py +0 -0
  39. {grss-4.3.2 → grss-4.3.6}/grss/fit/fit_radar.py +0 -0
  40. {grss-4.3.2 → grss-4.3.6}/grss/fit/fit_utils.py +0 -0
  41. {grss-4.3.2 → grss-4.3.6}/grss/kernels/get_kernels.py +0 -0
  42. {grss-4.3.2 → grss-4.3.6}/grss/prop/__init__.py +0 -0
  43. {grss-4.3.2 → grss-4.3.6}/grss/prop/prop_parallel.py +0 -0
  44. {grss-4.3.2 → grss-4.3.6}/grss/prop/prop_unscented.py +0 -0
  45. {grss-4.3.2 → grss-4.3.6}/grss.egg-info/SOURCES.txt +0 -0
  46. {grss-4.3.2 → grss-4.3.6}/grss.egg-info/dependency_links.txt +0 -0
  47. {grss-4.3.2 → grss-4.3.6}/grss.egg-info/requires.txt +0 -0
  48. {grss-4.3.2 → grss-4.3.6}/grss.egg-info/top_level.txt +0 -0
  49. {grss-4.3.2 → grss-4.3.6}/include/elements.h +0 -0
  50. {grss-4.3.2 → grss-4.3.6}/include/force.h +0 -0
  51. {grss-4.3.2 → grss-4.3.6}/include/gr15.h +0 -0
  52. {grss-4.3.2 → grss-4.3.6}/include/interpolate.h +0 -0
  53. {grss-4.3.2 → grss-4.3.6}/include/observe.h +0 -0
  54. {grss-4.3.2 → grss-4.3.6}/include/parallel.h +0 -0
  55. {grss-4.3.2 → grss-4.3.6}/include/pck.h +0 -0
  56. {grss-4.3.2 → grss-4.3.6}/include/spk.h +0 -0
  57. {grss-4.3.2 → grss-4.3.6}/include/stm.h +0 -0
  58. {grss-4.3.2 → grss-4.3.6}/include/timeconvert.h +0 -0
  59. {grss-4.3.2 → grss-4.3.6}/requirements.txt +0 -0
  60. {grss-4.3.2 → grss-4.3.6}/setup.cfg +0 -0
  61. {grss-4.3.2 → grss-4.3.6}/src/CMakeLists.txt +0 -0
  62. {grss-4.3.2 → grss-4.3.6}/src/elements.cpp +0 -0
  63. {grss-4.3.2 → grss-4.3.6}/src/gr15.cpp +0 -0
  64. {grss-4.3.2 → grss-4.3.6}/src/observe.cpp +0 -0
  65. {grss-4.3.2 → grss-4.3.6}/src/parallel.cpp +0 -0
  66. {grss-4.3.2 → grss-4.3.6}/src/spk.cpp +0 -0
  67. {grss-4.3.2 → grss-4.3.6}/src/stm.cpp +0 -0
  68. {grss-4.3.2 → grss-4.3.6}/src/timeconvert.cpp +0 -0
  69. {grss-4.3.2 → grss-4.3.6}/src/utilities.cpp +0 -0
  70. {grss-4.3.2 → grss-4.3.6}/tests/cpp/prop/CMakeLists.txt +0 -0
  71. {grss-4.3.2 → grss-4.3.6}/tests/cpp/prop/apophis.cpp +0 -0
  72. {grss-4.3.2 → grss-4.3.6}/tests/cpp/prop/didymos.cpp +0 -0
  73. {grss-4.3.2 → grss-4.3.6}/tests/cpp/prop/pck_map.cpp +0 -0
  74. {grss-4.3.2 → grss-4.3.6}/tests/cpp/prop/spk_map.cpp +0 -0
  75. {grss-4.3.2 → grss-4.3.6}/tests/python/prop/apophis.ipynb +0 -0
  76. {grss-4.3.2 → grss-4.3.6}/tests/python/prop/didymos.ipynb +0 -0
  77. {grss-4.3.2 → grss-4.3.6}/tests/python/prop/icarus.ipynb +0 -0
  78. {grss-4.3.2 → grss-4.3.6}/tests/python/prop/moshup.ipynb +0 -0
  79. {grss-4.3.2 → grss-4.3.6}/tests/python/run_tests.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: grss
3
- Version: 4.3.2
3
+ Version: 4.3.6
4
4
  Summary: GRSS: Gauss-Radau Small-body Simulator
5
5
  Author: Rahil Makadia
6
6
  Author-email: makadia2@illinois.edu
@@ -1,4 +1,6 @@
1
1
  #!/bin/bash
2
+ # make error stop the script
3
+ set -e
2
4
 
3
5
  clean=0
4
6
  docs=0
@@ -3,6 +3,7 @@
3
3
  import sys
4
4
  import datetime
5
5
  import numpy as np
6
+ import pandas as pd
6
7
  import matplotlib.pyplot as plt
7
8
  from astropy.time import Time
8
9
 
@@ -562,7 +563,7 @@ class FitSimulation:
562
563
  self.fixed_propsim_params[key] = nongrav_info[key]
563
564
  self.fixed_propsim_params['events'] = events if events is not None else []
564
565
  self.prior_est = None
565
- self.prior_sigs = None
566
+ self.prior_sig = None
566
567
  self._priors_given = False
567
568
  self._xbar0 = None
568
569
  self._info0 = None
@@ -574,6 +575,7 @@ class FitSimulation:
574
575
  num_auto_rejected = np.sum(sel_ast == 'D')
575
576
  num_force_rejected = np.sum(sel_ast == 'd')
576
577
  self.num_rejected = num_auto_rejected + num_force_rejected
578
+ self.info_mats = []
577
579
  self.converged = False
578
580
  return None
579
581
 
@@ -642,10 +644,10 @@ class FitSimulation:
642
644
  ValueError
643
645
  If the prior estimates and sigmas do not have the same length.
644
646
  """
645
- # check that the keys in self.x_init are the same as in self.prior_est and self.prior_sigs
646
- if self.prior_est is not None and self.prior_sigs is not None:
647
+ # check that the keys in self.x_init are the same as in self.prior_est and self.prior_sig
648
+ if self.prior_est is not None and self.prior_sig is not None:
647
649
  self._priors_given = True
648
- if len(self.prior_est) != len(self.prior_sigs):
650
+ if len(self.prior_est) != len(self.prior_sig):
649
651
  raise ValueError("Prior estimates and sigmas must have the same length.")
650
652
  for key in self.prior_est.keys():
651
653
  if key not in self.x_init:
@@ -654,7 +656,7 @@ class FitSimulation:
654
656
  self._xbar0 = np.zeros(self.n_fit)
655
657
  for key in self.prior_est.keys():
656
658
  idx = list(self.x_init.keys()).index(key)
657
- self._info0[idx, idx] = 1/self.prior_sigs[key]**2
659
+ self._info0[idx, idx] = 1/self.prior_sig[key]**2
658
660
  self._xbar0[idx] = self.prior_est[key] - self.x_init[key]
659
661
  self._prior_constant = list(self.x_nom.values())+self._xbar0
660
662
  return None
@@ -693,56 +695,60 @@ class FitSimulation:
693
695
  raise ValueError("Simulated observation data must have the same length.")
694
696
  perm_id = self.obs['permID'][0]
695
697
  prov_id = self.obs['provID'][0]
698
+ new_sim_obs = []
696
699
  for i, time in enumerate(times):
697
- idx = len(self.obs)
700
+ this_obs = {}
698
701
  mode = modes[i]
699
702
  if mode not in {'SIM_CCD', 'SIM_OCC', 'SIM_RAD_DEL', 'SIM_RAD_DOP'}:
700
703
  raise ValueError(f"Unknown simulated observation mode {mode}.")
701
704
  weight = weights[i]
702
705
  if isinstance(perm_id, str):
703
- self.obs.loc[idx, 'permID'] = f'SIM_{perm_id}'
706
+ this_obs['permID'] = f'SIM_{perm_id}'
704
707
  if isinstance(prov_id, str):
705
- self.obs.loc[idx, 'provID'] = f'SIM_{prov_id}'
706
- self.obs.loc[idx, 'obsTime'] = f'{time.utc.isot}Z'
707
- self.obs.loc[idx, 'obsTimeMJD'] = time.utc.mjd
708
- self.obs.loc[idx, 'obsTimeMJDTDB'] = time.tdb.mjd
709
- self.obs.loc[idx, 'mode'] = mode
708
+ this_obs['provID'] = f'SIM_{prov_id}'
709
+ this_obs['obsTime'] = f'{time.utc.isot}Z'
710
+ this_obs['obsTimeMJD'] = time.utc.mjd
711
+ this_obs['obsTimeMJDTDB'] = time.tdb.mjd
712
+ this_obs['mode'] = mode
710
713
  if mode in {'SIM_CCD', 'SIM_OCC'}:
711
- self.obs.loc[idx, 'stn'] = 'SIM'
712
- self.obs.loc[idx, 'ra'] = np.nan
713
- self.obs.loc[idx, 'rmsRA'] = weight[0]
714
- self.obs.loc[idx, 'sigRA'] = weight[0]
715
- self.obs.loc[idx, 'dec'] = np.nan
716
- self.obs.loc[idx, 'rmsDec'] = weight[1]
717
- self.obs.loc[idx, 'sigDec'] = weight[1]
718
- self.obs.loc[idx, 'rmsCorr'] = weight[2]
719
- self.obs.loc[idx, 'sigCorr'] = weight[2]
720
- self.obs.loc[idx, 'cosDec'] = 1.0
714
+ this_obs['stn'] = 'SIM'
715
+ this_obs['ra'] = np.nan
716
+ this_obs['rmsRA'] = weight[0]
717
+ this_obs['sigRA'] = weight[0]
718
+ this_obs['dec'] = np.nan
719
+ this_obs['rmsDec'] = weight[1]
720
+ this_obs['sigDec'] = weight[1]
721
+ this_obs['rmsCorr'] = weight[2]
722
+ this_obs['sigCorr'] = weight[2]
723
+ this_obs['cosDec'] = 1.0
721
724
  elif mode in {'SIM_RAD_DEL', 'SIM_RAD_DOP'}:
722
- self.obs.loc[idx, 'trx'] = 'SIM'
723
- self.obs.loc[idx, 'rcv'] = 'SIM'
724
- self.obs.loc[idx, 'com'] = 1
725
+ this_obs['trx'] = 'SIM'
726
+ this_obs['rcv'] = 'SIM'
727
+ this_obs['com'] = 1
725
728
  if mode == 'SIM_RAD_DEL':
726
- self.obs.loc[idx, 'delay'] = np.nan
727
- self.obs.loc[idx, 'rmsDelay'] = weight[0]
728
- self.obs.loc[idx, 'sigDelay'] = weight[0]
729
- self.obs.loc[idx, 'doppler'] = np.nan
730
- self.obs.loc[idx, 'rmsDoppler'] = np.nan
731
- self.obs.loc[idx, 'sigDoppler'] = np.nan
729
+ this_obs['delay'] = np.nan
730
+ this_obs['rmsDelay'] = weight[0]
731
+ this_obs['sigDelay'] = weight[0]
732
+ this_obs['doppler'] = np.nan
733
+ this_obs['rmsDoppler'] = np.nan
734
+ this_obs['sigDoppler'] = np.nan
732
735
  else:
733
- self.obs.loc[idx, 'delay'] = np.nan
734
- self.obs.loc[idx, 'rmsDelay'] = np.nan
735
- self.obs.loc[idx, 'sigDelay'] = np.nan
736
- self.obs.loc[idx, 'doppler'] = np.nan
737
- self.obs.loc[idx, 'rmsDoppler'] = weight[0]
738
- self.obs.loc[idx, 'sigDoppler'] = weight[0]
739
- self.obs.loc[idx, 'frq'] = 8560.0
740
- self.obs.loc[idx, 'ctr'] = 399
741
- self.obs.loc[idx, 'sys'] = 'ITRF'
742
- self.obs.loc[idx, 'pos1'] = 0.0
743
- self.obs.loc[idx, 'pos2'] = 0.0
744
- self.obs.loc[idx, 'pos3'] = 0.0
745
- self.obs.loc[idx, 'selAst'] = 'a'
736
+ this_obs['delay'] = np.nan
737
+ this_obs['rmsDelay'] = np.nan
738
+ this_obs['sigDelay'] = np.nan
739
+ this_obs['doppler'] = np.nan
740
+ this_obs['rmsDoppler'] = weight[0]
741
+ this_obs['sigDoppler'] = weight[0]
742
+ this_obs['frq'] = 8560.0
743
+ this_obs['ctr'] = 399
744
+ this_obs['sys'] = 'ITRF'
745
+ this_obs['pos1'] = 0.0
746
+ this_obs['pos2'] = 0.0
747
+ this_obs['pos3'] = 0.0
748
+ this_obs['selAst'] = 'a'
749
+ new_sim_obs.append(this_obs)
750
+ sim_obs_df = pd.DataFrame(new_sim_obs)
751
+ self.obs = pd.concat([self.obs, sim_obs_df], ignore_index=True)
746
752
  return None
747
753
 
748
754
  def _parse_observation_arrays(self, obs_df):
@@ -1635,33 +1641,29 @@ class FitSimulation:
1635
1641
  raise ValueError("Observer info length not recognized.")
1636
1642
  resid = residuals[i]
1637
1643
  # calculate chi-squared for each residual if after the first iteration
1638
- if self.n_iter > 1:
1644
+ if start_rejecting and size == 2:
1639
1645
  obs_cov = self.obs_cov[i]
1640
1646
  obs_partials = partials[j:j+size, :]
1641
1647
  if sel_ast[i] in {'D', 'd'}:
1642
1648
  resid_cov = obs_cov + obs_partials @ full_cov @ obs_partials.T
1643
1649
  else:
1644
1650
  resid_cov = obs_cov - obs_partials @ full_cov @ obs_partials.T
1645
- if size == 1:
1646
- resid_cov_inv = 1.0/resid_cov
1647
- else:
1648
- resid_cov_det = resid_cov[0,0]*resid_cov[1,1] - resid_cov[0,1]*resid_cov[1,0]
1649
- resid_cov_inv = np.array([[resid_cov[1,1], -resid_cov[0,1]],
1650
- [-resid_cov[1,0], resid_cov[0,0]]])/resid_cov_det
1651
- residual_chi_squared = resid @ resid_cov_inv @ resid.T
1652
- res_chisq_vals[i] = residual_chi_squared
1653
- # outlier rejection, only reject RA/Dec measurements
1654
- if start_rejecting and size == 2:
1655
- if abs(residual_chi_squared) > chi_reject**2 and sel_ast[i] not in {'a', 'd'}:
1651
+ resid_cov_det = resid_cov[0,0]*resid_cov[1,1] - resid_cov[0,1]*resid_cov[1,0]
1652
+ resid_cov_inv = np.array([[resid_cov[1,1], -resid_cov[0,1]],
1653
+ [-resid_cov[1,0], resid_cov[0,0]]])/resid_cov_det
1654
+ outlier_chisq = resid @ resid_cov_inv @ resid.T
1655
+ # outlier rejection, only reject RA/Dec measurements
1656
+ if abs(outlier_chisq) > chi_reject**2 and sel_ast[i] not in {'a', 'd'}:
1656
1657
  if sel_ast[i] == 'A':
1657
1658
  self.num_rejected += 1
1658
1659
  sel_ast[i] = 'D'
1659
- elif abs(residual_chi_squared) < chi_recover**2 and sel_ast[i] == 'D':
1660
+ elif abs(outlier_chisq) < chi_recover**2 and sel_ast[i] == 'D':
1660
1661
  sel_ast[i] = 'A'
1661
1662
  self.num_rejected -= 1
1663
+ res_chisq_vals[i] = resid @ self.obs_weight[i] @ resid.T
1662
1664
  if sel_ast[i] not in {'D', 'd'}:
1663
1665
  rms_u += resid @ resid.T
1664
- chi_sq += resid @ self.obs_weight[i] @ resid.T
1666
+ chi_sq += res_chisq_vals[i]
1665
1667
  j += size
1666
1668
  # # write res_chisq_vals to file if any values are negative
1667
1669
  # if np.any(res_chisq_vals < 0):
@@ -1759,6 +1761,7 @@ class FitSimulation:
1759
1761
  atwb = np.zeros(self.n_fit)
1760
1762
  j = 0
1761
1763
  sel_ast = self.obs['selAst'].values
1764
+ self.info_mats = []
1762
1765
  for i, obs_info_len in enumerate(self.observer_info_lengths):
1763
1766
  if obs_info_len in {4, 7}:
1764
1767
  size = 2
@@ -1768,10 +1771,12 @@ class FitSimulation:
1768
1771
  raise ValueError("Observer info length not recognized.")
1769
1772
  if sel_ast[i] in {'D', 'd'}:
1770
1773
  j += size
1774
+ self.info_mats.append(atwa.copy())
1771
1775
  continue
1772
1776
  atwa += partials[j:j+size, :].T @ self.obs_weight[i] @ partials[j:j+size, :]
1773
1777
  atwb += partials[j:j+size, :].T @ self.obs_weight[i] @ residuals[i]
1774
1778
  j += size
1779
+ self.info_mats.append(atwa.copy())
1775
1780
  # use pseudo-inverse if the data arc is less than 7 days
1776
1781
  if self.obs.obsTimeMJD.max() - self.obs.obsTimeMJD.min() < 7.0:
1777
1782
  self.covariance = np.linalg.pinv(atwa, rcond=1e-20, hermitian=True)
@@ -2074,7 +2079,7 @@ class FitSimulation:
2074
2079
  f.write(f" {units_dict[key[:2]]}")
2075
2080
  if self._priors_given and key in self.prior_est:
2076
2081
  p_val = self.prior_est[key]
2077
- p_sig = self.prior_sigs[key]
2082
+ p_sig = self.prior_sig[key]
2078
2083
  if key in ['om', 'w', 'i']:
2079
2084
  p_val *= 180/np.pi
2080
2085
  p_sig *= 180/np.pi
@@ -2114,7 +2119,7 @@ class FitSimulation:
2114
2119
  f.write(f" {units_dict[key[:2]]}")
2115
2120
  if self._priors_given and key in self.prior_est:
2116
2121
  p_val = self.prior_est[key]
2117
- p_sig = self.prior_sigs[key]
2122
+ p_sig = self.prior_sig[key]
2118
2123
  if key in ['om', 'w', 'i']:
2119
2124
  p_val *= 180/np.pi
2120
2125
  p_sig *= 180/np.pi
@@ -2211,7 +2216,7 @@ class FitSimulation:
2211
2216
  f.write(f'{final_val:18.11e}{half_tab}')
2212
2217
  f.write(f'{final_unc:18.11e}{half_tab}')
2213
2218
  f.write(f'{final_val-init_val:+18.11e}{half_tab}')
2214
- f.write(f'{(final_val-init_val)/init_unc:+10.3f}\n')
2219
+ f.write(f'{(final_val-init_val)/final_unc:+10.3f}\n')
2215
2220
  f.write("\n")
2216
2221
  if itrn.iter_number <= self.n_iter:
2217
2222
  f.write(subsection_full + '\n')
@@ -12,6 +12,7 @@ __all__ = [ 'equat2eclip',
12
12
  'plot_ca_summary',
13
13
  'plot_bplane',
14
14
  'plot_earth_impact',
15
+ 'compare_earth_impact',
15
16
  ]
16
17
 
17
18
  earth_obliq = 84381.448/3600.0*np.pi/180.0
@@ -536,27 +537,48 @@ def plot_bplane(ca_list, plot_offset=False, scale_coords=False, n_std=3, units_k
536
537
  lon = None
537
538
  lat = None
538
539
  impact_any = lon is not None and lat is not None
540
+ if kizner_nan and opik_nan and scaled_nan and not mtp_nan:
541
+ print("WARNING: Close approaches have some invalid B-planes but do have MTP. "
542
+ "Object might be captured.")
539
543
  if len(ca_list) >= 100 or sigma_points is not None:
544
+ # remove nans before converting data to ellipse
540
545
  if not kizner_nan:
541
546
  kizner_data = data_to_ellipse(kizner_x, kizner_y, n_std, 'kizner',
542
547
  print_ellipse_params, units, sigma_points)
543
548
  else:
544
- kizner_data = None
549
+ print(f"Kizner B-plane nan count: {np.sum(np.isnan(kizner_x))}")
550
+ non_nan_idx = np.where(~np.isnan(kizner_x) & ~np.isnan(kizner_y))[0]
551
+ kizner_data = data_to_ellipse(kizner_x[non_nan_idx], kizner_y[non_nan_idx],
552
+ n_std, 'kizner',
553
+ print_ellipse_params, units, sigma_points)
545
554
  if not opik_nan:
546
555
  opik_data = data_to_ellipse(opik_x, opik_y, n_std, 'opik',
547
556
  print_ellipse_params, units, sigma_points)
548
557
  else:
549
- opik_data = None
558
+ print(f"Opik B-plane nan count: {np.sum(np.isnan(opik_x))}")
559
+ non_nan_idx = np.where(~np.isnan(opik_x) & ~np.isnan(opik_y))[0]
560
+ opik_data = data_to_ellipse(opik_x[non_nan_idx], opik_y[non_nan_idx],
561
+ n_std, 'opik',
562
+ print_ellipse_params, units, sigma_points)
550
563
  if not scaled_nan:
551
564
  scaled_data = data_to_ellipse(scaled_x, scaled_y, n_std, 'scaled',
552
565
  print_ellipse_params, units, sigma_points)
553
566
  else:
554
- scaled_data = None
567
+ print(f"Scaled B-plane nan count: {np.sum(np.isnan(scaled_x))}")
568
+ non_nan_idx = np.where(~np.isnan(scaled_x) & ~np.isnan(scaled_y))[0]
569
+ scaled_data = data_to_ellipse(scaled_x[non_nan_idx], scaled_y[non_nan_idx],
570
+ n_std, 'scaled',
571
+ print_ellipse_params, units, sigma_points)
555
572
  if not mtp_nan:
556
573
  mtp_data = data_to_ellipse(mtp_x, mtp_y, n_std, 'mtp',
557
574
  print_ellipse_params, units, sigma_points)
558
575
  else:
559
- mtp_data = None
576
+ print(f"MTP B-plane nan count: {np.sum(np.isnan(mtp_x))}")
577
+ non_nan_idx = np.where(~np.isnan(mtp_x) & ~np.isnan(mtp_y))[0]
578
+ mtp_data = data_to_ellipse(mtp_x[non_nan_idx], mtp_y[non_nan_idx],
579
+ n_std, 'mtp',
580
+ print_ellipse_params, units, sigma_points)
581
+
560
582
  elif len(ca_list[0].xRel) >= 42 and analytic_info is not None:
561
583
  all_data = partials_to_ellipse(ca_list[0], au2units, n_std,
562
584
  print_ellipse_params, units, analytic_info)
@@ -566,9 +588,6 @@ def plot_bplane(ca_list, plot_offset=False, scale_coords=False, n_std=3, units_k
566
588
  opik_data = None
567
589
  scaled_data = None
568
590
  mtp_data = None
569
- if kizner_nan and opik_nan and scaled_nan and not mtp_nan:
570
- print("WARNING: Close approaches have no valid B-planes but do have MTP. "
571
- "Object might be captured.")
572
591
  fig, axes = plt.subplots(2, 2, figsize=(9, 9), dpi=250)
573
592
  plot_single_bplane(axes[0,0], kizner_x, kizner_y, kizner_data, 'kizner',
574
593
  focus_factor, show_central_body, plot_offset, scale_coords,
@@ -886,3 +905,95 @@ def plot_earth_impact(impact_list, print_ellipse_params=False, sigma_points=None
886
905
  y=0.93)
887
906
  plt.show()
888
907
  return None
908
+
909
+ def compare_earth_impact(coord1, coord2, sig1, sig2, labels, zoom_size=5.0e3, save_name=None):
910
+ """
911
+ Show two impact circumstances and compare them.
912
+
913
+ Parameters
914
+ ----------
915
+ coord1 : np.ndarray
916
+ [time, lon, lat] of the first impact
917
+ coord2 : np.ndarray
918
+ [time, lon, lat] of the second impact
919
+ sig1 : np.ndarray
920
+ [time, lon, lat] uncertainty of the first impact
921
+ sig2 : np.ndarray
922
+ [time, lon, lat] uncertainty of the second impact
923
+ labels : list
924
+ List of labels for the two impacts
925
+ zoom_size : float, optional
926
+ Size of the zoomed in plot, in km, by default 5.0e3.
927
+ save_name : str, optional
928
+ Name to save the plots as, by default None.
929
+
930
+ Returns
931
+ -------
932
+ None : NoneType
933
+ None
934
+ """
935
+ _, lon1, lat1 = coord1
936
+ _, lon2, lat2 = coord2
937
+ fig, ax = plt.subplots(1, 3, figsize=(10, 4), dpi=150, gridspec_kw={'width_ratios': [4, 3, 1]})
938
+ size = zoom_size*1e3
939
+ m = Basemap(width=size,height=size,
940
+ rsphere=(6378137.00,6356752.3142),
941
+ resolution='i',area_thresh=size/100,projection='lcc',
942
+ lat_0=lat1,lon_0=lon1,ax=ax[0])
943
+ m.bluemarble()
944
+ m.drawcountries()
945
+ lat_step = lon_step = 3 if zoom_size <= 1e3 else 10
946
+ if zoom_size <= 500:
947
+ lat_step = lon_step = 1
948
+ if zoom_size <= 100:
949
+ lat_step = lon_step = 0.5
950
+ if zoom_size > 5e3:
951
+ lon_step = 20
952
+ lat_step = 20
953
+ # draw parallels and meridians.
954
+ parallels = np.arange(-90, 91, lat_step)
955
+ # labels = [left,right,top,bottom]
956
+ m.drawparallels(parallels,labels=[True,False,False,False], color='gray')
957
+ meridians = np.arange(0, 361, lon_step)
958
+ m.drawmeridians(meridians,labels=[False,False,False,True], color='gray')
959
+ x_lon1,y_lat1 = m(lon1,lat1)
960
+ m.plot(x_lon1,y_lat1,'b.')
961
+ x_lon2,y_lat2 = m(lon2,lat2)
962
+ m.plot(x_lon2,y_lat2,'r.')
963
+ # draw line between two points
964
+ m.plot([x_lon1,x_lon2], [y_lat1,y_lat2], 'g--')
965
+
966
+ # plot error bars for grss solution, and plot GRSS solution
967
+ n_sig = 1
968
+ deg2m = np.pi/180*6378137.0
969
+ sig1[1:] *= deg2m
970
+ sig2[1:] *= deg2m
971
+ diff = coord2 - coord1
972
+ # diff[0] *= 86400*1e3
973
+ diff[1:] *= deg2m
974
+ diff[1] *= np.cos(lat1*np.pi/180)
975
+
976
+ # add two subplots
977
+ ax[1].set_xlabel('Longitude Difference [m]')
978
+ ax[1].set_ylabel('Latitude Difference [m]')
979
+ ax[1].errorbar(0.0, 0.0, xerr=sig1[1]*n_sig, yerr=sig1[2]*n_sig,
980
+ fmt='s', capsize=6, markeredgewidth=1.5, lw=1.5, label=labels[0])
981
+ ax[1].errorbar(diff[1], diff[2], xerr=sig2[1]*n_sig, yerr=sig2[2]*n_sig,
982
+ fmt='o', capsize=6, markeredgewidth=1.5, lw=1.5, label=labels[1])
983
+ ax[1].legend(ncol=1)
984
+
985
+ ax[2].set_ylabel('Time Difference [ms]')
986
+ ax[2].errorbar(0.0, 0.0, yerr=sig1[0]*n_sig,
987
+ fmt='s', capsize=6, markeredgewidth=1.5, lw=1.5, label=labels[0])
988
+ ax[2].errorbar(0.0, diff[0], yerr=sig2[0]*n_sig,
989
+ fmt='o', capsize=6, markeredgewidth=1.5, lw=1.5, label=labels[1])
990
+ ax[2].set_xticks([])
991
+ ax[2].yaxis.tick_right()
992
+ ax[2].yaxis.set_label_position('right')
993
+
994
+ fig.tight_layout()
995
+ if save_name is not None:
996
+ plt.savefig(save_name, bbox_inches='tight')
997
+ plt.suptitle(f'Impact Circumstance Comparison ({n_sig}$\\sigma$)', y=1.05)
998
+ plt.show()
999
+ return None
@@ -12,11 +12,11 @@ __all__ = [ 'default_kernel_path',
12
12
  grss_path = os.path.dirname(os.path.abspath(__file__))
13
13
  default_kernel_path = f'{grss_path}/kernels/'
14
14
 
15
- def _connected_to_internet(url='http://www.google.com/', timeout=5):
15
+ def _connected_to_internet(url='http://www.google.com/', timeout=30):
16
16
  try:
17
17
  _ = requests.head(url, timeout=timeout)
18
18
  return True
19
- except requests.ConnectionError:
19
+ except (requests.ConnectionError, requests.Timeout):
20
20
  print("No internet connection available.")
21
21
  return False
22
22
 
@@ -0,0 +1 @@
1
+ 4.3.6
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: grss
3
- Version: 4.3.2
3
+ Version: 4.3.6
4
4
  Summary: GRSS: Gauss-Radau Small-body Simulator
5
5
  Author: Rahil Makadia
6
6
  Author-email: makadia2@illinois.edu
@@ -28,6 +28,12 @@ void impact_r_calc(PropSimulation *propSim, const size_t &i, const size_t &j,
28
28
  std::vector<real> get_rel_state(PropSimulation *propSim, const size_t &i,
29
29
  const size_t &j, const real &t);
30
30
 
31
+ /**
32
+ * @brief Compute partials of B-plane parameters.
33
+ */
34
+ void get_bplane_partials(PropSimulation *propSim, CloseApproachParameters *ca,
35
+ const real &mu, const real &radius);
36
+
31
37
  /**
32
38
  * @brief Compute the time of close approach or impact using Brent's method
33
39
  * for root finding in a bracketed interval.
@@ -16,7 +16,9 @@ void PropSimulation::integrate() {
16
16
  this->map_ephemeris();
17
17
  this->preprocess();
18
18
  gr15(this);
19
- this->unmap_ephemeris();
19
+ if (!this->unsafePersistentMemoryMap) {
20
+ this->unmap_ephemeris();
21
+ }
20
22
 
21
23
  // flip vectors if integration is backwards in time
22
24
  if (this->integParams.t0 > this->integParams.tf) {
@@ -109,11 +109,17 @@ class Body {
109
109
  real J2 = 0.0L;
110
110
  real poleRA = 0.0L;
111
111
  real poleDec = 90.0L;
112
+ size_t nZon = 0;
113
+ size_t nTes = 0;
114
+ std::vector<real> J = {};
115
+ std::vector<std::vector<real>> C = {};
116
+ std::vector<std::vector<real>> S = {};
112
117
  std::string name;
113
118
  int spiceId;
114
119
  real pos[3], vel[3], acc[3];
115
120
  bool isPPN = false;
116
121
  bool isJ2 = false;
122
+ bool isHarmonic = false;
117
123
  bool isNongrav = false;
118
124
  bool isMajor = false;
119
125
  real caTol = 0.1;
@@ -121,6 +127,12 @@ class Body {
121
127
  * @brief Set the J2 for the body.
122
128
  */
123
129
  void set_J2(real J2, real poleRA, real poleDec);
130
+ /**
131
+ * @brief Set the harmonic coefficients for the body.
132
+ */
133
+ void set_harmonics(real poleRA, real poleDec, int nZon, int nTes,
134
+ std::vector<real> J, std::vector<std::vector<real>> C,
135
+ std::vector<std::vector<real>> S);
124
136
  };
125
137
 
126
138
  /**
@@ -389,12 +401,19 @@ class ImpactParameters : public CloseApproachParameters {
389
401
  /**
390
402
  * @brief Structure to hold interpolation parameters for a PropSimulation.
391
403
  *
404
+ * @param interpIdx Index of the interpolation parameters.
405
+ * @param t0 Initial time of the interpolation window.
406
+ * @param dt0 Timespan of the interpolation window.
407
+ * @param b0 Interpolation coefficients for the interpolation window.
408
+ * @param xInteg0 States at the initial time of the interpolation window.
409
+ * @param accInteg0 Accelerations at the initial time of the interpolation window.
392
410
  * @param tStack Stack of integrator epochs.
393
411
  * @param xIntegStack Stack of states at integrator epochs.
394
412
  * @param bStack Stack of interpolation coefficients at integrator epochs.
395
413
  * @param accIntegStack Stack of accelerations at integrator epochs.
396
414
  */
397
415
  struct InterpolationParameters {
416
+ size_t interpIdx = 0;
398
417
  real t0;
399
418
  real dt0;
400
419
  std::vector<real> b0;
@@ -411,7 +430,9 @@ struct InterpolationParameters {
411
430
  *
412
431
  * @param name Name of the simulation.
413
432
  * @param DEkernelPath Path to DE kernels.
414
- * @param ephem Ephemeris object for the simulation.
433
+ * @param spkEphem SPK ephemeris object for the simulation.
434
+ * @param pckEphem PCK ephemeris object for the simulation.
435
+ * @param unsafePersistentMemoryMap Flag to indicate if ephemeris memory maps should not be unmapped (unsafe, default: false).
415
436
  * @param consts Constants object for the simulation.
416
437
  * @param integParams Integration parameters for the simulation.
417
438
  * @param spiceBodies Vector of SpiceBody objects in the simulation.
@@ -423,7 +444,6 @@ struct InterpolationParameters {
423
444
  * @param t Current time of the simulation.
424
445
  * @param xInteg Current state of the simulation.
425
446
  * @param interpParams Interpolation parameters for the simulation.
426
- * @param interpIdx Index of the interpolation parameters.
427
447
  * @param tEvalUTC Flag to indicate if the evaluation times are in UTC.
428
448
  * @param evalApparentState Flag to indicate if the apparent state is evaluated.
429
449
  * @param evalMeasurements Flag to indicate if measurements are evaluated.
@@ -459,6 +479,7 @@ class PropSimulation {
459
479
  std::string DEkernelPath;
460
480
  SpkEphemeris spkEphem;
461
481
  PckEphemeris pckEphem;
482
+ bool unsafePersistentMemoryMap = false;
462
483
  /**
463
484
  * @brief Construct a new PropSimulation object.
464
485
  */
@@ -490,7 +511,6 @@ class PropSimulation {
490
511
  real t;
491
512
  std::vector<real> xInteg;
492
513
  InterpolationParameters interpParams;
493
- size_t interpIdx = 0;
494
514
  bool tEvalUTC = false;
495
515
  bool evalApparentState = false;
496
516
  bool evalMeasurements = false;
@@ -63,6 +63,16 @@ using std::tanh;
63
63
  *
64
64
  */
65
65
  #define EARTH_OBLIQUITY 84381.448 / 3600.0 * DEG2RAD
66
+ /**
67
+ * @brief Value of Earth's WGS84 radius in AU.
68
+ *
69
+ */
70
+ #define EARTH_RAD_WGS84 6378.137 / 1.495978707e8
71
+ /**
72
+ * @brief Value of Earth's WGS84 flattening.
73
+ *
74
+ */
75
+ #define EARTH_FLAT_WGS84 1.0 / 298.257223563
66
76
 
67
77
  /**
68
78
  * @brief Wrap an angle to the range [0, 2*pi).
@@ -66,6 +66,9 @@ dependencies = {file = "requirements.txt"}
66
66
 
67
67
  [tool.cibuildwheel]
68
68
  before-build = [
69
+ "cd ./extern/",
70
+ "python3 get_cspice.py",
71
+ "cd ..",
69
72
  "pip install pybind11",
70
73
  ". ./build_cpp.sh -clean",
71
74
  ]
@@ -31,14 +31,17 @@ class CMakeBuild(build_ext):
31
31
  """
32
32
 
33
33
  def build_extension(self, ext: CMakeExtension) -> None:
34
- subprocess.run(["./build_cpp.sh"], cwd=ext.sourcedir, check=True)
34
+ result = subprocess.run(["./build_cpp.sh"], cwd=ext.sourcedir, check=True)
35
+ # check return code of subprocess
36
+ if result.returncode != 0:
37
+ raise subprocess.CalledProcessError(returncode=result.returncode, cmd=result.args)
35
38
  binary_created = [f for f in os.listdir(f"{ext.sourcedir}/build/")
36
39
  if f.startswith("libgrss")]
37
40
  if not binary_created:
38
41
  raise FileNotFoundError("libgrss binary for C++ source code not found "
39
42
  "in cmake build directory")
40
43
  os.system(f"cp {ext.sourcedir}/build/libgrss.* {self.build_lib}/grss/")
41
- return
44
+ return None
42
45
 
43
46
  setup(
44
47
  ext_modules=[CMakeExtension("libgrss")],