''' '''
'''
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.
'''
#
# Unit Test Script
# Module Name: cssWlsEst()
# Author: Hanspeter Schaub
# Creation Date: April 29, 2018
#
import pytest
import sys, os, inspect
# import packages as needed e.g. 'numpy', 'ctypes, 'math' etc.
import numpy
import math
import logging
# Import all of the modules that we are going to be called in this simulation
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import unitTestSupport # general support file with common unit test functions
import matplotlib.pyplot as plt
from Basilisk.utilities import macros
from Basilisk.fswAlgorithms.cssWlsEst import cssWlsEst
from Basilisk.fswAlgorithms.fswMessages import fswMessages
from Basilisk.simulation.simFswInterfaceMessages import simFswInterfaceMessages
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
# Function that takes a sun pointing vector and array of CSS normal vectors and
# returns the measurements associated with those normal vectors.
def createCosList(sunPointVec, sensorPointList):
outList = []
for sensorPoint in sensorPointList:
outList.append(numpy.dot(sunPointVec, sensorPoint))
if(outList[-1] < 0.0):
outList[-1] = 0.0
return outList
# Method that checks that all of the numActive outputs from the data array
# that are greater than threshold thresh are consistent with the values in
# measVec
def checkNumActiveAccuracy(measVec, numActiveUse, numActiveFailCriteria, thresh):
numActivePred = 0
testFailCount = 0
# Iterate through measVec and find all valid signals
for i in range(0, 32):
obsVal = measVec.CosValue[i]
if (obsVal > thresh):
numActivePred += 1
# Iterate through the numActive array and sum up all numActive estimates
numActiveTotal = numpy.array([0.])
j = 0
while j < numActiveUse.shape[0]:
numActiveTotal += numActiveUse[j, 1:]
j += 1
numActiveTotal /= j # Mean number of numActive
# If we violate the test criteria, increment the failure count and alert user
if (abs(numActiveTotal[0] - numActivePred) > numActiveFailCriteria):
testFailCount += 1
errorString = "Active number failure for count of: "
errorString += str(numActivePred)
logging.error(errorString)
return testFailCount
# This method takes the sHat estimate output by the estimator and compares that
# against the actual sun vector passed in as an argument. If it doesn't match
# to the specified tolerance, increment failure counter and alert the user
def checksHatAccuracy(testVec, sHatEstUse, angleFailCriteria, TotalSim):
j = 0
testFailCount = 0
sHatTotal = numpy.array([0.0, 0.0, 0.0])
# Sum up all of the sHat estimates from the execution
while j < sHatEstUse.shape[0]:
sHatTotal += sHatEstUse[j, 1:]
j += 1
sHatTotal /= j # mean sHat estimate
# This logic is to protect cases where the dot product numerically breaks acos
dot_value = numpy.dot(sHatTotal, testVec)
if (abs(dot_value > 1.0)):
dot_value -= 2.0 * (dot_value - math.copysign(1.0, dot_value))
# If we violate the failure criteria, increment failure count and alert user
if (abs(math.acos(dot_value)) > angleFailCriteria):
testFailCount += 1
errorString = "Angle fail criteria violated for test vector:"
errorString += str(testVec).strip('[]') + "\n"
errorString += "Criteria violation of: "
errorString += str(abs(math.acos(numpy.dot(sHatTotal, testVec))))
logging.error(errorString)
return testFailCount
# This method takes the sHat estimate output by the estimator and compares that
# against the actual sun vector passed in as an argument. If it doesn't match
# to the specified tolerance, increment failure counter and alert the user
def checkResidAccuracy(testVec, sResids, sThresh, TotalSim):
j = 0
testFailCount = 0
# Sum up all of the sHat estimates from the execution
while j < sResids.shape[0]:
sNormObs = numpy.linalg.norm(sResids[j,1:])
if(sNormObs > sThresh):
testFailCount += 1
errorString = "Residual error computation failure:"
errorString += str(testVec).strip('[]') + "\n"
errorString += "Criteria violation of: "
errorString += str(sNormObs)
logging.error(errorString)
j += 1
return testFailCount
# 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(conditionstring)
[docs]@pytest.mark.parametrize("testSunHeading, testRate", [
("True", "False")
,("False", "True")
])
# provide a unique test method name, starting with test_
def test_module(show_plots, testSunHeading, testRate): # update "module" in this function name to reflect the module name
"""Module Unit Test"""
# each test method requires a single assert method to be called
# pass on the testPlotFixture so that the main test function may set the DataStore attributes
if testSunHeading:
[testResults, testMessage] = cssWlsEstTestFunction(show_plots)
assert testResults < 1, testMessage
if testRate:
[testResults, testMessage] = cssRateTestFunction(show_plots)
assert testResults < 1, testMessage
def cssWlsEstTestFunction(show_plots):
testFailCount = 0 # zero unit test result counter
testMessages = [] # create empty array to store test log messages
unitTaskName = "unitTask" # arbitrary name (don't change)
unitProcessName = "TestProcess" # arbitrary name (don't change)
# Create a sim module as an empty container
unitTestSim = SimulationBaseClass.SimBaseClass()
# Create test thread
testProc = unitTestSim.CreateNewProcess(unitProcessName)
testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, int(1E8)))
# Construct algorithm and associated C++ container
CSSWlsEstFSWConfig = cssWlsEst.CSSWLSConfig()
CSSWlsWrap = unitTestSim.setModelDataWrap(CSSWlsEstFSWConfig)
CSSWlsWrap.ModelTag = "CSSWlsEst"
# Add module to runtime call list
unitTestSim.AddModelToTask(unitTaskName, CSSWlsWrap, CSSWlsEstFSWConfig)
# Initialize the WLS estimator configuration data
CSSWlsEstFSWConfig.cssDataInMsgName = "css_data_aggregate"
CSSWlsEstFSWConfig.cssConfigInMsgName = "css_config_data"
CSSWlsEstFSWConfig.navStateOutMsgName = "css_nav_sunHeading"
CSSWlsEstFSWConfig.cssWLSFiltResOutMsgName = "css_est_pfr"
CSSWlsEstFSWConfig.useWeights = False
CSSWlsEstFSWConfig.sensorUseThresh = 0.15
CSSOrientationList = [
[0.70710678118654746, -0.5, 0.5],
[0.70710678118654746, -0.5, -0.5],
[0.70710678118654746, 0.5, -0.5],
[0.70710678118654746, 0.5, 0.5],
[-0.70710678118654746, 0, 0.70710678118654757],
[-0.70710678118654746, 0.70710678118654757, 0.0],
[-0.70710678118654746, 0, -0.70710678118654757],
[-0.70710678118654746, -0.70710678118654757, 0.0],
]
numCSS = len(CSSOrientationList)
# set the CSS unit vectors
cssConfigData = fswMessages.CSSConfigFswMsg()
totalCSSList = []
for CSSHat in CSSOrientationList:
CSSConfigElement = fswMessages.CSSUnitConfigFswMsg()
CSSConfigElement.CBias = 1.0
CSSConfigElement.nHat_B = CSSHat
totalCSSList.append(CSSConfigElement)
cssConfigData.nCSS = numCSS
cssConfigData.cssVals = totalCSSList
unitTestSupport.setMessage(unitTestSim.TotalSim,
unitProcessName,
CSSWlsEstFSWConfig.cssConfigInMsgName,
cssConfigData)
# Initialize input message
cssDataMsg = simFswInterfaceMessages.CSSArraySensorIntMsg()
unitTestSupport.setMessage(unitTestSim.TotalSim,
unitProcessName,
CSSWlsEstFSWConfig.cssDataInMsgName,
cssDataMsg)
angleFailCriteria = 17.5 * math.pi / 180.0 # Get 95% effective charging in this case
numActiveFailCriteria = 0.000001 # basically zero
residFailCriteria = 1.0E-12 #Essentially numerically "small"
# Log the output message as well as the internal numACtiveCss variables
unitTestSim.TotalSim.logThisMessage(CSSWlsEstFSWConfig.navStateOutMsgName, int(1E8))
unitTestSim.TotalSim.logThisMessage(CSSWlsEstFSWConfig.cssWLSFiltResOutMsgName, int(1E8))
unitTestSim.AddVariableForLogging("CSSWlsEst.numActiveCss", int(1E8))
# Initial test is all of the principal body axes
TestVectors = [[-1.0, 0.0, 0.0],
[0.0, -1.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, -1.0],
[0.0, 0.0, 1.0]]
# Initialize test and then step through all of the test vectors in a loop
unitTestSim.InitializeSimulation()
CSSWlsWrap.Reset(0) # this module reset function needs a time input (in NanoSeconds)
stepCount = 0
logLengthPrev = 0
truthData = []
for testVec in TestVectors:
if (stepCount > 1): # Doing this to test permutations and get code coverage
CSSWlsEstFSWConfig.useWeights = True
# Get observation data based on sun pointing and CSS orientation data
cssDataMsg.CosValue = createCosList(testVec, CSSOrientationList)
# Write in the observation data to the input message
unitTestSim.TotalSim.WriteMessageData(CSSWlsEstFSWConfig.cssDataInMsgName,
cssDataMsg.getStructSize(),
0,
cssDataMsg)
# Increment the stop time to new termination value
unitTestSim.ConfigureStopTime(int((stepCount + 1) * 1E9))
# Execute simulation to current stop time
unitTestSim.ExecuteSimulation()
stepCount += 1
# Pull logged data out into workspace for analysis
sHatEst = unitTestSim.pullMessageLogData(CSSWlsEstFSWConfig.navStateOutMsgName + '.vehSunPntBdy',
list(range(3)))
numActive = unitTestSim.GetLogVariableData("CSSWlsEst.numActiveCss")
sHatEstUse = sHatEst[logLengthPrev:, :] # Only data for this subtest
numActiveUse = numActive[logLengthPrev + 1:, :] # Only data for this subtest
# Check failure criteria and add test failures
testFailCount += checksHatAccuracy(testVec, sHatEstUse, angleFailCriteria,
unitTestSim)
testFailCount += checkNumActiveAccuracy(cssDataMsg, numActiveUse,
numActiveFailCriteria, CSSWlsEstFSWConfig.sensorUseThresh)
filtRes = unitTestSim.pullMessageLogData(CSSWlsEstFSWConfig.cssWLSFiltResOutMsgName + '.postFitRes',
list(range(8)))
testFailCount += checkResidAccuracy(testVec, filtRes, residFailCriteria,
unitTestSim)
# Pop truth state onto end of array for plotting purposes
currentRow = [sHatEstUse[0, 0]]
currentRow.extend(testVec)
truthData.append(currentRow)
currentRow = [sHatEstUse[-1, 0]]
currentRow.extend(testVec)
truthData.append(currentRow)
logLengthPrev = sHatEst.shape[0]
# Hand construct case where we get low coverage (2 valid sensors)
LonVal = 0.0
LatVal = 40.68 * math.pi / 180.0
doubleTestVec = [math.sin(LatVal), math.cos(LatVal) * math.sin(LonVal),
math.cos(LatVal) * math.cos(LonVal)]
cssDataMsg.CosValue = createCosList(doubleTestVec, CSSOrientationList)
# Write in double coverage conditions and ensure that we get correct outputs
unitTestSim.TotalSim.WriteMessageData(CSSWlsEstFSWConfig.cssDataInMsgName,
cssDataMsg.getStructSize(),
0,
cssDataMsg)
unitTestSim.ConfigureStopTime(int((stepCount + 1) * 1E9))
unitTestSim.ExecuteSimulation()
stepCount += 1
sHatEst = unitTestSim.pullMessageLogData(CSSWlsEstFSWConfig.navStateOutMsgName + '.vehSunPntBdy',
list(range(3)))
numActive = unitTestSim.GetLogVariableData("CSSWlsEst.numActiveCss")
sHatEstUse = sHatEst[logLengthPrev:, :]
numActiveUse = numActive[logLengthPrev + 1:, :]
logLengthPrev = sHatEst.shape[0]
currentRow = [sHatEstUse[0, 0]]
currentRow.extend(doubleTestVec)
truthData.append(currentRow)
currentRow = [sHatEstUse[-1, 0]]
currentRow.extend(doubleTestVec)
truthData.append(currentRow)
# Check test criteria again
testFailCount += checksHatAccuracy(doubleTestVec, sHatEstUse, angleFailCriteria,
unitTestSim)
testFailCount += checkNumActiveAccuracy(cssDataMsg, numActiveUse,
numActiveFailCriteria, CSSWlsEstFSWConfig.sensorUseThresh)
# Same test as above, but zero first element to get to a single coverage case
cssDataMsg.CosValue[0] = 0.0
unitTestSim.TotalSim.WriteMessageData(CSSWlsEstFSWConfig.cssDataInMsgName,
cssDataMsg.getStructSize(),
0,
cssDataMsg)
unitTestSim.ConfigureStopTime(int((stepCount + 1) * 1E9))
unitTestSim.ExecuteSimulation()
stepCount += 1
numActive = unitTestSim.GetLogVariableData("CSSWlsEst.numActiveCss")
numActiveUse = numActive[logLengthPrev + 1:, :]
sHatEst = unitTestSim.pullMessageLogData(CSSWlsEstFSWConfig.navStateOutMsgName + '.vehSunPntBdy',
list(range(3)))
sHatEstUse = sHatEst[logLengthPrev + 1:, :]
logLengthPrev = sHatEst.shape[0]
testFailCount += checkNumActiveAccuracy(cssDataMsg, numActiveUse,
numActiveFailCriteria, CSSWlsEstFSWConfig.sensorUseThresh)
currentRow = [sHatEstUse[0, 0]]
currentRow.extend(doubleTestVec)
truthData.append(currentRow)
currentRow = [sHatEstUse[-1, 0]]
currentRow.extend(doubleTestVec)
truthData.append(currentRow)
# Same test as above, but zero first and fourth elements to get to zero coverage
cssDataMsg.CosValue[0] = 0.0
cssDataMsg.CosValue[3] = 0.0
unitTestSim.TotalSim.WriteMessageData(CSSWlsEstFSWConfig.cssDataInMsgName,
cssDataMsg.getStructSize(),
0,
cssDataMsg)
unitTestSim.ConfigureStopTime(int((stepCount + 1) * 1E9))
unitTestSim.ExecuteSimulation()
numActive = unitTestSim.GetLogVariableData("CSSWlsEst.numActiveCss")
numActiveUse = numActive[logLengthPrev:, :]
logLengthPrev = numActive.shape[0]
testFailCount += checkNumActiveAccuracy(cssDataMsg, numActiveUse,
numActiveFailCriteria, CSSWlsEstFSWConfig.sensorUseThresh)
# Format data for plotting
truthData = numpy.array(truthData)
sHatEst = unitTestSim.pullMessageLogData(CSSWlsEstFSWConfig.navStateOutMsgName + '.vehSunPntBdy',
list(range(3)))
numActive = unitTestSim.GetLogVariableData("CSSWlsEst.numActiveCss")
#
# test the case where all CSS signals are zero
#
cssDataMsg.CosValue = numpy.zeros(len(CSSOrientationList))
unitTestSim.TotalSim.WriteMessageData(CSSWlsEstFSWConfig.cssDataInMsgName,
cssDataMsg.getStructSize(),
0,
cssDataMsg)
unitTestSim.ConfigureStopTime(int((stepCount + 2) * 1E9))
unitTestSim.ExecuteSimulation()
sHatEstZero = unitTestSim.pullMessageLogData(CSSWlsEstFSWConfig.navStateOutMsgName + '.vehSunPntBdy',
list(range(3)))
sHatEstZeroUse = sHatEstZero[logLengthPrev + 1:, :]
trueVector = [[0.0, 0.0, 0.0]]*len(sHatEstZeroUse)
for i in range(0,len(trueVector)):
# check a vector values
if not unitTestSupport.isArrayEqual(sHatEstZeroUse[i],trueVector[i],3,1e-12):
testFailCount += 1
testMessages.append("FAILED: " + CSSWlsWrap.ModelTag + " Module failed " +
CSSWlsEstFSWConfig.navStateOutMsgName + " unit test at t=" +
str(sHatEstZeroUse[i,0] * macros.NANO2SEC) +
"sec\n")
if show_plots:
plt.figure(1)
plt.plot(sHatEst[:, 0] * 1.0E-9, sHatEst[:, 1], label='x-Sun')
plt.plot(sHatEst[:, 0] * 1.0E-9, sHatEst[:, 2], label='y-Sun')
plt.plot(sHatEst[:, 0] * 1.0E-9, sHatEst[:, 3], label='z-Sun')
plt.legend(loc='upper left')
plt.xlabel('Time (s)')
plt.ylabel('Unit Component (--)')
plt.figure(2)
plt.plot(numActive[:, 0] * 1.0E-9, numActive[:, 1])
plt.xlabel('Time (s)')
plt.ylabel('Number Active CSS (--)')
plt.figure(3)
plt.subplot(3, 1, 1)
plt.plot(sHatEst[:, 0] * 1.0E-9, sHatEst[:, 1], label='Est')
plt.plot(truthData[:, 0] * 1.0E-9, truthData[:, 1], 'r--', label='Truth')
plt.xlabel('Time (s)')
plt.ylabel('X Component (--)')
plt.legend(loc='lower right')
plt.subplot(3, 1, 2)
plt.plot(sHatEst[:, 0] * 1.0E-9, sHatEst[:, 2], label='Est')
plt.plot(truthData[:, 0] * 1.0E-9, truthData[:, 2], 'r--', label='Truth')
plt.xlabel('Time (s)')
plt.ylabel('Y Component (--)')
plt.subplot(3, 1, 3)
plt.plot(sHatEst[:, 0] * 1.0E-9, sHatEst[:, 3], label='Est')
plt.plot(truthData[:, 0] * 1.0E-9, truthData[:, 3], 'r--', label='Truth')
plt.xlabel('Time (s)')
plt.ylabel('Z Component (--)')
plt.show()
plt.close('all')
# print out success message if no error were found
if testFailCount == 0:
print("PASSED: " + CSSWlsWrap.ModelTag)
# each test method requires a single assert method to be called
# this check below just makes sure no sub-test failures were found
return [testFailCount, ''.join(testMessages)]
def cssRateTestFunction(show_plots):
testFailCount = 0 # zero unit test result counter
testMessages = [] # create empty array to store test log messages
unitTaskName = "unitTask" # arbitrary name (don't change)
unitProcessName = "TestProcess" # arbitrary name (don't change)
# Create a sim module as an empty container
unitTestSim = SimulationBaseClass.SimBaseClass()
# Create test thread
testProc = unitTestSim.CreateNewProcess(unitProcessName)
testProcessRate = macros.sec2nano(0.5) # update process rate update time
testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, testProcessRate))
# Construct algorithm and associated C++ container
moduleConfig = cssWlsEst.CSSWLSConfig()
moduleWrap = unitTestSim.setModelDataWrap(moduleConfig)
moduleWrap.ModelTag = "CSSWlsEst"
# Add module to runtime call list
unitTestSim.AddModelToTask(unitTaskName, moduleWrap, moduleConfig)
# Initialize the WLS estimator configuration data
moduleConfig.cssDataInMsgName = "css_data_aggregate"
moduleConfig.cssConfigInMsgName = "css_config_data"
moduleConfig.navStateOutMsgName = "css_nav_sunHeading"
moduleConfig.useWeights = False
moduleConfig.sensorUseThresh = 0.15
CSSOrientationList = [
[0.70710678118654746, -0.5, 0.5],
[0.70710678118654746, -0.5, -0.5],
[0.70710678118654746, 0.5, -0.5],
[0.70710678118654746, 0.5, 0.5],
[-0.70710678118654746, 0, 0.70710678118654757],
[-0.70710678118654746, 0.70710678118654757, 0.0],
[-0.70710678118654746, 0, -0.70710678118654757],
[-0.70710678118654746, -0.70710678118654757, 0.0],
]
numCSS = len(CSSOrientationList)
# set the CSS unit vectors
cssConfigData = fswMessages.CSSConfigFswMsg()
totalCSSList = []
for CSSHat in CSSOrientationList:
CSSConfigElement = fswMessages.CSSUnitConfigFswMsg()
CSSConfigElement.CBias = 1.0
CSSConfigElement.nHat_B = CSSHat
totalCSSList.append(CSSConfigElement)
cssConfigData.nCSS = numCSS
cssConfigData.cssVals = totalCSSList
unitTestSupport.setMessage(unitTestSim.TotalSim,
unitProcessName,
moduleConfig.cssConfigInMsgName,
cssConfigData)
# Initialize input message
cssDataMsg = simFswInterfaceMessages.CSSArraySensorIntMsg()
unitTestSupport.setMessage(unitTestSim.TotalSim,
unitProcessName,
moduleConfig.cssDataInMsgName,
cssDataMsg)
# Log the output message as well as the internal numACtiveCss variables
unitTestSim.TotalSim.logThisMessage(moduleConfig.navStateOutMsgName, testProcessRate)
# Get observation data based on sun pointing and CSS orientation data
cssDataMsg.CosValue = createCosList([1.0, 0.0, 0.0], CSSOrientationList)
# Write in the observation data to the input message
unitTestSim.TotalSim.WriteMessageData(moduleConfig.cssDataInMsgName,
cssDataMsg.getStructSize(),
0,
cssDataMsg)
# Initialize test and then step through all of the test vectors in a loop
unitTestSim.InitializeSimulation()
# Increment the stop time to new termination value
unitTestSim.ConfigureStopTime(macros.sec2nano(1.0))
# Execute simulation to current stop time
unitTestSim.ExecuteSimulation()
# rotate sun heading by 90 degrees
cssDataMsg.CosValue = createCosList([0.0, 1.0, 0.0], CSSOrientationList)
# Write in the observation data to the input message
unitTestSim.TotalSim.WriteMessageData(moduleConfig.cssDataInMsgName,
cssDataMsg.getStructSize(),
0,
cssDataMsg)
unitTestSim.ConfigureStopTime(macros.sec2nano(2.0))
unitTestSim.ExecuteSimulation()
# test the module reset function
moduleWrap.Reset(1) # this module reset function needs a time input (in NanoSeconds)
unitTestSim.ConfigureStopTime(macros.sec2nano(2.5))
unitTestSim.ExecuteSimulation()
cssDataMsg.CosValue = createCosList([1.0, 0.0, 0.0], CSSOrientationList)
unitTestSim.TotalSim.WriteMessageData(moduleConfig.cssDataInMsgName,
cssDataMsg.getStructSize(),
0,
cssDataMsg)
unitTestSim.ConfigureStopTime(macros.sec2nano(3.0))
unitTestSim.ExecuteSimulation()
# Pull logged data out into workspace for analysis
omegaEst = unitTestSim.pullMessageLogData(moduleConfig.navStateOutMsgName + '.omega_BN_B',
list(range(3)))
accuracy = 1e-6
trueVector = [
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, -3.14159265],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, +3.14159265]
]
testFailCount, testMessages = unitTestSupport.compareArray(trueVector, omegaEst,
accuracy, "CSS Rate Vector",
testFailCount, testMessages)
# print out success message if no error were found
snippentName = "passFailRate"
if testFailCount == 0:
colorText = 'ForestGreen'
print("PASSED: " + moduleWrap.ModelTag)
passedText = r'\textcolor{' + colorText + '}{' + "PASSED" + '}'
else:
colorText = 'Red'
print("Failed: " + moduleWrap.ModelTag)
passedText = r'\textcolor{' + colorText + '}{' + "Failed" + '}'
unitTestSupport.writeTeXSnippet(snippentName, passedText, path)
# each test method requires a single assert method to be called
# this check below just makes sure no sub-test failures were found
return [testFailCount, ''.join(testMessages)]
#
# This statement below ensures that the unitTestScript can be run as a
# stand-along python script
#
if __name__ == "__main__":
test_module(
False, # show_plots
False, # testSunHeading Flag
True # testRate Flag
)