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 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
+ [![build](https://github.com/esa/dSGP4/actions/workflows/build.yml/badge.svg)](https://github.com/esa/dSGP4/actions/workflows/build.yml)
3
+ [![codecov](https://codecov.io/gh/esa/dSGP4/graph/badge.svg?token=K3py7YT8UR)](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,9 @@
1
+ __version__ = '0.0.2'
2
+
3
+ from .sgp4 import sgp4
4
+ from .initl import initl
5
+ from .sgp4init import sgp4init
6
+ from .newton_method import newton_method, update_TLE
7
+ from .sgp4_batched import sgp4_batched
8
+ from . import tle
9
+ from .tle import TLE
@@ -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)