dsgp4 0.0.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.
- dsgp4-0.0.2/PKG-INFO +15 -0
- dsgp4-0.0.2/README.md +42 -0
- dsgp4-0.0.2/dsgp4/__init__.py +9 -0
- dsgp4-0.0.2/dsgp4/initl.py +64 -0
- dsgp4-0.0.2/dsgp4/newton_method.py +207 -0
- dsgp4-0.0.2/dsgp4/sgp4.py +185 -0
- dsgp4-0.0.2/dsgp4/sgp4_batched.py +264 -0
- dsgp4-0.0.2/dsgp4/sgp4init.py +204 -0
- dsgp4-0.0.2/dsgp4/tle.py +430 -0
- dsgp4-0.0.2/dsgp4/util.py +381 -0
- dsgp4-0.0.2/dsgp4.egg-info/PKG-INFO +15 -0
- dsgp4-0.0.2/dsgp4.egg-info/SOURCES.txt +23 -0
- dsgp4-0.0.2/dsgp4.egg-info/dependency_links.txt +1 -0
- dsgp4-0.0.2/dsgp4.egg-info/requires.txt +8 -0
- dsgp4-0.0.2/dsgp4.egg-info/top_level.txt +1 -0
- dsgp4-0.0.2/setup.cfg +4 -0
- dsgp4-0.0.2/setup.py +36 -0
- dsgp4-0.0.2/tests/test_batched_sgp4.py +1558 -0
- dsgp4-0.0.2/tests/test_differentiability.py +1687 -0
- dsgp4-0.0.2/tests/test_initl.py +1583 -0
- dsgp4-0.0.2/tests/test_newton.py +42 -0
- dsgp4-0.0.2/tests/test_sgp4.py +1612 -0
- dsgp4-0.0.2/tests/test_sgp4init.py +1620 -0
- dsgp4-0.0.2/tests/test_tle.py +171 -0
- dsgp4-0.0.2/tests/test_utils.py +40 -0
dsgp4-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: dsgp4
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Differentiable SGP4 prototype
|
|
5
|
+
Author: Acciarini & Baydin
|
|
6
|
+
Author-email: giacomo.acciarini@gmail.com
|
|
7
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: torch
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest; extra == "dev"
|
|
13
|
+
Requires-Dist: coverage; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
15
|
+
Requires-Dist: sgp4>=2.21; extra == "dev"
|
dsgp4-0.0.2/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# dSGP4
|
|
2
|
+
[](https://github.com/esa/dSGP4/actions/workflows/build.yml)
|
|
3
|
+
[](https://codecov.io/gh/esa/dSGP4)
|
|
4
|
+
|
|
5
|
+
Differentiable SGP4.
|
|
6
|
+
<!-- PROJECT LOGO -->
|
|
7
|
+
<br />
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://github.com/esa/dSGP4">
|
|
10
|
+
<img src="doc/_static/logo_dsgp4.png" alt="Logo" width="280">
|
|
11
|
+
</a>
|
|
12
|
+
<p align="center">
|
|
13
|
+
Differentiable SGP4
|
|
14
|
+
<br />
|
|
15
|
+
<a href="https://esa.github.io/dSGP4"><strong>Explore the docs »</strong></a>
|
|
16
|
+
<br />
|
|
17
|
+
<br />
|
|
18
|
+
<a href="https://github.com/esa/dSGP4/issues/new/choose">Report bug</a>
|
|
19
|
+
·
|
|
20
|
+
<a href="https://github.com/esa/dSGP4/issues/new/choose">Request feature</a>
|
|
21
|
+
</p>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
## Goals
|
|
25
|
+
|
|
26
|
+
* Differentiable version of SGP4
|
|
27
|
+
|
|
28
|
+
## Installation, documentation, and examples
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Authors:
|
|
32
|
+
* [Giacomo Acciarini](https://www.esa.int/gsp/ACT/team/giacomo_acciarini/)
|
|
33
|
+
* [Atılım Güneş Baydin](http://gbaydin.github.io/)
|
|
34
|
+
* [Dario Izzo](https://www.esa.int/gsp/ACT/team/dario_izzo/)
|
|
35
|
+
|
|
36
|
+
The project originated after the work of the authors at the [University of Oxford OX4AI Lab](https://oxai4science.github.io/).
|
|
37
|
+
|
|
38
|
+
## Acknowledgements:
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Contact:
|
|
42
|
+
* `giacomo.acciarini@gmail.com`
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import numpy
|
|
2
|
+
import torch
|
|
3
|
+
|
|
4
|
+
from . import util
|
|
5
|
+
|
|
6
|
+
#torch.set_default_dtype(torch.float64)
|
|
7
|
+
|
|
8
|
+
def initl(
|
|
9
|
+
xke, j2,
|
|
10
|
+
ecco, epoch, inclo, no,
|
|
11
|
+
method,
|
|
12
|
+
opsmode,
|
|
13
|
+
):
|
|
14
|
+
|
|
15
|
+
x2o3 = torch.tensor(2.0 / 3.0);
|
|
16
|
+
|
|
17
|
+
eccsq = ecco * ecco;
|
|
18
|
+
omeosq = 1.0 - eccsq;
|
|
19
|
+
rteosq = omeosq.sqrt();
|
|
20
|
+
cosio = inclo.cos();
|
|
21
|
+
cosio2 = cosio * cosio;
|
|
22
|
+
|
|
23
|
+
ak = torch.pow(xke / no, x2o3);
|
|
24
|
+
d1 = 0.75 * j2 * (3.0 * cosio2 - 1.0) / (rteosq * omeosq);
|
|
25
|
+
del_ = d1 / (ak * ak);
|
|
26
|
+
adel = ak * (1.0 - del_ * del_ - del_ *
|
|
27
|
+
(1.0 / 3.0 + 134.0 * del_ * del_ / 81.0));
|
|
28
|
+
del_ = d1/(adel * adel);
|
|
29
|
+
no = no / (1.0 + del_);
|
|
30
|
+
|
|
31
|
+
ao = torch.pow(xke / no, x2o3);
|
|
32
|
+
sinio = inclo.sin();
|
|
33
|
+
po = ao * omeosq;
|
|
34
|
+
con42 = 1.0 - 5.0 * cosio2;
|
|
35
|
+
con41 = -con42-cosio2-cosio2;
|
|
36
|
+
ainv = 1.0 / ao;
|
|
37
|
+
posq = po * po;
|
|
38
|
+
rp = ao * (1.0 - ecco);
|
|
39
|
+
method = 'n';
|
|
40
|
+
|
|
41
|
+
if opsmode == 'a':
|
|
42
|
+
# gst time
|
|
43
|
+
ts70 = epoch - 7305.0;
|
|
44
|
+
ds70 = torch.floor_divide(ts70 + 1.0e-8,1);
|
|
45
|
+
tfrac = ts70 - ds70;
|
|
46
|
+
# find greenwich location at epoch
|
|
47
|
+
c1 = torch.tensor(1.72027916940703639e-2);
|
|
48
|
+
thgr70= torch.tensor(1.7321343856509374);
|
|
49
|
+
fk5r = torch.tensor(5.07551419432269442e-15);
|
|
50
|
+
c1p2p = c1 + (2*numpy.pi);
|
|
51
|
+
gsto = (thgr70 + c1*ds70 + c1p2p*tfrac + ts70*ts70*fk5r) % (2*numpy.pi)
|
|
52
|
+
if gsto < 0.0:
|
|
53
|
+
gsto = gsto + (2*numpy.pi);
|
|
54
|
+
|
|
55
|
+
else:
|
|
56
|
+
gsto = util.gstime(epoch + 2433281.5);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
no,
|
|
60
|
+
method,
|
|
61
|
+
ainv, ao, con41, con42, cosio,
|
|
62
|
+
cosio2,eccsq, omeosq, posq,
|
|
63
|
+
rp, rteosq,sinio , gsto,
|
|
64
|
+
)
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import torch
|
|
3
|
+
import datetime
|
|
4
|
+
from .sgp4 import sgp4
|
|
5
|
+
from .sgp4init import sgp4init
|
|
6
|
+
from . import util
|
|
7
|
+
from .tle import TLE
|
|
8
|
+
#torch.set_default_dtype(torch.float64)
|
|
9
|
+
|
|
10
|
+
def initial_guess(tle_0, time_mjd, target_state=None):
|
|
11
|
+
"""
|
|
12
|
+
This method takes an initial TLE and the time at which we want to propagate it, and returns
|
|
13
|
+
a set of parameter related to an initial guess useful to find the TLE observation that corresponds to the propagated state
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
- tle_0 (``dsgp4.tle.TLE``): starting TLE at time0
|
|
17
|
+
- new_date (``datetime.datetime``): new date of the TLE, as a datetime object
|
|
18
|
+
- target_state (``torch.tensor``): a 2x3 tensor for the position and velocity of the state. If None, then this is computed
|
|
19
|
+
by propagating `tle_0` at the `new_date`.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
- y0 (``torch.tensor``): initial guess for the TLE elements. In particular, y0 contains
|
|
23
|
+
the following elements (see SGP4 for a thorough description of these parameters):
|
|
24
|
+
* y0[0]: bstar (B* parameter)
|
|
25
|
+
* y0[1]: ndot (mean motion first derivative)
|
|
26
|
+
* y0[2]: nddot (mean motion second derivative)
|
|
27
|
+
* y0[3]: ecco (eccentricity)
|
|
28
|
+
* y0[4]: argpo (argument of perigee)
|
|
29
|
+
* y0[5]: inclo (inclination)
|
|
30
|
+
* y0[6]: mo (mean anomaly)
|
|
31
|
+
* y0[7]: no_kozai (mean anomaly in certain units)
|
|
32
|
+
* y0[8]: nodeo (right ascension of the ascending node)
|
|
33
|
+
- target_state (``torch.tensor``): this expresses the cartesian state as position & velocity in km & km/s, at the propagated time.
|
|
34
|
+
The objective of the Newton method is to find the TLE observation that corresponds to that, at the propagated
|
|
35
|
+
time.
|
|
36
|
+
- new_tle (``dsgp4.tle.TLE``): TLE constructed with `y0`, in order to find the TLE that corresponds to `target_state`
|
|
37
|
+
- tle_elements_0 (``dict``): dictionary used to construct `new_tle`
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
new_date=util.from_mjd_to_datetime(time_mjd)
|
|
41
|
+
if target_state is None:
|
|
42
|
+
whichconst=util.get_gravity_constants("wgs-84")
|
|
43
|
+
sgp4init(whichconst=whichconst,
|
|
44
|
+
opsmode=tle_0._opsmode,
|
|
45
|
+
satn=tle_0.satellite_catalog_number,
|
|
46
|
+
epoch=(tle_0._jdsatepoch+tle_0._jdsatepochF)-2433281.5,
|
|
47
|
+
xbstar=tle_0._bstar,
|
|
48
|
+
xndot=tle_0._ndot,
|
|
49
|
+
xnddot=tle_0._nddot,
|
|
50
|
+
xecco=tle_0._ecco,
|
|
51
|
+
xargpo=tle_0._argpo,
|
|
52
|
+
xinclo=tle_0._inclo,
|
|
53
|
+
xmo=tle_0._mo,
|
|
54
|
+
xno_kozai=tle_0._no_kozai,
|
|
55
|
+
xnodeo=tle_0._nodeo,
|
|
56
|
+
satellite=tle_0)
|
|
57
|
+
tsince=(time_mjd-util.from_datetime_to_mjd(tle_0._epoch))*1440.
|
|
58
|
+
x=tsince*torch.ones(1,1,requires_grad=True)#torch.rand(1,1, requires_grad=True)
|
|
59
|
+
target_state=sgp4(tle_0, x)
|
|
60
|
+
#print(target_state.size(), target_state)
|
|
61
|
+
tle_elements_0=util.from_cartesian_to_tle_elements(target_state.detach().numpy()*1e3)
|
|
62
|
+
#you need to transform it to the appropriate units
|
|
63
|
+
xpdotp=1440.0 / (2.0 *np.pi)
|
|
64
|
+
tle_elements_0['epoch_year']=new_date.year
|
|
65
|
+
tle_elements_0['epoch_days']=util.from_datetime_to_fractional_day(new_date)
|
|
66
|
+
#tle_elements_0['no_kozai']=tle_elements_0['mean_motion']/(np.pi/43200.0)/xpdotp
|
|
67
|
+
#tle_elements_0['inclo']=tle_elements_0['inclination']
|
|
68
|
+
#tle_elements_0['nodeo']=tle_elements_0['raan']
|
|
69
|
+
#tle_elements_0['argpo']=tle_elements_0['argument_of_perigee']
|
|
70
|
+
#tle_elements_0['mo']=tle_elements_0['mean_anomaly']
|
|
71
|
+
#tle_elements_0['ecco']=tle_elements_0['eccentricity']
|
|
72
|
+
tle_elements_0['mean_motion_first_derivative']=tle_0.mean_motion_first_derivative
|
|
73
|
+
tle_elements_0['mean_motion_second_derivative']=tle_0.mean_motion_second_derivative
|
|
74
|
+
tle_elements_0['classification']=tle_0.classification
|
|
75
|
+
tle_elements_0['international_designator']=tle_0.international_designator
|
|
76
|
+
tle_elements_0['satellite_catalog_number']=tle_0.satellite_catalog_number
|
|
77
|
+
tle_elements_0['ephemeris_type']=tle_0.ephemeris_type
|
|
78
|
+
tle_elements_0['element_number']=tle_0.element_number
|
|
79
|
+
tle_elements_0['b_star']=tle_0.b_star
|
|
80
|
+
tle_elements_0['revolution_number_at_epoch']=tle_0.revolution_number_at_epoch
|
|
81
|
+
new_tle=TLE(tle_elements_0)
|
|
82
|
+
y0=torch.tensor([new_tle._bstar,
|
|
83
|
+
new_tle._ndot,
|
|
84
|
+
new_tle._nddot,
|
|
85
|
+
new_tle._ecco,
|
|
86
|
+
new_tle._argpo,
|
|
87
|
+
new_tle._inclo,
|
|
88
|
+
new_tle._mo,
|
|
89
|
+
new_tle._no_kozai,
|
|
90
|
+
new_tle._nodeo,
|
|
91
|
+
0.],requires_grad=True)
|
|
92
|
+
return y0, target_state, new_tle, tle_elements_0
|
|
93
|
+
|
|
94
|
+
def update_TLE(old_tle,y0):
|
|
95
|
+
"""
|
|
96
|
+
This method takes a TLE and an initial guess, and returns a TLE updated accordingly. It
|
|
97
|
+
is useful while doing Newton iterations.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
- old_tle (``dsgp4.tle.TLE``): TLE corresponding to previous guess
|
|
101
|
+
- y0 (``torch.tensor``): initial guess (see the docstrings for `initial_guess` to know the content of `y0`)
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
- new_tle (``dsgp4.tle.TLE``): updated TLE
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
tle_elements={}
|
|
108
|
+
xpdotp=1440.0/(2.0 *np.pi)
|
|
109
|
+
#I need to convert no_kozai to consistent units for what is expected by TLE class:
|
|
110
|
+
no_kozai=float(y0[7])*xpdotp
|
|
111
|
+
tle_elements['mean_motion']=no_kozai*np.pi/43200.0
|
|
112
|
+
|
|
113
|
+
#the rest is consistent:
|
|
114
|
+
tle_elements['b_star']=float(y0[0])
|
|
115
|
+
tle_elements['raan']=float(y0[8])
|
|
116
|
+
tle_elements['eccentricity']=float(y0[3])
|
|
117
|
+
tle_elements['argument_of_perigee']=float(y0[4])
|
|
118
|
+
tle_elements['inclination']=float(y0[5])
|
|
119
|
+
tle_elements['mean_anomaly']=float(y0[6])
|
|
120
|
+
|
|
121
|
+
#now the ones that stay the same:
|
|
122
|
+
tle_elements['mean_motion_first_derivative']=old_tle.mean_motion_first_derivative
|
|
123
|
+
tle_elements['mean_motion_second_derivative']=old_tle.mean_motion_second_derivative
|
|
124
|
+
tle_elements['epoch_days']=old_tle.epoch_days
|
|
125
|
+
tle_elements['epoch_year']=old_tle.epoch_year
|
|
126
|
+
tle_elements['classification']=old_tle.classification
|
|
127
|
+
tle_elements['satellite_catalog_number']=old_tle.satellite_catalog_number
|
|
128
|
+
tle_elements['ephemeris_type']=old_tle.ephemeris_type
|
|
129
|
+
tle_elements['international_designator']=old_tle.international_designator
|
|
130
|
+
tle_elements['revolution_number_at_epoch']=old_tle.revolution_number_at_epoch
|
|
131
|
+
tle_elements['element_number']=old_tle.element_number
|
|
132
|
+
return TLE(tle_elements)
|
|
133
|
+
|
|
134
|
+
def newton_method(tle_0, time_mjd, target_state=None, new_tol=1e-12,max_iter=50):
|
|
135
|
+
"""
|
|
136
|
+
This method performs Newton method starting from an initial TLE and a given propagation time. The objective
|
|
137
|
+
is to find a TLE that accurately reconstructs the propagated state, at observation time.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
- tle_0 (``dsgp4.tle.TLE``): starting TLE (i.e., TLE at a given initial time)
|
|
141
|
+
- new_date (``datetime.datetime``): time (as a datetime object) at which we want the state to be propagated, and we want the TLE at that time
|
|
142
|
+
- target_state (``torch.tensor``): 2x3 tensor representing target state. If None, then this is directly computed by propagating the TLE at `new_date`
|
|
143
|
+
- new_tol (``float``): newton tolerance
|
|
144
|
+
- max_iter (``int``): maximum iterations for Newton's method
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
- tle (``dsgp4.tle.TLE``): found TLE
|
|
148
|
+
- y (``torch.tensor``): returns the solution that satisfied F(y)=0 (with a given tolerance)
|
|
149
|
+
or (in case no convergence is reached within the tolerance) the best
|
|
150
|
+
guess found in `max_iter` iterations
|
|
151
|
+
"""
|
|
152
|
+
i=0
|
|
153
|
+
tol=1e9
|
|
154
|
+
y0, state_target, next_tle, tle_elements_0=initial_guess(tle_0=tle_0,time_mjd=time_mjd,target_state=target_state)
|
|
155
|
+
#print(y0,states)
|
|
156
|
+
#newton iterations:
|
|
157
|
+
while i<max_iter and tol>new_tol:
|
|
158
|
+
#print(f"y0: {y0}")
|
|
159
|
+
propagate=lambda x: util.propagate(x,next_tle,(time_mjd-util.from_datetime_to_mjd(next_tle._epoch))*1440.)
|
|
160
|
+
y1=util.clone_w_grad(y0)
|
|
161
|
+
y2=util.clone_w_grad(y0)
|
|
162
|
+
y3=util.clone_w_grad(y0)
|
|
163
|
+
y4=util.clone_w_grad(y0)
|
|
164
|
+
y5=util.clone_w_grad(y0)
|
|
165
|
+
r_x=propagate(y0)[0][0]
|
|
166
|
+
r_x.backward()
|
|
167
|
+
gradient_rx=y0.grad
|
|
168
|
+
r_y=propagate(y1)[0][1]
|
|
169
|
+
r_y.backward()
|
|
170
|
+
gradient_ry=y1.grad
|
|
171
|
+
r_z=propagate(y2)[0][2]
|
|
172
|
+
r_z.backward()
|
|
173
|
+
gradient_rz=y2.grad
|
|
174
|
+
v_x=propagate(y3)[1][0]
|
|
175
|
+
v_x.backward()
|
|
176
|
+
gradient_vx=y3.grad
|
|
177
|
+
v_y=propagate(y4)[1][1]
|
|
178
|
+
v_y.backward()
|
|
179
|
+
gradient_vy=y4.grad
|
|
180
|
+
v_z=propagate(y5)[1][2]
|
|
181
|
+
v_z.backward()
|
|
182
|
+
gradient_vz=y5.grad
|
|
183
|
+
F=np.array([float(r_x)-float(state_target[0][0]),float(r_y)-float(state_target[0][1]), float(r_z)-float(state_target[0][2]), float(v_x)-float(state_target[1][0]), float(v_y)-float(state_target[1][1]), float(v_z)-float(state_target[1][2])])
|
|
184
|
+
tol=np.linalg.norm(F)
|
|
185
|
+
#print(f"|F|: {tol}")
|
|
186
|
+
DF=np.stack((gradient_rx, gradient_ry, gradient_rz, gradient_vx, gradient_vy, gradient_vz))
|
|
187
|
+
#we remove the first three columns (they are all zeros):
|
|
188
|
+
DF=DF[:,3:]
|
|
189
|
+
DF=DF[:,:-1]
|
|
190
|
+
dY=-np.matmul(np.matmul(np.linalg.pinv(np.matmul(DF.T,DF)),DF.T),F)
|
|
191
|
+
#we make sure the eccentricity does not go to negative values:
|
|
192
|
+
if float(y0[3])+dY[0]<0:
|
|
193
|
+
dY[0]=-float(y0[3])*0.9999
|
|
194
|
+
dY=torch.tensor([0.,0.,0.]+list(dY)+[0.], requires_grad=True)
|
|
195
|
+
if tol<new_tol:
|
|
196
|
+
print(f"F(y): {np.linalg.norm(F)}")
|
|
197
|
+
print(f"Solution found, at iter: {i}")
|
|
198
|
+
return next_tle, y0#+dY
|
|
199
|
+
else:
|
|
200
|
+
#Newton update:
|
|
201
|
+
#y0=y0+dY
|
|
202
|
+
y0=torch.tensor([float(el1)+float(el2) for el1, el2 in zip(list(y0),list(dY))],requires_grad=True)
|
|
203
|
+
next_tle=update_TLE(next_tle, y0)
|
|
204
|
+
i+=1
|
|
205
|
+
print("Solution not found, returning best found so far")
|
|
206
|
+
print(f"F(y): {np.linalg.norm(F)}")
|
|
207
|
+
return next_tle, y0
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import numpy
|
|
2
|
+
import torch
|
|
3
|
+
#torch.set_default_dtype(torch.float64)
|
|
4
|
+
|
|
5
|
+
#@torch.jit.script
|
|
6
|
+
def sgp4(satellite, tsince):
|
|
7
|
+
"""
|
|
8
|
+
This function represents the SGP4 propagator. Having created the TLE object, and
|
|
9
|
+
initialized the propagator (using `dsgp4.sgp4.sgp4init`), one can use this method
|
|
10
|
+
to propagate the TLE at future times. The method returns the satellite position and velocity
|
|
11
|
+
in km and km/s, respectively, after `tsince` minutes.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
- satellite (``dsgp4.tle.TLE``): TLE object
|
|
15
|
+
- tsince (``torch.tensor``): time to propagate, since the TLE epoch, in minutes
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
- state (``torch.tensor``): a 2x3 tensor, where the first row represents the spacecraft
|
|
19
|
+
position (in km) and the second the spacecraft velocity (in km/s)
|
|
20
|
+
"""
|
|
21
|
+
mrt = torch.zeros(tsince.size())
|
|
22
|
+
temp4 = torch.ones(tsince.size())*1.5e-12
|
|
23
|
+
x2o3 = torch.tensor(2.0 / 3.0)
|
|
24
|
+
|
|
25
|
+
vkmpersec = torch.ones(tsince.size())*(satellite._radiusearthkm * satellite._xke/60.0)
|
|
26
|
+
|
|
27
|
+
# sgp4 error flag
|
|
28
|
+
satellite._t = tsince.clone()
|
|
29
|
+
satellite._error = torch.tensor(0)
|
|
30
|
+
|
|
31
|
+
# secular gravity and atmospheric drag
|
|
32
|
+
xmdf = satellite._mo + satellite._mdot * satellite._t
|
|
33
|
+
argpdf = satellite._argpo + satellite._argpdot * satellite._t
|
|
34
|
+
nodedf = satellite._nodeo + satellite._nodedot * satellite._t
|
|
35
|
+
argpm = argpdf
|
|
36
|
+
mm = xmdf
|
|
37
|
+
t2 = satellite._t * satellite._t
|
|
38
|
+
nodem = nodedf + satellite._nodecf * t2
|
|
39
|
+
tempa = 1.0 - satellite._cc1 * satellite._t
|
|
40
|
+
tempe = satellite._bstar * satellite._cc4 * satellite._t
|
|
41
|
+
templ = satellite._t2cof * t2
|
|
42
|
+
|
|
43
|
+
if satellite._isimp != 1:
|
|
44
|
+
delomg = satellite._omgcof * satellite._t
|
|
45
|
+
delmtemp = 1.0 + satellite._eta * xmdf.cos()
|
|
46
|
+
delm = satellite._xmcof * \
|
|
47
|
+
(delmtemp * delmtemp * delmtemp -
|
|
48
|
+
satellite._delmo)
|
|
49
|
+
temp = delomg + delm
|
|
50
|
+
mm = xmdf + temp
|
|
51
|
+
argpm = argpdf - temp
|
|
52
|
+
t3 = t2 * satellite._t
|
|
53
|
+
t4 = t3 * satellite._t
|
|
54
|
+
tempa = tempa - satellite._d2 * t2 - satellite._d3 * t3 - \
|
|
55
|
+
satellite._d4 * t4
|
|
56
|
+
tempe = tempe + satellite._bstar * satellite._cc5 * (mm.sin() -
|
|
57
|
+
satellite._sinmao)
|
|
58
|
+
templ = templ + satellite._t3cof * t3 + t4 * (satellite._t4cof +
|
|
59
|
+
satellite._t * satellite._t5cof)
|
|
60
|
+
|
|
61
|
+
nm = satellite._no_unkozai.clone()
|
|
62
|
+
em = satellite._ecco.clone()
|
|
63
|
+
inclm = satellite._inclo.clone()
|
|
64
|
+
|
|
65
|
+
satellite._error=torch.any(nm<=0)*2
|
|
66
|
+
|
|
67
|
+
am = torch.pow((satellite._xke / nm),x2o3) * tempa * tempa
|
|
68
|
+
nm = satellite._xke / torch.pow(am, 1.5)
|
|
69
|
+
em = em - tempe
|
|
70
|
+
|
|
71
|
+
if satellite._error==0.:
|
|
72
|
+
satellite._error=torch.any((em>=1.0) | (em<-0.001))*1
|
|
73
|
+
|
|
74
|
+
# sgp4fix fix tolerance to avoid a divide by zero
|
|
75
|
+
em=torch.where(em<1.0e-6,1.0e-6,em)
|
|
76
|
+
mm = mm + satellite._no_unkozai * templ
|
|
77
|
+
xlm = mm + argpm + nodem
|
|
78
|
+
emsq = em * em
|
|
79
|
+
temp = 1.0 - emsq
|
|
80
|
+
|
|
81
|
+
nodem = torch.fmod(nodem, torch.tensor(2*numpy.pi))
|
|
82
|
+
|
|
83
|
+
argpm = argpm % (2*numpy.pi)
|
|
84
|
+
xlm = xlm % (2*numpy.pi)
|
|
85
|
+
mm = (xlm - argpm - nodem) % (2*numpy.pi)
|
|
86
|
+
|
|
87
|
+
satellite._am = am.clone()
|
|
88
|
+
satellite._em = em.clone()
|
|
89
|
+
satellite._im = inclm.clone()
|
|
90
|
+
satellite._Om = nodem.clone()
|
|
91
|
+
satellite._om = argpm.clone()
|
|
92
|
+
satellite._mm = mm.clone()
|
|
93
|
+
satellite._nm = nm.clone()
|
|
94
|
+
|
|
95
|
+
# compute extra mean quantities
|
|
96
|
+
sinim = inclm.sin()
|
|
97
|
+
cosim = inclm.cos()
|
|
98
|
+
|
|
99
|
+
# add lunar-solar periodics
|
|
100
|
+
ep = em
|
|
101
|
+
xincp = inclm
|
|
102
|
+
argpp = argpm
|
|
103
|
+
nodep = nodem
|
|
104
|
+
mp = mm
|
|
105
|
+
sinip = sinim
|
|
106
|
+
cosip = cosim
|
|
107
|
+
|
|
108
|
+
axnl = ep * argpp.cos()
|
|
109
|
+
temp = 1.0 / (am * (1.0 - ep * ep))
|
|
110
|
+
aynl = ep* argpp.sin() + temp * satellite._aycof
|
|
111
|
+
xl = mp + argpp + nodep + temp * satellite._xlcof * axnl
|
|
112
|
+
|
|
113
|
+
# solve kepler's equation
|
|
114
|
+
u = (xl - nodep) % (2*numpy.pi)
|
|
115
|
+
eo1 = u
|
|
116
|
+
tem5 = torch.ones(tsince.size())
|
|
117
|
+
# kepler iteration
|
|
118
|
+
for _ in range(10):
|
|
119
|
+
coseo1=eo1.cos()
|
|
120
|
+
sineo1=eo1.sin()
|
|
121
|
+
tem5 = 1.0 - coseo1 * axnl - sineo1 * aynl
|
|
122
|
+
tem5 = (u - aynl * coseo1 + axnl * sineo1 - eo1) / tem5
|
|
123
|
+
tem5=torch.where(tem5>=0.95, 0.95, tem5)
|
|
124
|
+
tem5=torch.where(tem5<=-0.95, -0.95, tem5)
|
|
125
|
+
eo1 = eo1 + tem5
|
|
126
|
+
|
|
127
|
+
# short period preliminary quantities
|
|
128
|
+
ecose = axnl*coseo1 + aynl*sineo1
|
|
129
|
+
esine = axnl*sineo1 - aynl*coseo1
|
|
130
|
+
el2 = axnl*axnl + aynl*aynl
|
|
131
|
+
pl = am*(1.0-el2)
|
|
132
|
+
if satellite._error==0.:
|
|
133
|
+
satellite._error=torch.any(pl<0.)*4
|
|
134
|
+
|
|
135
|
+
rl = am * (1.0 - ecose)
|
|
136
|
+
rdotl = am.sqrt() * esine/rl
|
|
137
|
+
rvdotl = pl.sqrt() / rl
|
|
138
|
+
betal = (1.0 - el2).sqrt()
|
|
139
|
+
temp = esine / (1.0 + betal)
|
|
140
|
+
sinu = am / rl * (sineo1 - aynl - axnl * temp)
|
|
141
|
+
cosu = am / rl * (coseo1 - axnl + aynl * temp)
|
|
142
|
+
su = torch.atan2(sinu, cosu)
|
|
143
|
+
sin2u = (cosu + cosu) * sinu
|
|
144
|
+
cos2u = 1.0 - 2.0 * sinu * sinu
|
|
145
|
+
temp = 1.0 / pl
|
|
146
|
+
temp1 = 0.5 * satellite._j2 * temp
|
|
147
|
+
temp2 = temp1 * temp
|
|
148
|
+
|
|
149
|
+
mrt = rl * (1.0 - 1.5 * temp2 * betal * satellite._con41) + \
|
|
150
|
+
0.5 * temp1 * satellite._x1mth2 * cos2u
|
|
151
|
+
su = su - 0.25 * temp2 * satellite._x7thm1 * sin2u
|
|
152
|
+
xnode = nodep + 1.5 * temp2 * cosip * sin2u
|
|
153
|
+
xinc = xincp + 1.5 * temp2 * cosip * sinip * cos2u
|
|
154
|
+
mvt = rdotl - nm * temp1 * satellite._x1mth2 * sin2u / satellite._xke
|
|
155
|
+
rvdot = rvdotl + nm * temp1 * (satellite._x1mth2 * cos2u +
|
|
156
|
+
1.5 * satellite._con41) / satellite._xke
|
|
157
|
+
|
|
158
|
+
# orientation vectors
|
|
159
|
+
sinsu = su.sin()
|
|
160
|
+
cossu = su.cos()
|
|
161
|
+
snod = xnode.sin()
|
|
162
|
+
cnod = xnode.cos()
|
|
163
|
+
sini = xinc.sin()
|
|
164
|
+
cosi = xinc.cos()
|
|
165
|
+
xmx = -snod * cosi
|
|
166
|
+
xmy = cnod * cosi
|
|
167
|
+
ux = xmx * sinsu + cnod * cossu
|
|
168
|
+
uy = xmy * sinsu + snod * cossu
|
|
169
|
+
uz = sini * sinsu
|
|
170
|
+
vx = xmx * cossu - cnod * sinsu
|
|
171
|
+
vy = xmy * cossu - snod * sinsu
|
|
172
|
+
vz = sini * cossu
|
|
173
|
+
|
|
174
|
+
# position and velocity (in km and km/sec)
|
|
175
|
+
_mr = mrt * satellite._radiusearthkm
|
|
176
|
+
|
|
177
|
+
r = torch.stack((_mr * ux, _mr * uy, _mr * uz))
|
|
178
|
+
v = torch.stack(((mvt * ux + rvdot * vx) * vkmpersec,
|
|
179
|
+
(mvt * uy + rvdot * vy) * vkmpersec,
|
|
180
|
+
(mvt * uz + rvdot * vz) * vkmpersec))
|
|
181
|
+
|
|
182
|
+
# decaying satellites
|
|
183
|
+
if satellite._error==0.:
|
|
184
|
+
satellite._error=torch.any(mrt<1.0)*6
|
|
185
|
+
return torch.transpose(torch.stack((r.squeeze(),v.squeeze()),1),0,-1)#torch.cat((r.swapaxes(0,2),v.swapaxes(0,2)),1)#torch.stack(list(r)+list(v)).reshape(2,3)
|