''' '''
'''
 ISC License
 Copyright (c) 2016, 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.
'''
#
#   IMU Unit Test
#   Purpose:  Test IMU functions
#   Author:  Scott Carnahan
#   Creation Date:  September 14, 2017
#
# import pytest
import os, inspect
import numpy as np
import pytest
import matplotlib.pyplot as plt
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import unitTestSupport
from Basilisk.utilities import macros
from Basilisk.simulation import imu_sensor
from Basilisk.utilities import RigidBodyKinematics as rbk
# methods
def v3vTmult(v1,v2):
    output = [[0,0,0],[0,0,0],[0,0,0]]
    for i in range(0,3):
        for j in range(0,3):
            output[i][j] = v1[i] * v2[j]
    return output
def skew(vector):
    vector = np.array(vector)
    skew_symmetric = np.array([[0, -vector.item(2), vector.item(1)],
                     [vector.item(2), 0, -vector.item(0)],
                     [-vector.item(1), vector.item(0), 0]])
    return skew_symmetric
def findSigmaDot(sigma, omega):
    sigmaMag = np.linalg.norm(sigma)
    B1 = 1 - sigmaMag ** 2
    BI = np.identity(3)
    sigmaTilde = skew(sigma)
    B2 = np.dot(2, sigmaTilde)
    B3 = np.dot(2, v3vTmult(sigma, sigma))
    B = np.dot(B1, BI) + B2 + B3
    sigmaDot = np.dot(0.25, np.dot(B, omega))
    return sigmaDot
def setRandomWalk(self,senRotNoiseStd = 0.0,senTransNoiseStd = 0.0,errorBoundsGyro = [1e6] * 3,errorBoundsAccel = [1e6] * 3):
    # sets the random walk for IRU module
    self.PMatrixAccel = np.eye(3) * senRotNoiseStd
    self.walkBoundsAccel = np.array(errorBoundsAccel)
    self.PMatrixGyro = np.eye(3) * senTransNoiseStd
    self.walkBoundsGyro = np.array(errorBoundsGyro)
# 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
#The following tests are parameterized and run:
# noise - clean plus some noise - test std dev
# error bounds
# The following 'parametrize' function decorator provides the parameters and expected results for each
#   of the multiple test runs for this test.
[docs]@pytest.mark.parametrize("show_plots,   testCase,       stopTime,       procRate,   gyroLSBIn,      accelLSBIn,     senRotMaxIn,    senTransMaxIn,  senRotNoiseStd,     senTransNoiseStd,   errorBoundsGyroIn,  errorBoundsAccelIn, senRotBiasIn,   senTransBiasIn, accuracy", [
                        (False,         'clean',        1.0,            0.01,       0.0,            0.0,            1000.,          1000.,          0.0,                0.0,                0.0,                0.0,                0.,             0.,             1e-8),
                        # (False,         'noise',        1.0,            0.001,      0.0,            0.0,            1000.,          1000.,          .1,                 .1,                 0.1,                0.1,                0.0,            0.0,            1e-1),
                        # (False,         'bias',         1.0,            0.01,       0.0,            0.0,            1000.,          1000.,          0.0,                0.0,                0.0,                0.0,                10.,            10.,            1e-8),
                        # (False,         'saturation',   1.0,            0.01,       0.0,            0.0,            1.0,            5.0,            0.0,                0.0,                0.0,                0.0,                0.0,            0.0,            1e-8),
                        # (False,        'discretization',1.,             0.01,       0.05,           0.5,            100.,           1000.,          0.0,                0.0,                1e6,                1e6,                0.0,            0.0,            1e-8),
])
# provide a unique test method name, starting with test_
def test_unitSimIMU(show_plots,   testCase,       stopTime,       procRate, gyroLSBIn,    accelLSBIn,   senRotMaxIn,    senTransMaxIn,  senRotNoiseStd,     senTransNoiseStd,   errorBoundsGyroIn,  errorBoundsAccelIn, senRotBiasIn,   senTransBiasIn, accuracy):
    """Module Unit Test"""
    # each test method requires a single assert method to be called
    [testResults, testMessage] = unitSimIMU(show_plots,   testCase,       stopTime,       procRate, gyroLSBIn,    accelLSBIn,   senRotMaxIn,    senTransMaxIn,  senRotNoiseStd,     senTransNoiseStd,   errorBoundsGyroIn,  errorBoundsAccelIn, senRotBiasIn,   senTransBiasIn, accuracy)
    assert testResults < 1, testMessage 
def unitSimIMU(show_plots,   testCase,       stopTime,       procRate, gyroLSBIn,    accelLSBIn,   senRotMaxIn,    senTransMaxIn,  senRotNoiseStd,     senTransNoiseStd,   errorBoundsGyroIn,  errorBoundsAccelIn, senRotBiasIn,   senTransBiasIn, accuracy):
    testFailCount = 0  # zero unit test result counter
    testMessages = []  # create empty array to store test log messages
    unitTaskName = "unitTask"  # arbitrary name (don't change)
    unitProcName = "TestProcess"  # arbitrary name (don't change)
    # initialize SimulationBaseClass
    unitSim = SimulationBaseClass.SimBaseClass()
    # create the task and specify the integration update time
    unitProcRate_s = procRate
    unitProcRate = macros.sec2nano(unitProcRate_s)
    unitProc = unitSim.CreateNewProcess(unitProcName)
    unitTask = unitSim.CreateNewTask(unitTaskName, unitProcRate)
    unitProc.addTask(unitTask)
    # Set-up the fake kinematics vectors
    # Note: No conservative accelerations are used in this test
    # center of mass
    rDotDot_CN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #acceleration of center of mass wrt inertial frame
    rDotDot_CB_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #acceleration of center of mass wrt body frame
    rDot_CN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #velocity of center of mass wrt inertial frame
    r_CN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #position of center of mass wrt to inertial frame
    # body frame
    rDotDot_BN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) # acceleration of body frame wrt to inertial
    rDot_BN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #velocity of body frame wrt to inertial
    r_BN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #position of body frame wrt to inertial
    omegaDot_BN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #angular acceleration of body frame wrt to inertial
    omega_BN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) # angular rate of body frame wrt to inertial
    sigmaDot_BN = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #MRP derivative, body wrt to inertial
    sigma_BN = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) # MRP body wrt to inertial
    # sensor
    rDotDot_SN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #sensor sensed acceleration
    rDot_SN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #sensor accumulated DV
    r_SN_N = np.resize(np.array([0., 0., 0.]),(int(stopTime/unitProcRate_s+1),3)) #sensor position in body frame coordinates
    # Set initial conditions for fake kinematics vectors
    # Acceleration vectors
    dataRows= np.shape(rDotDot_CN_N)[0]
    for i in range(0, dataRows): #loops through acceleration vectors so that each element can be set individually (or, hopefully, as a function)
        rDotDot_BN_N[i][0] = 1.
        rDotDot_BN_N[i][1] = 1.
        rDotDot_BN_N[i][2] = 1.
        rDotDot_CB_N[i][0] = 0.05
        rDotDot_CB_N[i][1] = 0.07
        rDotDot_CB_N[i][2] = 0.06
        rDotDot_CN_N[i][0] = rDotDot_BN_N[i][0] + rDotDot_CB_N[i][0]
        rDotDot_CN_N[i][1] = rDotDot_BN_N[i][1] + rDotDot_CB_N[i][1]
        rDotDot_CN_N[i][2] = rDotDot_BN_N[i][2] + rDotDot_CB_N[i][2]
        omegaDot_BN_N[i][0] = 1.
        omegaDot_BN_N[i][1] = 1.5
        omegaDot_BN_N[i][2] = 1.25
    # Center of Mass
    rDot_CN_N[0][:] = [0.05, 0.07, 0.08]
    r_CN_N[0][:] = [10000.5, 10000.7, 10000.5] # Some arbitrary location in space
    # Body Frame Origin
    rDot_BN_N[0][:] = [0.1, 0.2, -0.1]
    r_BN_N[0][:] = [10000., 10000., 10000.] # leaves r_CN_N[0][i] with some offset
    # Body Rotation
    omega_BN_N[0][:] = [0.0, 0.15, 0.1] #omega_BN_N
    sigma_BN[0][:] = [0.25, 0.1, 0.03] # note that unlabeled sigma is sigma_BN
    # Sensor linear states (note that these initial conditions must be solved as functions of another initial conditions to maintain consistency
    r_SB_B = np.array([1.0, 1.0, 2.0]) #constant. sensor position wrt to body frame origin
    cDotDot_N = rDotDot_CN_N[0][:] - rDotDot_BN_N[0][:]
    cDot_N = rDot_CN_N[0][:] - rDot_BN_N[0][:]
    c_N =  r_CN_N[0][:] - r_BN_N[0][:]
    cPrime_N = cDot_N - np.cross(omega_BN_N[0][:], c_N)
    cPrimePrime_N = cDotDot_N - np.dot(2, np.cross(omega_BN_N[0][:], cPrime_N)) - np.cross(omegaDot_BN_N[0][:], c_N) - np.cross(omega_BN_N[0][:], np.cross(omega_BN_N[0][:], c_N))
    dcm_BN = rbk.MRP2C(sigma_BN[0][:])
    dcm_NB = np.transpose(dcm_BN)
    sigmaDot_BN[0][:] = findSigmaDot(sigma_BN[0][:], np.dot(dcm_BN, omega_BN_N[0][:])) # sigmaDot_BN
    r_SB_N = np.dot(dcm_NB, r_SB_B)
    r_SC_N = r_BN_N[0][:] + r_SB_N - r_CN_N[0][:]
    rDotDot_SN_N[0][:] = rDotDot_CN_N[0][:] - cPrimePrime_N - np.dot(2, np.cross(omega_BN_N[0][:], cPrime_N)) + np.cross(omegaDot_BN_N[0][:], r_SC_N) + np.cross(omega_BN_N[0][:], np.cross(omega_BN_N[0][:], r_SC_N))
    rDot_SN_N[0][:] = rDot_CN_N[0][:] - cPrime_N + np.cross(omega_BN_N[0][:], r_SC_N)
    r_SN_N[0][:] = r_SB_N + r_BN_N[0][:]
    #Sensor Setup
    ImuSensor = imu_sensor.ImuSensor()
    ImuSensor.ModelTag = "imusensor"
    ImuSensor.sensorPos_B = np.array(r_SB_B) #must be set by user - no default. check if this works by giving an array - SJKC
    yaw = 0.7854 #should be given as parameter [rad]
    pitch = 1.0  # [rad]
    roll = 0.1 # [rad]
    dcm_PB = rbk.euler3212C([yaw,pitch,roll]) #done separately as a
    dcm_PN = np.dot(dcm_PB, dcm_BN)
    ImuSensor.setBodyToPlatformDCM(yaw, pitch, roll) # done separately as a check
    errorBoundsGyro = [errorBoundsGyroIn] * 3
    errorBoundsAccel = [errorBoundsAccelIn] * 3
    setRandomWalk(ImuSensor, senRotNoiseStd, senTransNoiseStd, errorBoundsGyro, errorBoundsAccel)
    ImuSensor.setLSBs(accelLSBIn, gyroLSBIn)
    ImuSensor.senRotBias = np.array([senRotBiasIn] * 3)
    ImuSensor.senTransBias = np.array([senTransBiasIn] * 3)
    ImuSensor.senTransMax = senTransMaxIn
    ImuSensor.senRotMax = senRotMaxIn
    accelScale = [2.,2.,2.]
    gyroScale = [1.,1.,1.]
    ImuSensor.accelScale = np.array(accelScale)
    ImuSensor.gyroScale = np.array(gyroScale)
    accel_SN_P_disc = np.array([0., 0., 0.])
    omega_SN_P_disc = np.array([0., 0., 0.])
    # Set-up the sensor output truth vectors
    rDotDot_SN_P = np.resize(np.array([0., 0., 0.]), (int(stopTime/unitProcRate_s+1), 3))  # sensor sensed acceleration in sensor platform frame coordinates
    rDotDot_SN_P[0][:] = np.dot(dcm_PN, rDotDot_SN_N[0][:])
    DVAccum_SN_P = np.resize(np.array([0., 0., 0.]), (int(stopTime/unitProcRate_s+1), 3))  # sensor accumulated delta V ouput in the platform frame
    stepPRV_PN = np.resize(np.array([0., 0., 0.]), (int(stopTime/unitProcRate_s+1), 3))  # principal rotatation vector from time i-1 to time i in platform frame coordinates
    omega_PN_P = np.resize(np.array([0., 0., 0.]), (int(stopTime/unitProcRate_s+1), 3))  # angular rate omega_BN_P = omega_PN_P
    omega_PN_P[0][:] = np.dot(dcm_PN, omega_BN_N[0][:])
    # configure spacecraft dummy message - Need to convert to B frame here first
    StateCurrent = imu_sensor.SCPlusStatesSimMsg()
    StateCurrent.sigma_BN = sigma_BN[0][:]
    StateCurrent.omega_BN_B = np.dot(dcm_BN, omega_BN_N[0][:]) #1 rpm around each axis
    StateCurrent.nonConservativeAccelpntB_B = np.dot(dcm_BN, rDotDot_BN_N[0][:])
    StateCurrent.omegaDot_BN_B = np.dot(dcm_BN, omegaDot_BN_N[0][:])
    StateCurrent.TotalAccumDV_BN_B = np.array([0., 0., 0.])
    # add module to the task
    unitSim.AddModelToTask(unitTaskName, ImuSensor)
    # configure inertial_state_output message
    unitSim.TotalSim.CreateNewMessage(unitProcName, "inertial_state_output", 8*3*11, 2)
    unitSim.TotalSim.WriteMessageData("inertial_state_output", 8*3*11, 0, StateCurrent)
    # log module output message
    unitSim.TotalSim.logThisMessage(ImuSensor.OutputDataMsg, unitProcRate)
    unitSim.InitializeSimulation()
    # loop through ExecuteSimulation() and propagate sigma, omega, DV
    dt = unitProcRate_s
    for i in range(1,int(stopTime/dt)+1):
        # Step through the sim
        unitSim.ConfigureStopTime(macros.sec2nano(unitProcRate_s*i))
        unitSim.ExecuteSimulation()
        # attitude kinematics
        omega_BN_N[i][:] = omega_BN_N[i-1][:] + ((omegaDot_BN_N[i-1][:] + omegaDot_BN_N[i][:])/2)*dt
        # iterate on sigma/sigmaDot
        sigmaDot_BN[i][:] = sigmaDot_BN[i-1][:]
        for j in range(0,10): #Seems to converge after a few iterations
            sigma_BN[i][:] = sigma_BN[i-1][:] + ((sigmaDot_BN[i-1][:]+sigmaDot_BN[i][:])/2)*dt
            dcm_BN_2 = rbk.MRP2C(sigma_BN[i][:])
            sigmaDot_BN[i][:] = findSigmaDot(sigma_BN[i][:],np.dot(dcm_BN_2, omega_BN_N[i][:]))
        sigma_BN[i][:] = sigma_BN[i-1][:] + ((sigmaDot_BN[i-1][:]+sigmaDot_BN[i][:])/2)*dt
        dcm_BN_2 = rbk.MRP2C(sigma_BN[i][:])
        dcm_NB = np.transpose(dcm_BN_2)
        r_SB_N = np.dot(dcm_NB, r_SB_B)
        # linear kinematcs
        rDot_CN_N[i][:] = rDot_CN_N[i-1][:] + ((rDotDot_CN_N[i-1][:] + rDotDot_CN_N[i][:])/2)*dt
        r_CN_N[i][:] = r_CN_N[i-1][:] + ((rDot_CN_N[i-1][:] + rDot_CN_N[i][:])/2)*dt
        rDot_BN_N[i][:] = rDot_BN_N[i-1][:] + ((rDotDot_BN_N[i-1][:] + rDotDot_BN_N[i][:])/2)*dt
        r_BN_N[i][:] = r_BN_N[i-1][:] + ((rDot_BN_N[i-1][:] + rDot_BN_N[i][:])/2)*dt
        cDotDot_N = rDotDot_CN_N[i][:] - rDotDot_BN_N[i][:]
        cDot_N = rDot_CN_N[i][:] - rDot_BN_N[i][:]
        c_N = r_CN_N[i][:] - r_BN_N[i][:]
        # center of mass calculations
        cPrime_N = cDot_N - np.cross(omega_BN_N[i][:], c_N)
        cPrimePrime_N = cDotDot_N - np.dot(2,np.cross(omega_BN_N[i][:], cPrime_N)) - np.cross(omegaDot_BN_N[i][:],c_N)-np.cross(omega_BN_N[i][:],np.cross(omega_BN_N[i][:],c_N))
        r_SC_N = r_BN_N[i][:] + r_SB_N - r_CN_N[i][:]
        # solving for sensor inertial states
        rDotDot_SN_N[i][:] = rDotDot_CN_N[i][:] - cPrimePrime_N - np.dot(2,np.cross(omega_BN_N[i][:],cPrime_N)) + np.cross(omegaDot_BN_N[i][:],r_SC_N) +np.cross(omega_BN_N[i][:],np.cross(omega_BN_N[i][:],r_SC_N))
        rDot_SN_N[i][:] = rDot_CN_N[i][:] - cPrime_N + np.cross(omega_BN_N[i][:],r_SC_N)
        # Now create outputs which are (supposed to be) equivalent to the IMU output
        # linear acceleration (non-conservative) in platform frame
        dcm_BN_1 = rbk.MRP2C(sigma_BN[i-1][:])
        dcm_PN_2 = np.dot(dcm_PB, dcm_BN_2)
        dcm_PN_1 = np.dot(dcm_PB, dcm_BN_1)
        dcm_NP_1 = np.transpose(dcm_PN_1)
        dcm_PN_21 = np.dot(dcm_PN_2, dcm_NP_1)
        rDotDot_SN_P[i][:] = np.multiply(np.dot(dcm_PN_2, rDotDot_SN_N[i][:]) + senTransBiasIn, accelScale) #This should match trueValues.AccelPlatform
        # accumulated delta v (non-conservative) in platform frame
        DVAccum_SN_P[i][:] = np.multiply(np.dot(dcm_PN_2, rDot_SN_N[i][:] - rDot_SN_N[i-1][:]) + senTransBiasIn*dt, accelScale)
        # find PRV between before and now
        stepPRV_PN[i][:] = np.multiply(rbk.MRP2PRV(rbk.C2MRP(dcm_PN_21)) + senRotBiasIn*dt, gyroScale)
        # angular rate in platform frame
        omega_PN_P[i][:] = np.multiply(np.dot(dcm_PN_2, omega_BN_N[i][:]) + senRotBiasIn, gyroScale)
        #
        # #discretization
        if accelLSBIn > 0.0:
            for k in [0,1,2]:
                accel_SN_P_disc[k] = np.floor(np.abs(rDotDot_SN_P[i][k] / accelLSBIn)) * accelLSBIn * np.sign(rDotDot_SN_P[i][k])
            accelDiscError = rDotDot_SN_P[i][:] - accel_SN_P_disc
            rDotDot_SN_P[i][:] = accel_SN_P_disc
            DVAccum_SN_P[i][:] -= accelDiscError * dt
        if gyroLSBIn > 0.0:
            for k in [0,1,2]:
                omega_SN_P_disc[k] = np.floor(np.abs(omega_PN_P[i][k] / gyroLSBIn)) * gyroLSBIn * np.sign(omega_PN_P[i][k])
            omegaDiscError = omega_PN_P[i][:] - omega_SN_P_disc
            omega_PN_P[i][:] = omega_SN_P_disc
            stepPRV_PN[i][:] -= omegaDiscError * dt
        #saturation
        for k in [0,1,2]:
            if omega_PN_P[i][k] > senRotMaxIn:
                omega_PN_P[i][k] = senRotMaxIn
                stepPRV_PN[i][k] = senRotMaxIn*dt
            elif omega_PN_P[i][k] < -senRotMaxIn:
                omega_PN_P[i][k] = -senRotMaxIn
                stepPRV_PN[i][k] = -senRotMaxIn*dt
            if rDotDot_SN_P[i][k] > senTransMaxIn:
                rDotDot_SN_P[i][k] = senTransMaxIn
                DVAccum_SN_P[i][k] = rDotDot_SN_P[i][k] * dt
            elif rDotDot_SN_P[i][k] < -senTransMaxIn:
                rDotDot_SN_P[i][k] = -senTransMaxIn
                DVAccum_SN_P[i][k] = rDotDot_SN_P[i][k] * dt
        #Now update spacecraft states for the IMU:
        StateCurrent = imu_sensor.SCPlusStatesSimMsg()
        StateCurrent.sigma_BN = sigma_BN[i][:]
        StateCurrent.omega_BN_B = np.dot(dcm_BN_2, omega_BN_N[i][:])
        StateCurrent.nonConservativeAccelpntB_B = np.dot(dcm_BN_2, rDotDot_BN_N[i][:])
        StateCurrent.omegaDot_BN_B = np.dot(dcm_BN_2, omegaDot_BN_N[i][:])
        StateCurrent.TotalAccumDV_BN_B = np.dot(dcm_BN_2, rDot_BN_N[i][:] - rDot_BN_N[0][:])
        unitSim.TotalSim.WriteMessageData("inertial_state_output", 8 * 3 * 11, unitSim.TotalSim.CurrentNanos, StateCurrent)
    # Pull output time histories from messaging system
    DRout       = unitSim.pullMessageLogData(ImuSensor.OutputDataMsg + '.' + "DRFramePlatform", list(range(3)))
    omegaOut    = unitSim.pullMessageLogData(ImuSensor.OutputDataMsg + '.' + "AngVelPlatform", list(range(3)))
    rDotDotOut  = unitSim.pullMessageLogData(ImuSensor.OutputDataMsg + '.' + "AccelPlatform", list(range(3)))
    DVout       = unitSim.pullMessageLogData(ImuSensor.OutputDataMsg + '.' + "DVFramePlatform", list(range(3)))
    # truth/output comparison plots and AutoTex output
    plt.figure(1,figsize=(7, 5), dpi=80, facecolor='w', edgecolor='k')
    plt.clf()
    plt.plot(DRout[1:,0]/1e9, DRout[1:,1], linewidth = 6, color = 'black', label = "output1")
    plt.plot(DRout[1:,0]/1e9, stepPRV_PN[1:-1,0], linestyle = '--', color = 'cyan', label = "truth1")
    plt.plot(DRout[1:,0]/1e9, DRout[1:,2], linewidth = 4, color = 'black', label = "output2")
    plt.plot(DRout[1:,0]/1e9, stepPRV_PN[1:-1,1], linestyle = '--', color = 'cyan', label = "truth2")
    plt.plot(DRout[1:,0]/1e9, DRout[1:,3], linewidth = 2, color = 'black', label = "output3")
    plt.plot(DRout[1:,0]/1e9, stepPRV_PN[1:-1,2], linestyle = '--', color = 'cyan', label = "truth3")
    plt.xlabel("Time[s]")
    plt.ylabel("Time Step PRV Component Magnitude [rad]")
    plt.title("PRV Comparison")
    myLegend = plt.legend()
    myLegend.get_frame().set_facecolor('#909090')
    unitTestSupport.writeFigureLaTeX(testCase + "PRVcomparison",
                                     'Plot Comparing Time Step PRV Truth and Output for test: ' + testCase +'. Note that 1, 2, and 3 indicate the components of the principal rotation vector.', plt,
                                     'height=0.7\\textwidth, keepaspectratio', path)
    plt.figure(4,figsize=(7, 5), dpi=80, facecolor='w', edgecolor='k')
    plt.clf()
    plt.plot(DRout[1:,0]/1e9, omegaOut[1:,1], linewidth = 6, color = 'black', label = "output1")
    plt.plot(DRout[1:,0]/1e9, omega_PN_P[1:-1,0], linestyle = '--', color = 'cyan', label = "truth1")
    plt.plot(DRout[1:,0]/1e9, omegaOut[1:,2], linewidth = 4, color = 'black', label = "output2")
    plt.plot(DRout[1:,0]/1e9, omega_PN_P[1:-1,1], linestyle = '--', color = 'cyan', label = "truth2")
    plt.plot(DRout[1:,0]/1e9, omegaOut[1:,3], linewidth = 2, color = 'black', label = "output3")
    plt.plot(DRout[1:,0]/1e9, omega_PN_P[1:-1,2], linestyle = '--', color = 'cyan', label = "truth3")
    plt.xlabel("Time[s]")
    plt.ylabel("Angular Rate Component Magnitudes [rad/s]")
    plt.title("Angular Rate Comparison")
    myLegend = plt.legend()
    myLegend.get_frame().set_facecolor('#909090')
    unitTestSupport.writeFigureLaTeX(testCase + "omegaComparison",
                                     'Plot Comparing Angular Rate Truth and Output for test: ' + testCase +'. Note that 1, 2, and 3 indicate the components of the angular rate.', plt,
                                     'height=0.7\\textwidth, keepaspectratio', path)
    plt.figure(7,figsize=(7, 5), dpi=80, facecolor='w', edgecolor='k')
    plt.clf()
    plt.plot(DRout[1:,0]/1e9, rDotDotOut[1:,1], linewidth = 6, color = 'black', label = "output1")
    plt.plot(DRout[1:,0]/1e9, rDotDot_SN_P[1:-1,0], linestyle = '--', color = 'cyan', label = "truth1")
    plt.plot(DRout[1:,0]/1e9, rDotDotOut[1:,2], linewidth = 4, color = 'black', label = "output2")
    plt.plot(DRout[1:,0]/1e9, rDotDot_SN_P[1:-1,1], linestyle = '--', color = 'cyan', label = "truth2")
    plt.plot(DRout[1:,0]/1e9, rDotDotOut[1:,3], linewidth = 2, color = 'black', label = "output3")
    plt.plot(DRout[1:,0]/1e9, rDotDot_SN_P[1:-1,2], linestyle = '--', color = 'cyan', label = "truth3")
    plt.xlabel("Time[s]")
    plt.ylabel("Linear Acceleration Component Magnitudes [m/s/s]")
    plt.title("Acceleration Comparison")
    myLegend = plt.legend()
    myLegend.get_frame().set_facecolor('#909090')
    unitTestSupport.writeFigureLaTeX(testCase + "accelComparison",
                                     'Plot Comparing Sensor Linear Accelertaion Truth and Output for test: ' + testCase +'. Note that 1, 2, and 3 indicate the components of the acceleration.', plt,
                                     'height=0.7\\textwidth, keepaspectratio', path)
    plt.figure(10,figsize=(7, 5), dpi=80, facecolor='w', edgecolor='k')
    plt.clf()
    plt.plot(DRout[1:,0]/1e9, DVout[1:,1], linewidth = 6, color = 'black', label = "output1")
    plt.plot(DRout[1:,0]/1e9, DVAccum_SN_P[1:-1,0], linestyle = '--', color = 'cyan', label = "truth1")
    plt.plot(DRout[1:,0]/1e9, DVout[1:,2], linewidth = 4, color = 'black', label = "output2")
    plt.plot(DRout[1:,0]/1e9, DVAccum_SN_P[1:-1,1], linestyle = '--', color = 'cyan', label = "truth2")
    plt.plot(DRout[1:,0]/1e9, DVout[1:,3], linewidth = 2, color = 'black', label = "output3")
    plt.plot(DRout[1:,0]/1e9, DVAccum_SN_P[1:-1,2], linestyle = '--', color = 'cyan', label = "truth3")
    plt.xlabel("Time[s]")
    plt.ylabel("Step DV Magnitudes [m/s]")
    plt.title("DV Comparison")
    myLegend = plt.legend()
    myLegend.get_frame().set_facecolor('#909090')
    unitTestSupport.writeFigureLaTeX(testCase + "DVcomparison",
                                     'Plot Comparing Time Step DV Truth and Output for test: ' + testCase +'. Note that 1, 2, and 3 indicate the components of the velocity delta.', plt,
                                     'height=0.7\\textwidth, keepaspectratio', path)
    if show_plots and testCase != "noise":
        plt.show()
        plt.close('all')
    # test outputs
    if testCase != 'noise':
        for i in range(2,len(stepPRV_PN)-1):
            if not unitTestSupport.isArrayEqualRelative(DRout[i][:], stepPRV_PN[i][:], 3, accuracy):
                testMessages.append("FAILED DR @ i = "+ str(i) + ". \\\\& &")
                testFailCount += 1
            if not unitTestSupport.isArrayEqualRelative(omegaOut[i][:], omega_PN_P[i][:], 3, accuracy):
                testMessages.append("FAILED OMEGA @ i = "+ str(i) + ". \\\\& &")
                testFailCount += 1
            if not (testCase == "discretization" and (i == 572 or i == 934)):
                if not unitTestSupport.isArrayEqualRelative(DVout[i][:], DVAccum_SN_P[i][:], 3, accuracy):
                    testMessages.append("FAILED DV @ i = " + str(i) + ". \\\\& &")
                    testFailCount += 1
            if not unitTestSupport.isArrayEqualRelative(rDotDotOut[i][:], rDotDot_SN_P[i][:], 3, accuracy):
                testMessages.append("FAILED ACCEL @ i = " + str(i) + ". \\\\& &")
                testFailCount += 1
    else:
        DRoutNoise = np.zeros((np.shape(DRout)[0], np.shape(DRout)[1]-1))
        for i in range(2,len(stepPRV_PN)-1):
            for j in [0,1,2]:
                DRoutNoise[i][j] = DRout[i][j+1] - stepPRV_PN[i+1][j]
        rDotDotOutNoise = np.zeros((np.shape(DRout)[0], np.shape(DRout)[1] - 1))
        for i in range(2, len(stepPRV_PN) - 1):
            for j in [0, 1, 2]:
                rDotDotOutNoise[i, j] = rDotDotOut[i, j+1] - rDotDot_SN_P[i+1, j]
        DVoutNoise = np.zeros((np.shape(DRout)[0], np.shape(DRout)[1] - 1))
        for i in range(2, len(stepPRV_PN) - 1):
            for j in [0, 1, 2]:
                DVoutNoise[i, j] = DVout[i, j + 1] - DVAccum_SN_P[i + 1, j]
        omegaOutNoise = np.zeros((np.shape(DRout)[0], np.shape(DRout)[1] - 1))
        for i in range(2, len(stepPRV_PN)-1):
            for j in [0, 1, 2]:
                omegaOutNoise[i, j] = omegaOut[i, j + 1] - omega_PN_P[i + 1, j]
        if not unitTestSupport.isDoubleEqualRelative(np.std(DRoutNoise[:,0]),senRotNoiseStd*dt/1.5,accuracy):
            testMessages.append(("FAILED DRnoise1. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(DRoutNoise[:,1]),senRotNoiseStd*dt/1.5,accuracy):
            testMessages.append(("FAILED DRnoise2. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(DRoutNoise[:,2]),senRotNoiseStd*dt/1.5,accuracy):
            testMessages.append(("FAILED DRnoise3. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(DVoutNoise[:,0]),senTransNoiseStd*dt/1.5 * accelScale[0],accuracy):
            testMessages.append(("FAILED DVnoise1. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(DVoutNoise[:,1]),senTransNoiseStd*dt/1.5 * accelScale[1],accuracy):
            testMessages.append(("FAILED DVnoise2. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(DVoutNoise[:,2]),senTransNoiseStd*dt/1.5 * accelScale[2],accuracy):
            testMessages.append(("FAILED DVnoise3. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(rDotDotOutNoise[:,0]),senTransNoiseStd/1.5 * accelScale[0],accuracy):
            testMessages.append(("FAILED AccelNoise1. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(rDotDotOutNoise[:,1]),senTransNoiseStd/1.5 * accelScale[1],accuracy):
            testMessages.append(("FAILED AccelNoise2. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(rDotDotOutNoise[:,2]),senTransNoiseStd/1.5 * accelScale[2],accuracy):
            testMessages.append(("FAILED AccelNoise3. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(omegaOutNoise[:,0]),senRotNoiseStd/1.5,accuracy):
            testMessages.append(("FAILED omegaNoise1. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(omegaOutNoise[:,1]),senRotNoiseStd/1.5,accuracy):
            testMessages.append(("FAILED oemgaNoise2. \\\\& &"))
            testFailCount += 1
        if not unitTestSupport.isDoubleEqualRelative(np.std(omegaOutNoise[:,2]),senRotNoiseStd/1.5,accuracy):
            testMessages.append(("FAILED omegaNoise3. \\\\& &"))
            testFailCount += 1
        # noise plots
        plt.figure(1000, figsize=(7, 5), dpi=80, facecolor='w', edgecolor='k')
        plt.clf()
        plt.plot(DRout[1:, 0]/1e9, DVoutNoise[1:,:])
        plt.xlabel("Time[s]")
        plt.ylabel("DV Noise [um/s]")
        plt.title("DV Noise")
        unitTestSupport.writeFigureLaTeX("DVnoise",
                                         'Plot of DeltaV noise along each component for the noise test.',
                                         plt,
                                         'height=0.7\\textwidth, keepaspectratio', path)
        plt.figure(1001, figsize=(7, 5), dpi=80, facecolor='w', edgecolor='k')
        plt.clf()
        plt.plot(DRout[1:, 0]/1e9, rDotDotOutNoise[1:,:])
        plt.xlabel("Time[s]")
        plt.ylabel("Acceleration Noise [m/s/s]")
        plt.title("Acceleration Noise")
        unitTestSupport.writeFigureLaTeX("AccelNoise",
                                         'Plot of acceleration noise along each component for the noise test.',
                                         plt,
                                         'height=0.7\\textwidth, keepaspectratio', path)
        plt.figure(1002, figsize=(7, 5), dpi=80, facecolor='w', edgecolor='k')
        plt.clf()
        plt.plot(DRout[1:, 0]/1e9, DRoutNoise[1:,:])
        plt.xlabel("Time[s]")
        plt.ylabel("DR Noise [rad]")
        plt.title("DR Noise")
        unitTestSupport.writeFigureLaTeX("DRnoise",
                                         'Plot of PRV noise along each component for the noise test.',
                                         plt,
                                         'height=0.7\\textwidth, keepaspectratio', path)
        plt.figure(1003, figsize=(7, 5), dpi=80, facecolor='w', edgecolor='k')
        plt.clf()
        plt.plot(DRout[1:, 0]/1e9, omegaOutNoise[1:,:])
        plt.xlabel("Time[s]")
        plt.ylabel("Angular Rate Noise [rad/s]")
        plt.title("Angular Rate Noise")
        unitTestSupport.writeFigureLaTeX("omegaNoise",
                                         'Plot of Angular Rate noise along each component for the noise test.',
                                         plt,
                                         'height=0.7\\textwidth, keepaspectratio', path)
        if show_plots:
            plt.show()
            plt.close('all')
    #
    # Outputs to AutoTex
    #
    accuracySnippetName = testCase+"accuracy"
    accuracySnippetContent = '{:1.0e}'.format(accuracy)
    unitTestSupport.writeTeXSnippet(accuracySnippetName, accuracySnippetContent, path)
    if testFailCount == 0:
        colorText = 'ForestGreen'
        passedText = r'\textcolor{' + colorText + '}{' + "PASSED" + '}'
    else:
        colorText = 'Red'
        passedText = r'\textcolor{' + colorText + '}{' + "FAILED" + '}'
    passFailSnippetName = testCase+"passFail"
    passFailSnippetContent = passedText
    unitTestSupport.writeTeXSnippet(passFailSnippetName, passFailSnippetContent, path)
    snippetName = testCase + "gyroLSB"
    snippetContent = '{:1.0e}'.format(gyroLSBIn)
    unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path)
    snippetName = testCase + "accelLSB"
    snippetContent = '{:1.0e}'.format(accelLSBIn)
    unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path)
    snippetName = testCase + "rotMax"
    snippetContent = '{:1.1e}'.format(senRotMaxIn)
    unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path)
    snippetName = testCase + "transMax"
    snippetContent = '{:1.1e}'.format(senTransMaxIn)
    unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path)
    snippetName = testCase + "rotNoise"
    snippetContent = '{:0.1f}'.format(senRotNoiseStd)
    unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path)
    snippetName = testCase + "transNoise"
    snippetContent = '{:0.1f}'.format(senTransNoiseStd)
    unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path)
    snippetName = testCase + "rotBias"
    snippetContent = '{:1.1e}'.format(senRotBiasIn)
    unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path)
    snippetName = testCase + "transBias"
    snippetContent = '{:1.1e}'.format(senTransBiasIn)
    unitTestSupport.writeTeXSnippet(snippetName, snippetContent, path)
    return [testFailCount, ''.join(testMessages)]
# This statement below ensures that the unit test script can be run as a
# stand-along python script
if __name__ == "__main__":
    unitSimIMU(True,        'clean',        1.0,            0.01,       0.0,            0.0,            1000.,          1000.,          0.0,                0.0,                0.0,                0.0,                0.,             0.,             1e-8)