''' '''
'''
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.
'''
import pytest
import os, inspect
#
# Spice Unit Test
#
# Purpose: Test the proper function of the Spice Ephemeris module.
# Proper function is tested by comparing Spice Ephemeris to
# JPL Horizons Database for different planets and times of year
# Author: Thibaud Teil
# Creation Date: Dec. 20, 2016
#
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
from Basilisk import __path__
bskPath = __path__[0]
import datetime
from Basilisk.utilities import unitTestSupport
from Basilisk.utilities import SimulationBaseClass
import numpy
from Basilisk.simulation import spice_interface
from Basilisk.utilities import macros
import matplotlib.pyplot as plt
# Class in order to plot using data accross the different paramatrized scenarios
class DataStore:
def __init__(self):
self.Date = [] # replace these with appropriate containers for the data to be stored for plotting
self.MarsPosErr = []
self.EarthPosErr = []
self.SunPosErr = []
def plotData(self):
fig1 = plt.figure(1)
rect =fig1.patch
rect.set_facecolor('white')
plt.xticks(numpy.arange(len(self.Date)), self.Date)
plt.plot(self.MarsPosErr, 'r', label='Mars')
plt.plot(self.EarthPosErr, 'bs', label='Earth')
plt.plot(self.SunPosErr, 'yo', label='Sun')
# plt.ylim(0,0.000005)
# plt.ylim(0,0.02)
plt.rc('font', size=50)
plt.legend(loc='upper left')
fig1.autofmt_xdate()
plt.xlabel('Date of test')
plt.ylabel('Position Error [m]')
plt.show()
def giveData(self):
plt.figure(1)
plt.close(1)
fig1 = plt.figure(1,figsize=(7,5), dpi=80, facecolor='w', edgecolor='k')
plt.xticks(numpy.arange(len(self.Date)), self.Date)
plt.plot(self.MarsPosErr, 'r', label='Mars')
plt.plot(self.EarthPosErr, 'b', label='Earth')
plt.plot(self.SunPosErr, 'y', label='Sun')
plt.legend(loc='upper left')
fig1.autofmt_xdate()
plt.xlabel('Date of test')
plt.ylabel('Position Error [m]')
return plt
# Py.test fixture in order to plot
@pytest.fixture(scope="module")
def testPlottingFixture(show_plots):
dataStore = DataStore()
yield dataStore
plt = dataStore.giveData()
unitTestSupport.writeFigureLaTeX('EphemMars', 'Ephemeris Error on Mars', plt, 'height=0.7\\textwidth, keepaspectratio', path)
plt.ylim(0, 2E-2)
unitTestSupport.writeFigureLaTeX('EphemEarth', 'Ephemeris Error on Earth', plt, 'height=0.7\\textwidth, keepaspectratio', path)
plt.ylim(0, 5E-6)
unitTestSupport.writeFigureLaTeX('EphemSun', 'Ephemeris Error on Sun', plt, 'height=0.7\\textwidth, keepaspectratio', path)
if show_plots:
dataStore.plotData()
# 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(True)
# 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("DateSpice, DatePlot , MarsTruthPos , EarthTruthPos, , SunTruthPos", [
("2015 February 10, 00:00:00.0 TDB", "02/10/15" ,[2.049283795042291E+08, 4.654550957513031E+07, 1.580778617009296E+07] ,[-1.137790671899544E+08, 8.569008401822130E+07, 3.712507705247846E+07],[4.480338216752146E+05, -7.947764237588293E+04, -5.745748832696378E+04]),
("2015 February 20, 00:00:00.0 TDB", "02/20/15" ,[ 1.997780190793433E+08, 6.636509140613769E+07, 2.503734390917690E+07], [-1.286636391244711E+08, 6.611156946652703E+07, 2.863736192793162E+07],[4.535258225415821E+05, -7.180260790174162E+04, -5.428151563919459E+04]),
("2015 April 10, 00:00:00.0 TDB", "04/10/15" ,[1.468463828343225E+08, 1.502909016357645E+08, 6.495986693265321E+07],[-1.406808744055921E+08, -4.614996219219251E+07, -2.003047222725225E+07],[4.786772771370058E+05, -3.283487082146838E+04, -3.809690999538498E+04]),
("2015 April 20, 00:00:00.0 TDB", "04/20/15" ,[1.313859463489376E+08, 1.637422864185919E+08, 7.154683454681394E+07], [-1.304372112275012E+08, -6.769916175223185E+07, -2.937179538983412E+07],[4.834188130324496E+05, -2.461799304268214E+04, -3.467083541217462E+04]),
("2015 June 10, 00:00:00.0 TDB", "06/10/15" , [3.777078276008123E+07, 2.080046702252371E+08, 9.437503998071109E+07],[-2.946784585692780E+07, -1.365779215199542E+08, -5.923299652516938E+07],[5.052070933145228E+05, 1.837038638578682E+04, -1.665575509449521E+04]),
("2015 June 20, 00:00:00.0 TDB", "06/20/15" , [1.778745850655047E+07, 2.116712756672253E+08, 9.659608128589308E+07], [-4.338053580692466E+06, -1.393743714818930E+08, -6.044500073550620E+07],[5.090137386469169E+05, 2.694272566092012E+04, -1.304862690440150E+04]),
("2015 August 10, 00:00:00.0 TDB", "08/10/15" , [-8.302866300591002E+07, 2.053384243312354E+08, 9.641192179982041E+07],[1.111973130587749E+08, -9.507060674145325E+07, -4.123957889640842E+07],[5.263951240980130E+05, 7.113788303899391E+04, 5.601415938789949E+03]),
("2015 August 20, 00:00:00.0 TDB", "08/20/15" , [-1.015602120938274E+08, 1.994877113707506E+08, 9.422840996376510E+07], [1.267319535735967E+08, -7.664279872369213E+07, -3.325170492059625E+07],[5.294368386862668E+05, 7.989635630604146E+04, 9.307380417148834E+03]),
("2015 October 10, 00:00:00.0 TDB", "10/10/15" , [-1.828944464171771E+08, 1.498117608528323E+08, 7.363786404040858E+07],[1.440636517249971E+08, 3.827074461651713E+07, 1.656440704503509E+07],[5.433268959250135E+05, 1.251717566539142E+05, 2.849999378507032E+04]),
("2015 October 20, 00:00:00.0 TDB", "10/20/15" , [-1.955685835661002E+08, 1.367530082668284E+08, 6.799006120628108E+07],[1.343849818314204E+08, 6.019977403127116E+07, 2.607024615553362E+07],[5.457339294611338E+05, 1.342148834831663E+05, 3.234185290692726E+04]),
("2015 December 10, 00:00:00.0 TDB", "12/10/15" , [-2.392022492203017E+08, 5.847873056287902E+07, 3.326431285934674E+07],[3.277145427626925E+07, 1.320956053465003E+08, 5.723804900679157E+07],[5.559607335969181E+05, 1.813263443761486E+05, 5.242991145066972E+04]),
("2015 December 20, 00:00:00.0 TDB", "12/20/15" , [-2.432532635321551E+08, 4.156075241419497E+07, 2.561357000250468E+07], [6.866134677340530E+06, 1.351080593806486E+08, 5.854460725992832E+07],[5.575179227151767E+05, 1.907337298309725E+05, 5.645553733620559E+04]),
("2016 February 10, 00:00:00.0 TDB", "02/10/16" , [-2.385499316808322E+08, -4.818711325555268E+07, -1.567983931044450E+07],[-1.132504603146183E+08, 8.647183658089657E+07, 3.746046979137553E+07],[5.630027736619673E+05, 2.402590276126245E+05, 7.772804647512530E+04]),
("2016 February 20, 00:00:00.0 TDB", "02/20/16" , [-2.326189626865872E+08, -6.493600278878165E+07,-2.352250994262638E+07], [-1.282197228963156E+08, 6.695033443521750E+07, 2.899822684259482E+07],[5.635403728358555E+05, 2.498445036007692E+05, 8.185973180152237E+04]),
("2016 April 10, 00:00:00.0 TDB", "04/10/16" , [-1.795928090776869E+08, -1.395102817786797E+08, -5.916067235603344E+07],[-1.399773606371217E+08, -4.749126225182984E+07, -2.061331784067504E+07],[5.639699901756521E+05, 2.977755140828988E+05, 1.025609223621660E+05]),
("2016 April 20, 00:00:00.0 TDB", "04/20/16" , [-1.646488222290798E+08, -1.517361128528306E+08, -6.517204439842616E+07], [-1.294310199316289E+08, -6.890085351639988E+07, -2.989515576444890E+07],[5.636348418836893E+05, 3.073681895822543E+05, 1.067117713233756E+05]),
("2016 June 10, 00:00:00.0 TDB", "06/10/16" , [-7.085675304355654E+07, -1.933788289169757E+08, -8.680583098365544E+07],[-2.756178030784301E+07, -1.365892814324490E+08, -5.923888961370525E+07],[5.597493293268962E+05, 3.563312003229167E+05, 1.279448896916322E+05]),
("2016 June 20, 00:00:00.0 TDB", "06/20/16" , [-4.994076874358515E+07, -1.967336617729592E+08, -8.890950206156498E+07], [-2.413995783152328E+06, -1.390828611356292E+08, -6.032062925759617E+07],[5.585605124166313E+05, 3.659402953599973E+05, 1.321213212542782E+05]),
("2016 August 10, 00:00:00.0 TDB", "08/10/16" , [5.965895856067932E+07, -1.851251207911916E+08, -8.654489416392270E+07],[1.124873641861596E+08, -9.344410235679612E+07, -4.053621796412993E+07],[5.501477436262188E+05, 4.149525682073312E+05, 1.534983234437988E+05]),
("2016 August 20, 00:00:00.0 TDB", "08/20/16" , [8.021899957229958E+07, -1.770523551156136E+08, -8.339737954004113E+07] , [1.277516713236801E+08, -7.484361731649198E+07, -3.247232896320245E+07],[5.480148786547601E+05, 4.245199573757614E+05, 1.576874582857746E+05]),
("2016 October 10, 00:00:00.0 TDB", "10/10/16" , [1.674991232418756E+08, -1.090427183510760E+08, -5.456038490399034E+07],[ 1.434719792031415E+08, 4.029387436854774E+07, 1.744057129058395E+07],[5.348794087050703E+05, 4.727986075510736E+05, 1.788947974297997E+05]),
("2016 October 20, 00:00:00.0 TDB", "10/20/16" , [1.797014954219412E+08, -9.133803506487390E+07, -4.676932170648168E+07] , [ 1.334883617893556E+08, 6.209917895944476E+07, 2.689423661839254E+07],[5.318931290166953E+05, 4.821605725626923E+05, 1.830201949404063E+05]),
("2016 December 10, 00:00:00.0 TDB", "12/10/16" , [2.087370835407280E+08, 1.035903131428964E+07, -9.084001492689754E+05] ,[3.082308903715757E+07, 1.328026978502711E+08, 5.754559376449955E+07],[5.147792796720199E+05, 5.293951386498961E+05, 2.038816823549219E+05]),
("2016 December 20, 00:00:00.0 TDB", "12/20/16" , [2.075411186038260E+08, 3.092173198610598E+07, 8.555251204561792E+06], [4.885421269793877E+06, 1.355125217382336E+08, 5.872015978003684E+07],[5.110736843893399E+05, 5.385860060393942E+05, 2.079481168492821E+05])
])
@pytest.mark.parametrize("useMsg", [False, True])
# provide a unique test method name, starting with test_
def test_unitSpice(testPlottingFixture, show_plots, DateSpice, DatePlot , MarsTruthPos , EarthTruthPos, SunTruthPos, useMsg):
"""Module Unit Test"""
# each test method requires a single assert method to be called
[testResults, testMessage] = unitSpice(testPlottingFixture, show_plots, DateSpice, DatePlot , MarsTruthPos , EarthTruthPos, SunTruthPos, useMsg)
assert testResults < 1, testMessage
# Run unit test
def unitSpice(testPlottingFixture, show_plots, DateSpice, DatePlot , MarsTruthPos , EarthTruthPos, SunTruthPos, useMsg):
testFailCount = 0 # zero unit test result counter
testMessages = [] # create empty array to store test log messages
# Create a sim module as an empty container
unitTaskName = "unitTask" # arbitrary name (don't change)
unitProcessName = "TestProcess" # arbitrary name (don't change)
# Create a sim module as an empty container
TotalSim = SimulationBaseClass.SimBaseClass()
DynUnitTestProc = TotalSim.CreateNewProcess(unitProcessName)
# create the dynamics task and specify the integration update time
DynUnitTestProc.addTask(TotalSim.CreateNewTask(unitTaskName, macros.sec2nano(0.1)))
# Initialize the spice modules that we are using.
SpiceObject = spice_interface.SpiceInterface()
SpiceObject.ModelTag = "SpiceInterfaceData"
SpiceObject.SPICEDataPath = bskPath + '/supportData/EphemerisData/'
SpiceObject.outputBufferCount = 10000
SpiceObject.planetNames = spice_interface.StringVector(["earth", "mars barycenter", "sun"])
SpiceObject.UTCCalInit = DateSpice
TotalSim.AddModelToTask(unitTaskName, SpiceObject)
if useMsg:
SpiceObject.epochInMsgName = "simEpoch"
epochMsg = unitTestSupport.timeStringToGregorianUTCMsg(DateSpice)
unitTestSupport.setMessage(TotalSim.TotalSim,
unitProcessName,
SpiceObject.epochInMsgName,
epochMsg)
# The following value is set, but should not be used by the module. This checks that the above
# epoch message over-rules any info set in this variable.
SpiceObject.UTCCalInit = "1990 February 10, 00:00:00.0 TDB"
# Configure simulation
TotalSim.ConfigureStopTime(int(60.0 * 1E9))
TotalSim.AddVariableForLogging('SpiceInterfaceData.GPSSeconds')
TotalSim.AddVariableForLogging('SpiceInterfaceData.J2000Current')
TotalSim.AddVariableForLogging('SpiceInterfaceData.julianDateCurrent')
TotalSim.AddVariableForLogging('SpiceInterfaceData.GPSWeek')
# Execute simulation
TotalSim.InitializeSimulation()
TotalSim.ExecuteSimulation()
# Get the logged variables (GPS seconds, Julian Date)
DataGPSSec = TotalSim.GetLogVariableData('SpiceInterfaceData.GPSSeconds')
DataJD = TotalSim.GetLogVariableData('SpiceInterfaceData.julianDateCurrent')
#Get parametrized date from DatePlot
year = "".join(("20",DatePlot[6:8]))
date = datetime.datetime( int(year) , int(DatePlot[0:2]) , int(DatePlot[3:5]))
testPlottingFixture.Date.append(DatePlot[0:8])
#Get the GPS time
date2 = datetime.datetime(1980, 0o1, 6) #Start of GPS time
timeDiff = date-date2 #Time in days since GPS time
GPSTimeSeconds = timeDiff.days*86400
#Get the Julian day
JulianStartDate = date.toordinal()+1721424.5
#
# Begin testing module results to truth values
#
# Truth values
# For Time delta check
GPSRow = DataGPSSec[0, :]
InitDiff = GPSRow[1] - GPSRow[0] * 1.0E-9
i = 1
# Compare the GPS seconds values
AllowTolerance = 1E-6
while (i < DataGPSSec.shape[0]):
if date.isoweekday() == 7: # Skip test on Sundays, because it's the end of a GPS week (seconds go to zero)
i += 1
else:
GPSRow = DataGPSSec[i, :]
CurrDiff = GPSRow[1] - GPSRow[0] * 1.0E-9
if (abs(CurrDiff - InitDiff) > AllowTolerance):
testFailCount += 1
testMessages.append("FAILED: Time delta check failed with difference of: %(DiffVal)f \n"% \
{"DiffVal": CurrDiff - InitDiff})
i += 1
# Truth values
# For absolute GPS time check
if (date>datetime.datetime(2015,0o6,30)): # Taking into account the extra leap second added on 6/30/2015
GPSEndTime = GPSTimeSeconds + 17 + 60.0 - 68.184 # 17 GPS skip seconds passed that date
else:
GPSEndTime = GPSTimeSeconds + 16 + 60.0 - 67.184 # 16 GPS skip seconds before
GPSWeek = int(GPSEndTime / (86400 * 7))
GPSSecondAssumed = GPSEndTime - GPSWeek * 86400 * 7
GPSSecDiff = abs(GPSRow[1] - GPSSecondAssumed)
# TestResults['GPSAbsTimeCheck'] = True
AllowTolerance = 1E-4
if useMsg:
AllowTolerance = 2E-2
if (date.isoweekday() != 7 and GPSSecDiff > AllowTolerance): #Skip test days that are Sunday because of the end of a GPS week
testFailCount += 1
testMessages.append("FAILED: Absolute GPS time check failed with difference of: %(DiffVal)f \n" % \
{"DiffVal": GPSSecDiff})
# Truth values
# For absolute Julian date time check
if (date>datetime.datetime(2015,0o6,30)): # Taking into account the extra leap second added on 6/30/2015
JDEndTime = JulianStartDate + 0.0006944440 - 68.184 / (86400)
else:
JDEndTime = JulianStartDate + 0.0006944440 - 67.184 / (86400)
#Simulated values
JDEndSim = DataJD[i - 1, 1]
JDTimeErrorAllow = 0.1 / (24.0 * 3600.0)
if (abs(JDEndSim - JDEndTime) > JDTimeErrorAllow):
testFailCount += 1
testMessages.append("FAILED: Absolute Julian Date time check failed with difference of: %(DiffVal)f \n" % \
{"DiffVal": abs(JDEndSim - JDEndTime)})
# Truth values
# For Mars position check
MarsPosEnd = numpy.array(MarsTruthPos)
MarsPosEnd = MarsPosEnd * 1000.0
# Get Simulated values
FinalMarsMessage = spice_interface.SpicePlanetStateSimMsg()
TotalSim.TotalSim.GetWriteData("mars barycenter_planet_data", 120, FinalMarsMessage, 0)
MarsPosVec = FinalMarsMessage.PositionVector
# Compare Mars position values
MarsPosArray = numpy.array([MarsPosVec[0], MarsPosVec[1], MarsPosVec[2]])
MarsPosDiff = MarsPosArray - MarsPosEnd
PosDiffNorm = numpy.linalg.norm(MarsPosDiff)
# Plot Mars postion values
testPlottingFixture.MarsPosErr.append(PosDiffNorm)
# Test Mars position values
PosErrTolerance = 250
if useMsg:
PosErrTolerance = 1000
if (PosDiffNorm > PosErrTolerance):
testFailCount += 1
testMessages.append("FAILED: Mars position check failed with difference of: %(DiffVal)f \n" % \
{"DiffVal": PosDiffNorm})
# Truth values
# For Earth position check
EarthPosEnd = numpy.array(EarthTruthPos)
EarthPosEnd = EarthPosEnd * 1000.0
#Simulated Earth position values
FinalEarthMessage = spice_interface.SpicePlanetStateSimMsg()
TotalSim.TotalSim.GetWriteData("earth_planet_data", 120, FinalEarthMessage, 0)
EarthPosVec = FinalEarthMessage.PositionVector
# Compare Earth position values
EarthPosArray = numpy.array([EarthPosVec[0], EarthPosVec[1], EarthPosVec[2]])
EarthPosDiff = EarthPosArray - EarthPosEnd
PosDiffNorm = numpy.linalg.norm(EarthPosDiff)
# Plot Earth position values
testPlottingFixture.EarthPosErr.append(PosDiffNorm)
# TestResults['EarthPosCheck'] = True
if (PosDiffNorm > PosErrTolerance):
testFailCount += 1
testMessages.append("FAILED: Earth position check failed with difference of: %(DiffVal)f \n" % \
{"DiffVal": PosDiffNorm})
# Truth Sun position values
SunPosEnd = numpy.array(SunTruthPos)
SunPosEnd = SunPosEnd * 1000.0
#Simulated Sun position values
FinalSunMessage = spice_interface.SpicePlanetStateSimMsg()
TotalSim.TotalSim.GetWriteData("sun_planet_data", 120, FinalSunMessage, 0)
SunPosVec =FinalSunMessage.PositionVector
# Compare Sun position values
SunPosArray = numpy.array([SunPosVec[0], SunPosVec[1], SunPosVec[2]])
SunPosDiff = SunPosArray - SunPosEnd
PosDiffNorm = numpy.linalg.norm(SunPosDiff)
# plot Sun position values
testPlottingFixture.SunPosErr.append(PosDiffNorm)
# Test Sun position values
if (PosDiffNorm > PosErrTolerance):
testFailCount += 1
testMessages.append("FAILED: Sun position check failed with difference of: %(DiffVal)f \n" % \
{"DiffVal": PosDiffNorm})
if date==datetime.datetime(2016,12,20): # Only test the false files on the last test
# Test non existing directory
SpiceObject.SPICEDataPath = "ADirectoryThatDoesntreallyexist"
SpiceObject.SPICELoaded = False
#TotalSim.ConfigureStopTime(int(1E9)) #Uncomment these 3 lines to test false directory
#TotalSim.InitializeSimulation()
#TotalSim.ExecuteSimulation()
# Test overly long planet name
SpiceObject.SPICEDataPath = ""
SpiceObject.SPICELoaded = False
SpiceObject.planetNames = spice_interface.StringVector(["earth", "mars", "sun",
"thisisaplanetthatisntreallyanythingbutIneedthenametobesolongthatIhitaninvalidconditioninmycode"])
#TotalSim.ConfigureStopTime(int(1E9)) #Uncomment these 3 lines to test false planet names
#TotalSim.InitializeSimulation()
#TotalSim.ExecuteSimulation()
# print out success message if no error were found
if testFailCount == 0:
print(" \n PASSED ")
# 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 unit test scrip can be run as a
# stand-along python script
#
if __name__ == "__main__":
test_unitSpice(
testPlottingFixture,
False, # show_plots
"2015 February 10, 00:00:00.00 TDB",
"02/10/15",
[2.049283795042291E+08, 4.654550957513031E+07, 1.580778617009296E+07],
[-1.137790671899544E+08, 8.569008401822130E+07, 3.712507705247846E+07],
[4.480338216752146E+05, -7.947764237588293E+04, -5.745748832696378E+04],
True # useMsg
)