SPK Checker

This notebook processes an ESOC (Mission Analysis or Flight Dynamics) OEM trajectory file into a SPICE kernel (SPK).

In [1]:
!date # Notebook was last run
Tue Oct  9 16:11:10 CEST 2018
In [2]:
%matplotlib inline

import spiceypy
import os
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np

from datetime import datetime as dt
from numpy import round
In [3]:
spiceypy.furnsh('/Users/mcosta/JUICE/kernels/mk/juice_crema_3_2_ops_local.tm')
In [4]:
input_file = 'JUI-ESOC-MOC-RP-001_(WP-578)-4/JUICE_CReMA4d0_Baseline_0001.oem'
spk_file = 'JUICE_CReMA4d0_Baseline_0001_man.bsp'
In [5]:
spacecraft = 'JUICE'
spacecraft_id = '-28'
frame = 'J2000'
ltcorr = 'None'

Import data

Read in the Mission Analysis orbit data using read_table function in Pandas.

The input file has no header, we'll use the regular expression for 1 or more spaces as the column separator, we'll set the index column to be the first column (=0) and we'll parse the content of that first column using the date_parser function defined above.

We'll also manually specify the column names via the names attribute to the pd.read_table call.

In [6]:
def date_parser_function(et):
    et_list.append(float(et))
    return spiceypy.et2utc(float(et),'ISOC',6)

with open('oem_table.txt', 'w') as o:
    with open(input_file,'r') as i:
        data_flag = False
        previous_tdb = ''
        for line in i:
            if 'CENTER_NAME' in line:
                center = line.split('=')[-1].strip()
            if 'META_START' in line:
                data_flag = False
            if data_flag and line.strip() != '':
                line = line.split()
                tdb = spiceypy.str2et(line[0].replace('T',' ') + ' TDB')
                if tdb != previous_tdb:
                    o.write('{} {} {} {} {} {} {} {}\n'.format(tdb,
                                                         spiceypy.bodn2c(center),
                                                         line[1],line[2],line[3],
                                                         line[4],line[5],line[6])) 
                previous_tdb = tdb
            if 'META_STOP' in line:
                data_flag = True
In [7]:
et_list = []
trajectory_data = pd.read_table(
    'oem_table.txt', skiprows=0, header=None, sep=r"\s*", 
    engine='python', index_col=0, parse_dates=True, 
    date_parser = date_parser_function,
    names=['utc','c', 'x', 'y', 'z', 'vx', 'vy', 'vz'])

trajectory_data['r'] = np.sqrt(np.square(trajectory_data[['x','y','z']]).sum(axis=1))
trajectory_data['vr'] = np.sqrt(np.square(trajectory_data[['vx','vy','vz']]).sum(axis=1))
trajectory_data['et'] = et_list

trajectory_data = trajectory_data[['et','c','x', 'y', 'z', 'r', 'vx', 'vy', 'vz', 'vr']]
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/pandas/io/parsers.py:2227: FutureWarning: split() requires a non-empty pattern match.
  yield pat.split(line.strip())
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/pandas/io/parsers.py:2229: FutureWarning: split() requires a non-empty pattern match.
  yield pat.split(line.strip())
In [8]:
trajectory_data.head()
Out[8]:
et c x y z r vx vy vz vr
utc
2022-05-31 23:58:50.815090 7.073136e+08 399 1451.646737 -6393.821607 -971.572870 6628.136000 10.211802 2.938721 -4.081763 11.383223
2022-06-01 00:01:39.587156 7.073138e+08 399 3136.254922 -5778.113053 -1637.406918 6775.233339 9.693791 4.319357 -3.786825 11.267940
2022-06-01 00:04:34.008584 7.073139e+08 399 4759.147017 -4922.709396 -2263.781235 7211.605481 8.884948 5.425427 -3.386605 10.947450
2022-06-01 00:07:45.549424 7.073141e+08 399 6366.806711 -3801.931310 -2868.569045 7951.075257 7.902319 6.206306 -2.932729 10.467367
2022-06-01 00:11:27.293607 7.073144e+08 399 8000.105522 -2367.710204 -3466.021252 9034.436523 6.856252 6.665512 -2.470928 9.876372

Determine the number of significant figures/digits for the position and velocity vector data provided.

In [9]:
xyz_signifig = len(str(trajectory_data['x'].values[0]).split('.')[1])
vxvyvz_signifig = len(str( trajectory_data['vx'].values[0]).split('.')[1])

print ("Position Vectors Significance: {}".format(xyz_signifig))
print ("Velocity Vectors Significance: {}".format(vxvyvz_signifig))
Position Vectors Significance: 6
Velocity Vectors Significance: 6

Examine sample of input data

Define start and end times for the sample of data you wish to examine. The range of times in the input data are listed below.

In [10]:
print(trajectory_data.index[0])
print(trajectory_data.index[-1])
2022-05-31 23:58:50.815090
2033-06-26 02:43:39.378497
In [11]:
#print(trajectory_data.loc['2030-10-01 15'])
#print(trajectory_data.loc['2030-10-01 17'])

#sample_start = trajectory_data.index.get_loc('2030-10-01 15:30:50.735136') 
#sample_end = trajectory_data.index.get_loc('2030-10-01 15:59:16.630431')  

sample_start = 0
sample_end = -1
#sample_end = min(1000, trajectory_data.shape[0])
In [12]:
mpl.rcParams['figure.figsize'] = (26.0, 10.0)

title = 'Input Data State Vector Components'
ax = trajectory_data[sample_start:sample_end][['x','y','z']].plot(title=title)
ax.set_ylabel("km");
plt.savefig('output/{}.png'.format('_'.join(title.split())))
In [13]:
title = 'Input Data State Vector Magnitude'
ax = trajectory_data[sample_start:sample_end][['r']].plot(title=title, marker='.')
ax.set_ylabel("km");
plt.savefig('output/{}.png'.format('_'.join(title.split())))
In [14]:
title = 'Input Data Velocity Vector Components'
ax = trajectory_data[sample_start:sample_end][['vx','vy','vz']].plot(title=title)
ax.set_ylabel("km/s");
plt.savefig('output/{}.png'.format('_'.join(title.split())))
In [15]:
title = 'Input Data Velocity Vector Magnitude'
ax = trajectory_data[sample_start:sample_end][['vr']].plot(title=title)
ax.set_ylabel("km/s");
plt.savefig('output/{}.png'.format('_'.join(title.split())))

Check the Step of the input for for interpolation analysis

In [16]:
data_sample1 = trajectory_data.head(len(trajectory_data.index)-1)
data_sample2 = trajectory_data.tail(len(trajectory_data.index)-1)

if data_sample1.shape==data_sample2.shape:
    cols = data_sample2.columns.tolist()
    diff_df = pd.DataFrame(columns=cols)
    for col in data_sample2.columns.tolist():
        diff_df[col] = data_sample2[col].values - data_sample1[col].values
    diff_df.index = data_sample2.index
else:
    print('The input and spice-sampled data were not of the same shape!')
    
diff_df.tail()
Out[16]:
et c x y z r vx vy vz vr
utc
2033-06-26 02:20:21.956071 351.474254 0 -409.874805 -400.646800 233.526223 -3.506534 0.131547 0.048301 0.319282 0.002096
2033-06-26 02:26:12.625677 350.669606 0 -355.146378 -375.136723 339.800642 -4.353005 0.174294 0.091545 0.287939 0.002600
2033-06-26 02:32:02.516260 349.890583 0 -286.826101 -335.179122 432.686178 -5.030173 0.210521 0.131415 0.245565 0.002963
2033-06-26 02:37:51.361828 348.845567 0 -207.383819 -282.097636 508.223987 -5.503101 0.238594 0.166234 0.193456 0.003165
2033-06-26 02:43:39.378497 348.016670 0 -120.265068 -218.405371 564.166774 -5.748252 0.257636 0.194795 0.133864 0.003245
In [17]:
title = 'Delta ET Differences (Sample)'
ax = diff_df[sample_start:sample_end][['et']].plot(title=title, marker='.')
ax.set_ylabel("seconds");
plt.savefig('output/{}.png'.format('_'.join(title.split())))
In [18]:
diff_df.loc['2032-10-15 10:08:23.999992':'2032-10-15 23:12:28.800018']
Out[18]:
et c x y z r vx vy vz vr
utc
2032-10-15 10:23:27.286516 1330.939408 0 -997.942556 650.154526 -924.675395 59.948689 -0.141865 0.000067 0.169746 -0.010206
2032-10-15 10:45:54.088773 1346.802257 0 -1179.531393 645.404444 -691.913777 59.514821 -0.109466 -0.018501 0.191037 -0.009953
2032-10-15 11:08:36.409085 1362.320312 0 -1317.948118 615.427070 -429.304389 55.434646 -0.073366 -0.036196 0.204930 -0.008972
2032-10-15 11:31:32.363198 1375.954113 0 -1405.775193 560.431376 -146.573422 48.118131 -0.034933 -0.052380 0.211014 -0.007379
2032-10-15 11:54:41.007287 1388.644089 0 -1439.910265 482.737113 144.897536 38.372560 0.004434 -0.066586 0.209503 -0.005383
2032-10-15 12:18:00.480386 1399.473099 0 -1417.456263 384.765413 433.726490 27.119157 0.043508 -0.078374 0.200484 -0.003204
2032-10-15 12:41:26.133806 1405.653420 0 -1336.000485 269.748027 706.597985 15.350121 0.080902 -0.087209 0.183954 -0.001080
2032-10-15 13:04:55.612303 1409.478498 0 -1200.888162 143.179958 952.188813 4.078749 0.115410 -0.092906 0.160811 0.000783
2032-10-15 13:28:26.503165 1410.890862 0 -1017.250022 10.216152 1160.137023 -5.961043 0.145873 -0.095228 0.131739 0.002244
2032-10-15 13:51:54.614554 1408.111389 0 -791.557089 -123.361377 1319.769835 -14.290286 0.170927 -0.093901 0.097532 0.003242
2032-10-15 14:15:20.415712 1405.801157 0 -535.759095 -252.255481 1428.475234 -20.803103 0.190090 -0.089192 0.059658 0.003808
2032-10-15 14:38:38.997453 1398.581741 0 -258.130423 -370.245852 1476.478186 -25.556842 0.201803 -0.080847 0.019185 0.004010
2032-10-15 15:01:49.365594 1390.368141 0 27.736588 -472.851674 1465.807639 -28.924837 0.205967 -0.069382 -0.022111 0.003988
2032-10-15 15:24:52.976503 1383.610910 0 311.192075 -557.090657 1399.806912 -31.389563 0.202672 -0.055276 -0.062812 0.003888
2032-10-15 15:47:50.000531 1377.024028 0 582.129775 -619.523495 1279.728522 -33.335115 0.191716 -0.038907 -0.101481 0.003832
2032-10-15 16:10:39.539595 1369.539064 0 829.699850 -657.227606 1109.310629 -35.029296 0.173234 -0.020829 -0.136550 0.003912
2032-10-15 16:33:19.533273 1359.993678 0 1042.908128 -668.079393 894.940576 -36.560569 0.147731 -0.001747 -0.166419 0.004160
2032-10-15 16:55:47.239061 1347.705788 0 1211.935625 -651.415288 646.171209 -37.830262 0.116191 0.017505 -0.189647 0.004554
2032-10-15 17:18:03.943333 1336.704272 0 1333.777627 -610.132055 375.766930 -38.701503 0.080203 0.036179 -0.205801 0.005030
2032-10-15 17:40:12.605157 1328.661824 0 1406.596459 -546.592330 93.366129 -38.859740 0.040974 0.053702 -0.214542 0.005487
2032-10-15 18:02:13.158755 1320.553598 0 1425.007902 -461.783802 -191.708538 -37.793553 -0.000350 0.069331 -0.214926 0.005780
2032-10-15 18:24:02.196165 1309.037410 0 1384.833964 -358.303935 -466.604856 -35.023902 -0.041985 0.082133 -0.206195 0.005764
2032-10-15 18:45:43.969529 1301.773364 0 1296.020597 -242.639712 -722.613549 -30.496820 -0.082342 0.091943 -0.189712 0.005379
2032-10-15 19:07:17.893030 1293.923501 0 1157.155879 -117.884105 -948.703437 -23.995229 -0.119585 0.097974 -0.165211 0.004547
2032-10-15 19:28:42.706241 1284.813212 0 974.099276 10.446374 -1134.710655 -15.682953 -0.151749 0.099798 -0.133669 0.003281
2032-10-15 19:50:06.068326 1283.362085 0 760.392708 137.787351 -1282.157907 -6.005981 -0.178438 0.097957 -0.097247 0.001669
2032-10-15 20:11:28.781157 1282.712831 0 517.888652 259.960708 -1380.563409 4.634492 -0.197725 0.091954 -0.056646 -0.000202
2032-10-15 20:32:52.330741 1283.549584 0 256.418455 372.234561 -1426.824108 15.545415 -0.208749 0.082101 -0.013828 -0.002175
2032-10-15 20:54:18.960293 1286.629552 0 -14.088470 470.622715 -1420.257162 25.989778 -0.211197 0.068929 0.029216 -0.004080
2032-10-15 21:15:52.649522 1293.689229 0 -284.578428 552.418762 -1363.110995 35.301707 -0.205402 0.053131 0.070728 -0.005767
2032-10-15 21:37:34.642048 1301.992526 0 -545.574726 613.700648 -1254.411489 42.788677 -0.191387 0.035299 0.108943 -0.007082
2032-10-15 21:59:27.731745 1313.089696 0 -788.380778 652.866321 -1099.375888 47.999597 -0.170207 0.016268 0.142521 -0.007933
2032-10-15 22:21:33.795993 1326.064249 0 -1004.306682 667.949968 -902.085907 50.602243 -0.142772 -0.003234 0.170298 -0.008260
2032-10-15 22:43:53.660278 1339.864285 0 -1184.695375 657.586105 -668.379876 50.459574 -0.110188 -0.022474 0.191337 -0.008055
2032-10-15 23:06:27.048400 1353.388122 0 -1321.416867 621.282504 -406.098906 47.654078 -0.073723 -0.040735 0.204943 -0.007364

Re-process input

In [19]:
et_times = trajectory_data['et'].tolist()

input_trajectory = pd.DataFrame(columns=('et','c','x', 'y', 'z', 'vx', 'vy', 'vz'))

min_step = 760.3

for i, epoch in enumerate(et_times):
    if i == 0:
        step = 0
    else: 
        step += et_times[i]-et_times[i-1]
        
    if step >= min_step:
        input_trajectory.loc[i] = trajectory_data.iloc[i]
        step = 0

input_trajectory.set_index('et', inplace=True)
In [20]:
input_trajectory.tail()
Out[20]:
c x y z vx vy vz
et
1.056634e+09 503.0 2199.166103 2072.355713 -875.258457 -0.537075 -0.118646 -1.681266
1.056635e+09 503.0 1289.515959 1599.993203 -2397.403197 -1.135360 -0.749180 -1.120170
1.056636e+09 503.0 -62.377164 580.097637 -3105.885758 -1.344840 -1.121731 -0.180033
1.056637e+09 503.0 -1390.563613 -635.022325 -2757.348591 -1.096567 -1.112041 0.822000
1.056638e+09 503.0 -2239.919911 -1627.435806 -1476.637784 -0.473158 -0.722847 1.548960
In [21]:
input_trajectory[['c','x', 'y', 'z', 'vx', 'vy', 'vz']].to_csv('filtered_input.data', sep=" ", header=False)

Quick-check the SPK

The SPICE toolkit app brief can be used to see a few details of the resulting kernel, and commnt can be used to view the setup used in producing the kernel.

In [22]:
! commnt -r $spk_file
 
CREATION DATE           =         2018-05-17
ORIGIN                  =         ESOC-OPS/GFA
AUTHOR                  =         A. BOUTONNET
INTERPLANETARY EPH      =         JPL DE432
JUPITER EPH             =         IMCCE L2
 
This kernel is the end-to-end trajectory used to prepare the CReMA 4.0
This kernel is identical to JUICE_CReMA3d2_Baseline_0001.bsp
 
 
 
In [23]:
! brief $spk_file -c -t -s
 
BRIEF -- Version 4.0.0, September 8, 2010 -- Toolkit Version N0066
 
 
Summary for: JUICE_CReMA4d0_Baseline_0001_man.bsp
 
Bodies                         Start of Interval (ET)          End of Interval (ET)
-------                        -----------------------------   -----------------------------
-28 JUICE w.r.t. 399 EARTH     2022 JUN 01 00:00:00.000        2022 JUN 04 01:41:33.267
-28 JUICE w.r.t. 10 SUN        2022 JUN 04 01:41:33.267        2023 MAY 27 20:58:59.253
-28 JUICE w.r.t. 399 EARTH     2023 MAY 27 20:58:59.253        2023 JUN 02 19:13:01.326
-28 JUICE w.r.t. 10 SUN        2023 JUN 02 19:13:01.326        2023 OCT 21 08:55:29.210
-28 JUICE w.r.t. 299 VENUS     2023 OCT 21 08:55:29.210        2023 OCT 23 19:30:38.392
-28 JUICE w.r.t. 10 SUN        2023 OCT 23 19:30:38.392        2024 AUG 31 14:28:29.514
-28 JUICE w.r.t. 399 EARTH     2024 AUG 31 14:28:29.514        2024 SEP 03 00:03:51.643
-28 JUICE w.r.t. 10 SUN        2024 SEP 03 00:03:51.643        2025 FEB 10 02:09:59.498
-28 JUICE w.r.t. 499 MARS      2025 FEB 10 02:09:59.498        2025 FEB 11 09:54:05.006
-28 JUICE w.r.t. 10 SUN        2025 FEB 11 09:54:05.006        2026 NOV 25 03:20:25.030
-28 JUICE w.r.t. 399 EARTH     2026 NOV 25 03:20:25.030        2026 NOV 26 23:16:34.351
-28 JUICE w.r.t. 10 SUN        2026 NOV 26 23:16:34.351        2029 JUL 15 22:20:14.986
-28 JUICE w.r.t. 599 JUPITER   2029 JUL 15 22:20:14.986        2029 OCT 06 22:09:22.084
-28 JUICE w.r.t. 503 GANYMEDE  2029 OCT 06 22:09:22.084        2029 OCT 06 23:52:26.977
-28 JUICE w.r.t. 599 JUPITER   2029 OCT 06 23:52:26.977        2030 MAY 09 14:15:19.448
-28 JUICE w.r.t. 503 GANYMEDE  2030 MAY 09 14:15:19.448        2030 MAY 09 16:17:48.323
-28 JUICE w.r.t. 599 JUPITER   2030 MAY 09 16:17:48.323        2030 JUL 05 19:24:38.078
-28 JUICE w.r.t. 503 GANYMEDE  2030 JUL 05 19:24:38.078        2030 JUL 05 21:25:03.094
-28 JUICE w.r.t. 599 JUPITER   2030 JUL 05 21:25:03.094        2030 AUG 13 02:33:03.397
-28 JUICE w.r.t. 503 GANYMEDE  2030 AUG 13 02:33:03.397        2030 AUG 13 04:34:53.190
-28 JUICE w.r.t. 599 JUPITER   2030 AUG 13 04:34:53.190        2030 SEP 03 13:37:13.221
-28 JUICE w.r.t. 503 GANYMEDE  2030 SEP 03 13:37:13.221        2030 SEP 03 15:38:45.612
-28 JUICE w.r.t. 599 JUPITER   2030 SEP 03 15:38:45.612        2030 SEP 17 09:37:45.900
-28 JUICE w.r.t. 502 EUROPA    2030 SEP 17 09:37:45.900        2030 SEP 17 11:03:04.401
-28 JUICE w.r.t. 599 JUPITER   2030 SEP 17 11:03:04.401        2030 OCT 01 15:28:17.620
-28 JUICE w.r.t. 502 EUROPA    2030 OCT 01 15:28:17.620        2030 OCT 01 16:54:10.309
-28 JUICE w.r.t. 599 JUPITER   2030 OCT 01 16:54:10.309        2030 OCT 13 08:01:19.790
-28 JUICE w.r.t. 504 CALLISTO  2030 OCT 13 08:01:19.790        2030 OCT 13 12:07:09.954
-28 JUICE w.r.t. 599 JUPITER   2030 OCT 13 12:07:09.954        2030 NOV 03 20:43:47.789
-28 JUICE w.r.t. 503 GANYMEDE  2030 NOV 03 20:43:47.789        2030 NOV 03 23:21:43.884
-28 JUICE w.r.t. 599 JUPITER   2030 NOV 03 23:21:43.884        2030 DEC 13 08:54:55.068
-28 JUICE w.r.t. 504 CALLISTO  2030 DEC 13 08:54:55.068        2030 DEC 13 13:09:41.336
-28 JUICE w.r.t. 599 JUPITER   2030 DEC 13 13:09:41.336        2030 DEC 30 01:25:57.249
-28 JUICE w.r.t. 504 CALLISTO  2030 DEC 30 01:25:57.249        2030 DEC 30 05:40:43.596
-28 JUICE w.r.t. 599 JUPITER   2030 DEC 30 05:40:43.596        2031 JAN 15 17:45:39.063
-28 JUICE w.r.t. 504 CALLISTO  2031 JAN 15 17:45:39.063        2031 JAN 15 22:00:17.373
-28 JUICE w.r.t. 599 JUPITER   2031 JAN 15 22:00:17.373        2031 FEB 01 10:05:35.506
-28 JUICE w.r.t. 504 CALLISTO  2031 FEB 01 10:05:35.506        2031 FEB 01 14:20:42.404
-28 JUICE w.r.t. 599 JUPITER   2031 FEB 01 14:20:42.404        2031 APR 25 20:34:41.595
-28 JUICE w.r.t. 504 CALLISTO  2031 APR 25 20:34:41.595        2031 APR 26 00:49:08.842
-28 JUICE w.r.t. 599 JUPITER   2031 APR 26 00:49:08.842        2031 MAY 12 12:54:24.375
-28 JUICE w.r.t. 504 CALLISTO  2031 MAY 12 12:54:24.375        2031 MAY 12 17:09:17.729
-28 JUICE w.r.t. 599 JUPITER   2031 MAY 12 17:09:17.729        2031 MAY 29 05:19:10.807
-28 JUICE w.r.t. 504 CALLISTO  2031 MAY 29 05:19:10.807        2031 MAY 29 09:34:09.943
-28 JUICE w.r.t. 599 JUPITER   2031 MAY 29 09:34:09.943        2031 JUN 14 21:43:34.947
-28 JUICE w.r.t. 504 CALLISTO  2031 JUN 14 21:43:34.947        2031 JUN 15 01:58:25.910
-28 JUICE w.r.t. 599 JUPITER   2031 JUN 15 01:58:25.910        2031 JUL 01 13:40:32.255
-28 JUICE w.r.t. 504 CALLISTO  2031 JUL 01 13:40:32.255        2031 JUL 01 17:54:40.776
-28 JUICE w.r.t. 599 JUPITER   2031 JUL 01 17:54:40.776        2031 JUL 19 23:35:49.558
-28 JUICE w.r.t. 503 GANYMEDE  2031 JUL 19 23:35:49.558        2031 JUL 20 02:25:33.310
-28 JUICE w.r.t. 599 JUPITER   2031 JUL 20 02:25:33.310        2031 AUG 24 17:53:47.180
-28 JUICE w.r.t. 503 GANYMEDE  2031 AUG 24 17:53:47.180        2031 AUG 24 20:49:37.960
-28 JUICE w.r.t. 599 JUPITER   2031 AUG 24 20:49:37.960        2031 SEP 10 02:17:44.092
-28 JUICE w.r.t. 503 GANYMEDE  2031 SEP 10 02:17:44.092        2031 SEP 10 05:43:31.716
-28 JUICE w.r.t. 599 JUPITER   2031 SEP 10 05:43:31.716        2031 SEP 27 00:18:35.960
-28 JUICE w.r.t. 504 CALLISTO  2031 SEP 27 00:18:35.960        2031 SEP 27 08:59:29.180
-28 JUICE w.r.t. 599 JUPITER   2031 SEP 27 08:59:29.180        2031 NOV 25 05:57:26.213
-28 JUICE w.r.t. 504 CALLISTO  2031 NOV 25 05:57:26.213        2031 NOV 25 14:41:08.465
-28 JUICE w.r.t. 599 JUPITER   2031 NOV 25 14:41:08.465        2032 SEP 13 01:10:59.638
-28 JUICE w.r.t. 503 GANYMEDE  2032 SEP 13 01:10:59.638        2033 JUN 26 02:44:48.562

Check SPK against input data

In [24]:
spiceypy.furnsh(spk_file)

Set times based on input data

These times will be in ephemerides time (et) format.

In [25]:
et_times = trajectory_data['et'].tolist()
utc_times = trajectory_data.index.tolist()
centers = trajectory_data['c'].tolist()
sample_size = len(et_times)

states_x = trajectory_data['x']
states_y = trajectory_data['y']
states_z = trajectory_data['z']

states_vx = trajectory_data['vx']
states_vy = trajectory_data['vy']
states_vz = trajectory_data['vz']

Compare sample of input (mission analysis) and output (generated SPK) data

For the times defined above build a Pandas dataframe with spacecraft states using spice.spkezr.
Set the time column as the index and map it from et to datetime objects.

In [26]:
spice_trajectory = pd.DataFrame(columns=('utc','et','x', 'y', 'z', 'vx', 'vy', 'vz'))

for i, epoch in enumerate(et_times):
    try:
        x, y, z, vx, vy, vz = spiceypy.spkezr(spacecraft, float(epoch), frame, ltcorr, spiceypy.bodc2n(centers[i]))[0]
        spice_trajectory.loc[i] =  [ utc_times[i], float(epoch), x, y, z, vx, vy, vz]
    except:
         spice_trajectory.loc[i] =  [ utc_times[i], float(epoch), states_x[i], states_y[i], states_z[i],
                                      states_vx[i], states_vy[i], states_vz[i]]
In [27]:
spice_trajectory.set_index('utc', inplace=True)

spice_trajectory['c'] = trajectory_data['c'].tolist()
spice_trajectory['r'] = np.sqrt(np.square(spice_trajectory[['x','y','z']]).sum(axis=1))
spice_trajectory['vr'] = np.sqrt(np.square(spice_trajectory[['vx','vy','vz']]).sum(axis=1))

spice_trajectory = spice_trajectory[['et','c', 'x', 'y', 'z', 'r', 'vx', 'vy', 'vz', 'vr']]

Examine the heads of the SPICE-based dataframe and read-in Mission Analysis dataframe. The latter has had its time index converted to et.

In [28]:
spice_trajectory.head()
Out[28]:
et c x y z r vx vy vz vr
utc
2022-05-31 23:58:50.815090 7.073136e+08 399 1451.646737 -6393.821607 -971.572870 6628.136000 10.211802 2.938721 -4.081763 11.383223
2022-06-01 00:01:39.587156 7.073138e+08 399 3136.254239 -5778.113358 -1637.406651 6775.233219 9.693791 4.319356 -3.786825 11.267940
2022-06-01 00:04:34.008584 7.073139e+08 399 4759.143566 -4922.711502 -2263.779920 7211.604228 8.884950 5.425425 -3.386606 10.947450
2022-06-01 00:07:45.549424 7.073141e+08 399 6366.809128 -3801.929413 -2868.569942 7951.076609 7.902317 6.206307 -2.932728 10.467367
2022-06-01 00:11:27.293607 7.073144e+08 399 8000.103934 -2367.711749 -3466.020680 9034.435302 6.856253 6.665512 -2.470928 9.876373
In [29]:
trajectory_data.head()
Out[29]:
et c x y z r vx vy vz vr
utc
2022-05-31 23:58:50.815090 7.073136e+08 399 1451.646737 -6393.821607 -971.572870 6628.136000 10.211802 2.938721 -4.081763 11.383223
2022-06-01 00:01:39.587156 7.073138e+08 399 3136.254922 -5778.113053 -1637.406918 6775.233339 9.693791 4.319357 -3.786825 11.267940
2022-06-01 00:04:34.008584 7.073139e+08 399 4759.147017 -4922.709396 -2263.781235 7211.605481 8.884948 5.425427 -3.386605 10.947450
2022-06-01 00:07:45.549424 7.073141e+08 399 6366.806711 -3801.931310 -2868.569045 7951.075257 7.902319 6.206306 -2.932729 10.467367
2022-06-01 00:11:27.293607 7.073144e+08 399 8000.105522 -2367.710204 -3466.021252 9034.436523 6.856252 6.665512 -2.470928 9.876372

Do an element-by-element comparison of the 2 dataframes if they have the same shape.

In [30]:
spice_sample = spice_trajectory.head(sample_size)
data_sample = trajectory_data.head(sample_size)
if spice_sample.shape==data_sample.shape:
    cols = spice_sample.columns.tolist()
    diff_df = pd.DataFrame(columns=cols)
    for col in spice_sample.columns.tolist():
        diff_df[col] = spice_sample[col].values - data_sample[col].values
    diff_df.index = spice_sample.index
else:
    print('The input and spice-sampled data were not of the same shape.!')
In [31]:
diff_df.shape
Out[31]:
(50064, 10)

Plot the differences.

In [32]:
title = 'State Vector Components Max Differences (Sample)'
ax = diff_df[['x','y','z']].plot(title=title)
ax.set_ylabel("km");
plt.savefig('output/{}.png'.format('_'.join(title.split())))
In [33]:
title='State Vector Magnitude Max Differences (Sample)'
ax = diff_df[['r']].plot(title=title)
ax.set_ylabel("km");
plt.savefig('output/{}.png'.format('_'.join(title.split())))
In [34]:
title='Velocity Vector Components Max. Differences (Sample)'
ax = diff_df[['vx','vy','vz']].plot(title=title)
ax.set_ylabel("km/s");
plt.savefig('output/{}.png'.format('_'.join(title.split())))

Examine input (OEM) and output (SPK) data for maximum difference

Note: this may take some time to complete. It is done row-by-row to avoid having to load large (500,000+ rows) dataframes into memory.

In [35]:
def et_to_datetime(et, scale='TDB'):
    """
    convert a SPICE ephemerides epoch (TBD seconds) to a python datetime
    object. The default time scale returned will be TDB but can be set
    to any of the accepted SPICE time scales.

    Args:
        et (float): SPICE ephemerides sceonds (TBD)
        scale (str, optional): time scale of output time (default: TDB)

    Returns:
        datetime: python datetime
    """
    t = spiceypy.timout(et, 'YYYY-MON-DD HR:MN:SC.### ::{}'.format(scale), 41)
    return dt.strptime(t, '%Y-%b-%d %H:%M:%S.%f')


max_diff = [0, 0, 0, 0, 0, 0]
max_diff_times = [0, 0, 0, 0, 0, 0]
max_diff_et = [0, 0, 0, 0, 0, 0]
max_diff_i = [0, 0, 0, 0, 0, 0]

for i, epoch in enumerate(et_times):
    try:
        state0 = spiceypy.spkezr(spacecraft, epoch, frame, ltcorr, spiceypy.bodc2n(centers[i]))[0]
    except:
        state0 = np.array((states_x[i], states_y[i], states_z[i], states_vx[i], states_vy[i], states_vz[i]))
        
    et, c, x, y, z, r, vx, vy, vz, vr = trajectory_data.values[i]
    state1 = [x, y, z, vx, vy, vz]
    gt = abs(state1-state0) > max_diff
    for j, bool in enumerate(gt):
        if bool:
            max_diff[j] = abs(state1-state0)[j]
            max_diff_times[j] = et_to_datetime(epoch)
            max_diff_et[j] = epoch
            max_diff_i[j] = i          
        else:
            pass
        
for i, val in enumerate(max_diff[:3]):
    max_diff[i]=val.round(xyz_signifig)
for i, val in enumerate(max_diff[3:]):
    max_diff[i+3]=val.round(vxvyvz_signifig)   
    
orbit_diff = pd.DataFrame.from_records(
    [max_diff_times, max_diff_et, max_diff, max_diff_i], 
    columns=('x [km]', 'y [km]', 'z [km]', 'vx [km/s]', 'vy [km/s]', 'vz [km/s]'), 
    index=['time', 'et', 'max diff', 'index'])
orbit_diff
Out[35]:
x [km] y [km] z [km] vx [km/s] vy [km/s] vz [km/s]
time 2025-02-10 02:09:59.498000 2025-02-10 02:09:59.498000 2025-02-10 02:09:59.498000 2022-06-01 00:05:43.193000 2022-06-01 00:05:43.193000 2022-06-01 00:05:43.193000
et 7.92425e+08 7.92425e+08 7.92425e+08 7.07314e+08 7.07314e+08 7.07314e+08
max diff 0.385713 0.551326 0.131654 2e-06 2e-06 1e-06
index 478 478 478 2 2 2