Source code for test_relativeODuKF

```
#
# Copyright (c) 2016-2018, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import numpy as np
from Basilisk.architecture import messaging
from Basilisk.fswAlgorithms import relativeODuKF  # import the module that is to be tested
from Basilisk.utilities import SimulationBaseClass, macros, orbitalMotion

import relativeODuKF_test_utilities as FilterPlots

return np.transpose(np.vstack([[time], np.transpose(data)]))

def rk4(f, t, x0):
x = np.zeros([len(t),len(x0)+1])
h = (t[len(t)-1] - t[0])/len(t)
x[0,0] = t[0]
x[0,1:] = x0
for i in range(len(t)-1):
h = t[i+1] - t[i]
x[i,0] = t[i]
k1 = h * f(t[i], x[i,1:])
k2 = h * f(t[i] + 0.5 * h, x[i,1:] + 0.5 * k1)
k3 = h * f(t[i] + 0.5 * h, x[i,1:] + 0.5 * k2)
k4 = h * f(t[i] + h, x[i,1:] + k3)
x[i+1,1:] = x[i,1:] + (k1 + 2.*k2 + 2.*k3 + k4) / 6.
x[i+1,0] = t[i+1]
return x

def twoBodyGrav(t, x, mu = 42828.314*1E9):
dxdt = np.zeros(np.shape(x))
dxdt[0:3] = x[3:]
dxdt[3:] = -mu/np.linalg.norm(x[0:3])**3.*x[0:3]
return dxdt

def setupFilterData(filterObject):

filterObject.planetIdInit = 2
filterObject.alpha = 0.02
filterObject.beta = 2.0
filterObject.kappa = 0.0

mu = 42828.314*1E9 #m^3/s^2
elementsInit = orbitalMotion.ClassicElements()
elementsInit.a = 4000*1E3 #m
elementsInit.e = 0.2
elementsInit.i = 10
elementsInit.Omega = 0.001
elementsInit.omega = 0.01
elementsInit.f = 0.1
r, v = orbitalMotion.elem2rv(mu, elementsInit)

filterObject.stateInit = r.tolist() + v.tolist()
filterObject.covarInit = [1000.*1E6, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 1000.*1E6, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 1000.*1E6, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 5*1E6, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 5*1E6, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 5*1E6]

qNoiseIn = np.identity(6)
qNoiseIn[0:3, 0:3] = qNoiseIn[0:3, 0:3]*0.00001*0.00001*1E-6
qNoiseIn[3:6, 3:6] = qNoiseIn[3:6, 3:6]*0.0001*0.0001*1E-6
filterObject.qNoise = qNoiseIn.reshape(36).tolist()
filterObject.noiseSF = 1

# uncomment this line is this test is to be skipped in the global unit test run, adjust message as needed
# @pytest.mark.skipif(conditionstring)
# uncomment this line if this test has an expected failure, adjust message as needed
# @pytest.mark.xfail() # need to update how the RW states are defined
# provide a unique test method name, starting with test_

[docs]def test_methods_kf(show_plots):
"""Module Unit Test"""
[testResults, testMessage] = relOD_method_test(show_plots)
assert testResults < 1, testMessage
[docs]def test_propagation_kf(show_plots):
"""Module Unit Test"""
[testResults, testMessage] = StatePropRelOD(show_plots, 10.0)
assert testResults < 1, testMessage
[docs]def test_measurements_kf(show_plots):
"""Module Unit Test"""
[testResults, testMessage] = StateUpdateRelOD(show_plots)
assert testResults < 1, testMessage

def relOD_method_test(show_plots):
# The __tracebackhide__ setting influences pytest showing of tracebacks:
# the mrp_steering_tracking() function will not be shown unless the
# --fulltrace command line option is specified.
__tracebackhide__ = True

testFailCount = 0  # zero unit test result counter
testMessages = []  # create empty list to store test log messages

state = [250, 32000, 1000, 5, 3, 2]
dt = 10
mu = 42828.314
# Measurement Model Test
data = relativeODuKF.RelODuKFConfig()
msg.r_BN_N = [300, 200, 100]
data.planetId = 2
data.opNavInBuffer = msg
data.countHalfSPs = 6
data.noiseSF = 1

Covar = np.eye(6)
SPexp = np.zeros([6, 2*6+1])
SPexp[:,0] = np.array(state)
for i in range(1, 6+1):
SPexp[:,i] = np.array(state) + Covar[:,i-1]
SPexp[:, i+6] = np.array(state) - Covar[:,i-1]

data.SP =  np.transpose(SPexp).flatten().tolist()
relativeODuKF.relODuKFMeasModel(data)

measurements = data.yMeas

if np.linalg.norm(np.array(measurements) - np.transpose(SPexp[0:3,:]).flatten()) > 1.0E-15:
testFailCount += 1
testMessages.append("Measurement Model Failure")

# Dynamics Model Test
data.planetId = 2

stateIn = relativeODuKF.new_doubleArray(6)
for i in range(len(state)):
relativeODuKF.doubleArray_setitem(stateIn, i, state[i])

relativeODuKF.relODStateProp(data, stateIn, dt)

propedState = []
for i in range(6):
propedState.append(relativeODuKF.doubleArray_getitem(stateIn, i))
expected = rk4(twoBodyGrav, [0, dt], np.array(state)*1E3)
expected[:,1:]*=1E-3
if np.linalg.norm((np.array(propedState) - expected[-1,1:])/(expected[-1,1:])) > 1.0E-15:
testFailCount += 1
testMessages.append("State Prop Failure")

if testFailCount:
print(testMessages)
else:
print("Passed")
return [testFailCount, ''.join(testMessages)]

def StateUpdateRelOD(show_plots):
# The __tracebackhide__ setting influences pytest showing of tracebacks:
# the mrp_steering_tracking() function will not be shown unless the
# --fulltrace command line option is specified.
__tracebackhide__ = True

testFailCount = 0  # zero unit test result counter
testMessages = []  # create empty list to store test log messages

unitProcessName = "TestProcess"  # arbitrary name (don't change)

#   Create a sim module as an empty container
unitTestSim = SimulationBaseClass.SimBaseClass()

dt = 1.0
t1 = 250
multT1 = 8

testProcessRate = macros.sec2nano(dt)  # update process rate update time
testProc = unitTestSim.CreateNewProcess(unitProcessName)

# Construct algorithm
module = relativeODuKF.relativeODuKF()
module.ModelTag = "relodSuKF"

# Add test module to runtime call list

setupFilterData(module)
module.noiseSF = 1

dataLog = module.filtDataOutMsg.recorder()

time = np.linspace(0, int(multT1*t1), int(multT1*t1//dt)+1)
dydt = np.zeros(6)
energy = np.zeros(len(time))
expected=np.zeros([len(time), 7])
expected[0,1:] = module.stateInit
mu = 42828.314*1E9
energy[0] = -mu/(2*orbitalMotion.rv2elem(mu, expected[0,1:4], expected[0,4:]).a)

kick = np.array([0., 0., 0., -0.01, 0.01, 0.02]) * 10 *1E3

expected[0:t1,:] = rk4(twoBodyGrav, time[0:t1], module.stateInit)
expected[t1:multT1*t1+1, :] = rk4(twoBodyGrav, time[t1:len(time)], expected[t1-1, 1:] + kick)
for i in range(1, len(time)):
energy[i] = - mu / (2 * orbitalMotion.rv2elem(mu, expected[i, 1:4], expected[i, 4:]).a)

opnavInMsg = messaging.OpNavMsg()
module.opNavInMsg.subscribeTo(opnavInMsg)

inputData.planetID = 2
inputData.r_BN_B = expected[0, 1:4]

opnavInMsg.write(inputData, 0)

unitTestSim.InitializeSimulation()
for i in range(t1):
if i > 0 and i % 50 == 0:
inputData.timeTag = macros.sec2nano(i * dt)
inputData.r_BN_N = expected[i,1:4] + np.random.normal(0, 5*1E-2, 3)
inputData.valid = 1
inputData.covar_N = [5.*1E-2, 0., 0.,
0., 5.*1E-2, 0.,
0., 0., 5.*1E-2]
opnavInMsg.write(inputData, unitTestSim.TotalSim.CurrentNanos)
unitTestSim.ConfigureStopTime(macros.sec2nano((i + 1) * dt))
unitTestSim.ExecuteSimulation()

for i in range(6):
if (covarLog[t1, i * 6 + 1 + i] > covarLog[0, i * 6 + 1 + i] / 100):
testFailCount += 1
testMessages.append("Covariance update failure at " + str(t1))

for i in range(t1, multT1*t1):
if i % 50 == 0:
inputData.timeTag = macros.sec2nano(i * dt + 1)
inputData.r_BN_N = expected[i,1:4] +  np.random.normal(0, 5*1E-2, 3)
inputData.valid = 1
inputData.covar_N = [5.*1E-2, 0.,0.,
0., 5.*1E-2, 0.,
0., 0., 5.*1E-2]
opnavInMsg.write(inputData, unitTestSim.TotalSim.CurrentNanos)
unitTestSim.ConfigureStopTime(macros.sec2nano((i + 1)*dt))
unitTestSim.ExecuteSimulation()

diff = np.copy(stateLog)
diff[:,1:]-=expected[:,1:]
FilterPlots.EnergyPlot(time, energy, 'Update', show_plots)
FilterPlots.StateCovarPlot(stateLog, covarLog, 'Update', show_plots)
FilterPlots.StatePlot(diff, 'Update', show_plots)
FilterPlots.plot_TwoOrbits(expected[:,0:4], stateLog[:,0:4])
FilterPlots.PostFitResiduals(postFitLog, np.sqrt(5*1E-2*1E6), 'Update', show_plots)

for i in range(6):
if (covarLog[t1*multT1, i * 6 + 1 + i] > covarLog[0, i * 6 + 1 + i] / 100):
testFailCount += 1
testMessages.append("Covariance update failure at " + str(t1*multT1))

if (np.linalg.norm(diff[-1, 1:]/expected[-1,1:]) > 1.0E-1):
testFailCount += 1
testMessages.append("State propagation failure")

# print out success message if no error were found
if testFailCount == 0:
print("PASSED: " + module.ModelTag + " state update")
else:
print(testMessages)

# return fail count and join into a single string all messages in the list
# testMessage
return [testFailCount, ''.join(testMessages)]

def StatePropRelOD(show_plots, dt):
# The __tracebackhide__ setting influences pytest showing of tracebacks:
# the mrp_steering_tracking() function will not be shown unless the
# --fulltrace command line option is specified.
__tracebackhide__ = True

testFailCount = 0  # zero unit test result counter
testMessages = []  # create empty list to store test log messages

unitProcessName = "TestProcess"  # arbitrary name (don't change)

#   Create a sim module as an empty container
unitTestSim = SimulationBaseClass.SimBaseClass()

testProcessRate = macros.sec2nano(dt)  # update process rate update time
testProc = unitTestSim.CreateNewProcess(unitProcessName)

# Construct algorithm and associated C++ container
module = relativeODuKF.relativeODuKF()
module.ModelTag = "relodSuKF"

# Add test module to runtime call list

setupFilterData(module)
module.noiseSF = 1

dataLog = module.filtDataOutMsg.recorder()

opnavInMsg = messaging.OpNavMsg()
module.opNavInMsg.subscribeTo(opnavInMsg)

opnavInMsg.write(inputData, 0)

timeSim = 60
unitTestSim.InitializeSimulation()
unitTestSim.ConfigureStopTime(macros.min2nano(timeSim))
unitTestSim.ExecuteSimulation()

time = np.linspace(0, int(timeSim*60), int(timeSim*60//dt)+1)
dydt = np.zeros(6)
energy = np.zeros(len(time))
expected=np.zeros([len(time), 7])
expected[0,1:] = module.stateInit
mu = 42828.314*1E9
energy[0] = -mu/(2*orbitalMotion.rv2elem(mu, expected[0,1:4], expected[0,4:]).a)
expected = rk4(twoBodyGrav, time, module.stateInit)
for i in range(1, len(time)):
energy[i] = - mu / (2 * orbitalMotion.rv2elem(mu, expected[i, 1:4], expected[i, 4:]).a)

diff = np.copy(stateLog)
diff[:,1:]-=expected[:,1:]
FilterPlots.plot_TwoOrbits(expected[:,0:4], stateLog[:,0:4])
FilterPlots.EnergyPlot(time, energy, 'Prop', show_plots)
FilterPlots.StateCovarPlot(stateLog, covarLog, 'Prop', show_plots)
FilterPlots.StatePlot(diff, 'Prop', show_plots)

if (np.linalg.norm(diff[-1,1:]/expected[-1,1:]) > 1.0E-10):
testFailCount += 1
testMessages.append("State propagation failure")

if (energy[0] - energy[-1])/energy[0] > 1.0E-10:
testFailCount += 1
testMessages.append("State propagation failure")

# print out success message if no error were found
if testFailCount == 0:
print("PASSED: " + module.ModelTag + " state propagation")

# return fail count and join into a single string all messages in the list
# testMessage
return [testFailCount, ''.join(testMessages)]

if __name__ == "__main__":
# relOD_method_test(True)
# StatePropRelOD(True, 1.0)
StateUpdateRelOD(False)
```