# ISC License
#
# Copyright (c) 2023, 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.
#
# Bore Angle Calculation Test
#
# Purpose: Test the proper function of the Bore Angle Calculation module using the inertial heading option.
#
# Author: João Vaz Carneiro
# Creation Date: Jan. 12, 2023
#
import math
import os
import numpy as np
import pytest
from Basilisk.architecture import messaging
from Basilisk.simulation import boreAngCalc
from Basilisk.utilities import SimulationBaseClass, macros as mc, RigidBodyKinematics as rbk, unitTestSupport
path = os.path.dirname(os.path.abspath(__file__))
# 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("inertialHeading, eulerLoc",
[([1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
([0.0, 1.0, 0.0], [0.0, 0.0, 0.0]),
([0.0, 0.0, 1.0], [0.0, 0.0, 0.0]),
([1.0, 0.0, 0.0], [np.pi / 4, 0.0, - np.pi / 4]),
([0.0, 1.0, 0.0], [np.pi / 4, 0.0, - np.pi / 4]),
([0.0, 0.0, 1.0], [np.pi / 4, 0.0, - np.pi / 4]),
([1 / np.sqrt(2), - 1 / np.sqrt(2), 0.0], [np.pi / 4, 0.0, - np.pi / 4]),
([0.0, 1 / np.sqrt(2), 1 / np.sqrt(2)], [np.pi / 4, 0.0, - np.pi / 4]),
([1 / np.sqrt(2), 0.0, - 1 / np.sqrt(2)], [np.pi / 4, 0.0, - np.pi / 4])])
def test_bore_ang_calc_inertial_heading(show_plots, inertialHeading, eulerLoc):
"""Module Unit Test"""
# each test method requires a single assert method to be called
[testResults, testMessage] = bore_ang_calc_inertial_heading_func(show_plots, inertialHeading, eulerLoc)
assert testResults < 1, testMessage
# Run unit test
def bore_ang_calc_inertial_heading_func(show_plots, inertialHeading, eulerLoc):
testFailCount = 0 # zero unit test result counter
testMessages = [] # create empty array to store test log messages
# Assign task and process names
unitTaskName = "unitTask" # arbitrary name (don't change)
unitProcessName = "unitProcess" # arbitrary name (don't change)
# Create the sim module, process and task
TotalSim = SimulationBaseClass.SimBaseClass()
UnitTestProc = TotalSim.CreateNewProcess(unitProcessName)
UnitTestProc.addTask(TotalSim.CreateNewTask(unitTaskName, mc.sec2nano(1.0)))
# Create the state message and populate it
stateMessage = messaging.SCStatesMsgPayload()
stateMessage.sigma_BN = rbk.euler3212MRP(eulerLoc)
scMsg = messaging.SCStatesMsg().write(stateMessage)
# Initialize the bac module
BACObject = boreAngCalc.BoreAngCalc()
BACObject.ModelTag = "solarArrayBoresight"
boreVec_B = [1.0, 0.0, 0.0]
BACObject.boreVec_B = boreVec_B # boresight in body frame
BACObject.scStateInMsg.subscribeTo(scMsg)
BACObject.inertialHeadingVec_N = inertialHeading
TotalSim.AddModelToTask(unitTaskName, BACObject)
# Configure the recorder
dataLog = BACObject.angOutMsg.recorder()
TotalSim.AddModelToTask(unitTaskName, dataLog)
# Execute simulation
TotalSim.InitializeSimulation()
TotalSim.TotalSim.SingleStepProcesses()
# Configure the tests
# Compute the inertial heading in B frame
dcm_BN = rbk.MRP2C(stateMessage.sigma_BN)
inertialHeading_B = dcm_BN.dot(np.array(inertialHeading))
# Compute the miss angle
missAngle = math.acos(np.array(boreVec_B).dot(inertialHeading_B))
# Extract the miss angle from data
simMissAngle = dataLog.missAngle[0]
# Compare the results
tol = 1E-10
if not unitTestSupport.isDoubleEqual(missAngle, simMissAngle, tol):
testFailCount += 1
testMessages.append("FAILED: Calculating the miss angle of the boresight failed \n")
# print out success message if no error were found
if testFailCount == 0:
print("PASSED")
else:
print(testMessages)
return [testFailCount, ''.join(testMessages)]
# This statement below ensures that the unit test scrip can be run as a
# stand-along python script
#
if __name__ == "__main__":
test_bore_ang_calc_inertial_heading(False, # show_plots
[1.0 / np.sqrt(3), 1.0 / np.sqrt(3), 1.0 / np.sqrt(3)],
[np.pi, 0.0, 0.0])