# 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.
#
# Unit Test Script
# Module Name: prescribedMotion integrated unit test with prescribedRotation1DOF and prescribedLinearTranslation
# Author: Leah Kiner
# Creation Date: Jan 10, 2022
#
import inspect
import os
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pytest
from Basilisk.architecture import messaging
from Basilisk.simulation import gravityEffector
from Basilisk.simulation import prescribedLinearTranslation
from Basilisk.simulation import prescribedMotionStateEffector
from Basilisk.simulation import prescribedRotation1DOF
from Basilisk.simulation import spacecraft
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros
from Basilisk.utilities import RigidBodyKinematics as rbk
from Basilisk.utilities import unitTestSupport
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
splitPath = path.split('simulation')
matplotlib.rc('xtick', labelsize=16)
matplotlib.rc('ytick', labelsize=16)
# Vary the simulation parameters for pytest
[docs]
@pytest.mark.parametrize("rotTest", [True, False])
@pytest.mark.parametrize("thetaInit", [0, np.pi/18])
@pytest.mark.parametrize("theta_Ref", [np.pi/36])
@pytest.mark.parametrize("posInit", [0, 0.2])
@pytest.mark.parametrize("posRef", [0.1])
@pytest.mark.parametrize("accuracy", [1e-8])
def test_PrescribedMotionTestFunction(show_plots, rotTest, thetaInit, theta_Ref, posInit, posRef, accuracy):
r"""
**Validation Test Description**
The unit test for this module is an integrated test with two flight software profiler modules. This is required
because the dynamics module must be connected to a flight software profiler module to define the states of the
prescribed secondary body that is connected to the rigid spacecraft hub. The integrated test for this module has
two simple scenarios it is testing. The first scenario prescribes a 1 DOF rotation for the prescribed body
using the :ref:`prescribedRotation1DOF` flight software module. The second scenario prescribes
linear translation for the prescribed body using the :ref:`prescribedLinearTranslation` flight software module.
This unit test ensures that the profiled 1 DOF rotational attitude maneuver is properly computed for a series of
initial and reference PRV angles and maximum angular accelerations. The final prescribed attitude and angular
velocity magnitude are compared with the reference values. This unit test also ensures that the profiled
translational maneuver is properly computed for a series of initial and reference positions and maximum
accelerations. The final prescribed position and velocity magnitudes are compared with the reference values.
Additionally for each scenario, the conservation quantities of orbital angular momentum, rotational angular
momentum, and orbital energy are checked to validate the module dynamics.
**Test Parameters**
Args:
rotTest (bool): (True) Runs the rotational motion test. (False) Runs the translational motion test.
thetaInit (float): [rad] Initial PRV angle of the F frame with respect to the M frame
theta_Ref (float): [rad] Reference PRV angle of the F frame with respect to the M frame
thetaDDotMax (float): [rad/s^2] Maximum angular acceleration for the attitude maneuver
scalarPosInit (float): [m] Initial scalar position of the F frame with respect to the M frame
scalarPosRef (float): [m] Reference scalar position of the F frame with respect to the M frame
scalarAccelMax (float): [m/s^2] Maximum acceleration for the translational maneuver
accuracy (float): absolute accuracy value used in the validation tests
**Description of Variables Being Tested**
This unit test ensures that the profiled 1 DOF rotational attitude maneuver is properly computed for a series of
initial and reference PRV angles and maximum angular accelerations. The final prescribed angle ``theta_FM_Final``
and angular velocity magnitude ``thetaDot_Final`` are compared with the reference values ``theta_Ref`` and
``thetaDot_Ref``, respectively. This unit test also ensures that the profiled translational maneuver is properly
computed for a series of initial and reference positions and maximum accelerations. The final prescribed position
magnitude ``r_FM_M_Final`` and velocity magnitude ``rPrime_FM_M_Final`` are compared with the reference values
``r_FM_M_Ref`` and ``rPrime_FM_M_Ref``, respectively. Additionally for each scenario, the conservation quantities
of orbital angular momentum, rotational angular momentum, and orbital energy are checked to validate the module
dynamics.
"""
[testResults, testMessage] = PrescribedMotionTestFunction(show_plots, rotTest, thetaInit, theta_Ref, posInit, posRef, accuracy)
assert testResults < 1, testMessage
[docs]
def PrescribedMotionTestFunction(show_plots, rotTest, thetaInit, theta_Ref, posInit, posRef, accuracy):
"""Call this routine directly to run the unit test."""
__tracebackhide__ = True
testFailCount = 0 # zero unit test result counter
testMessages = [] # create empty list to store test log messages
unitTaskName = "unitTask"
unitProcessName = "TestProcess"
# Create a sim module as an empty container
unitTestSim = SimulationBaseClass.SimBaseClass()
# Create test thread
testIncrement = 0.1 # [s]
testProcessRate = macros.sec2nano(testIncrement) # update process rate update time
testProc = unitTestSim.CreateNewProcess(unitProcessName)
testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, testProcessRate))
# Add the spacecraft module to test file
scObject = spacecraft.Spacecraft()
scObject.ModelTag = "spacecraftBody"
# Define the mass properties of the rigid spacecraft hub
scObject.hub.mHub = 750.0
scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]]
scObject.hub.IHubPntBc_B = [[900.0, 0.0, 0.0], [0.0, 800.0, 0.0], [0.0, 0.0, 600.0]]
# Set the initial inertial hub states
scObject.hub.r_CN_NInit = [[-4020338.690396649], [7490566.741852513], [5248299.211589362]]
scObject.hub.v_CN_NInit = [[-5199.77710904224], [-3436.681645356935], [1041.576797498721]]
scObject.hub.omega_BN_BInit = np.array([0.0, 0.0, 0.0])
scObject.hub.sigma_BNInit = [[0.0], [0.0], [0.0]]
# Add the scObject to the runtime call list
unitTestSim.AddModelToTask(unitTaskName, scObject)
# Add the prescribedMotion dynamics module to test file
platform = prescribedMotionStateEffector.PrescribedMotionStateEffector()
# Define the state effector properties
transAxis_M = np.array([1.0, 0.0, 0.0])
rotAxis_M = np.array([1.0, 0.0, 0.0])
r_FM_M = posInit * transAxis_M
prvInit_FM = thetaInit * rotAxis_M
sigma_FM = rbk.PRV2MRP(prvInit_FM)
platform.mass = 100.0
platform.IPntFc_F = [[50.0, 0.0, 0.0], [0.0, 50.0, 0.0], [0.0, 0.0, 50.0]]
platform.r_MB_B = [0.0, 0.0, 0.0]
platform.r_FcF_F = [0.0, 0.0, 0.0]
platform.r_FM_M = r_FM_M
platform.rPrime_FM_M = np.array([0.0, 0.0, 0.0])
platform.rPrimePrime_FM_M = np.array([0.0, 0.0, 0.0])
platform.omega_FM_F = np.array([0.0, 0.0, 0.0])
platform.omegaPrime_FM_F = np.array([0.0, 0.0, 0.0])
platform.sigma_FM = sigma_FM
platform.omega_MB_B = [0.0, 0.0, 0.0]
platform.omegaPrime_MB_B = [0.0, 0.0, 0.0]
platform.sigma_MB = [0.0, 0.0, 0.0]
platform.ModelTag = "Platform"
# Add platform to spacecraft
scObject.addStateEffector(platform)
# Add the test module to runtime call list
unitTestSim.AddModelToTask(unitTaskName, platform)
if rotTest:
# ** ** ** ** ** ROTATIONAL 1 DOF INTEGRATED TEST: ** ** ** ** **
# Create an instance of the prescribedRotation1DOF module to be tested
PrescribedRot1DOF = prescribedRotation1DOF.PrescribedRotation1DOF()
PrescribedRot1DOF.ModelTag = "prescribedRotation1DOF"
# Add the prescribedRotation1DOF test module to runtime call list
unitTestSim.AddModelToTask(unitTaskName, PrescribedRot1DOF)
# Initialize the prescribedRotation1DOF test module configuration data
accelMax = 0.01 # [rad/s^2]
PrescribedRot1DOF.setRotHat_M(rotAxis_M)
PrescribedRot1DOF.setThetaDDotMax(accelMax)
PrescribedRot1DOF.setThetaInit(thetaInit)
# Create the prescribedRotation1DOF input message
thetaDot_Ref = 0.0 # [rad/s]
SpinningBodyMessageData = messaging.HingedRigidBodyMsgPayload()
SpinningBodyMessageData.theta = theta_Ref
SpinningBodyMessageData.thetaDot = thetaDot_Ref
SpinningBodyMessage = messaging.HingedRigidBodyMsg().write(SpinningBodyMessageData)
PrescribedRot1DOF.spinningBodyInMsg.subscribeTo(SpinningBodyMessage)
platform.prescribedRotationInMsg.subscribeTo(PrescribedRot1DOF.prescribedRotationOutMsg)
# Add Earth gravity to the simulation
earthGravBody = gravityEffector.GravBodyData()
earthGravBody.planetName = "earth_planet_data"
earthGravBody.mu = 0.3986004415E+15
earthGravBody.isCentralBody = True
scObject.gravField.gravBodies = spacecraft.GravBodyVector([earthGravBody])
# Add energy and momentum variables to log
scObjectLog = scObject.logger(["totOrbEnergy", "totOrbAngMomPntN_N", "totRotAngMomPntC_N", "totRotEnergy"])
unitTestSim.AddModelToTask(unitTaskName, scObjectLog)
# Add other states to log
scStateData = scObject.scStateOutMsg.recorder()
prescribedRotStateData = platform.prescribedRotationOutMsg.recorder()
unitTestSim.AddModelToTask(unitTaskName, scStateData)
unitTestSim.AddModelToTask(unitTaskName, prescribedRotStateData)
# Initialize the simulation
unitTestSim.InitializeSimulation()
# Set the simulation time
simTime = np.sqrt(((0.5 * np.abs(theta_Ref - thetaInit)) * 8) / accelMax) + 3 * testIncrement
unitTestSim.ConfigureStopTime(macros.sec2nano(simTime))
# Begin the simulation
unitTestSim.ExecuteSimulation()
# Extract the logged data
orbEnergy = unitTestSupport.addTimeColumn(scObjectLog.times(), scObjectLog.totOrbEnergy)
orbAngMom_N = unitTestSupport.addTimeColumn(scObjectLog.times(), scObjectLog.totOrbAngMomPntN_N)
rotAngMom_N = unitTestSupport.addTimeColumn(scObjectLog.times(), scObjectLog.totRotAngMomPntC_N)
rotEnergy = unitTestSupport.addTimeColumn(scObjectLog.times(), scObjectLog.totRotEnergy)
omega_BN_B = scStateData.omega_BN_B
r_BN_N = scStateData.r_BN_N
sigma_BN = scStateData.sigma_BN
omega_FM_F = prescribedRotStateData.omega_FM_F
omegaPrime_FM_F = prescribedRotStateData.omegaPrime_FM_F
sigma_FM = prescribedRotStateData.sigma_FM
timespan = prescribedRotStateData.times()
thetaDot_Final = np.linalg.norm(omega_FM_F[-1, :])
sigma_FM_Final = sigma_FM[-1, :]
theta_FM_Final = 4 * np.arctan(np.linalg.norm(sigma_FM_Final))
# Setup the conservation quantities
initialOrbAngMom_N = [[orbAngMom_N[0, 1], orbAngMom_N[0, 2], orbAngMom_N[0, 3]]]
finalOrbAngMom = [orbAngMom_N[-1]]
initialRotAngMom_N = [[rotAngMom_N[0, 1], rotAngMom_N[0, 2], rotAngMom_N[0, 3]]]
finalRotAngMom = [rotAngMom_N[-1]]
initialOrbEnergy = [[orbEnergy[0, 1]]]
finalOrbEnergy = [orbEnergy[-1]]
# Convert the logged sigma_FM MRPs to a scalar theta_FM array
n = len(timespan)
theta_FM = []
for i in range(n):
theta_FM.append((180 / np.pi) * (4 * np.arctan(np.linalg.norm(sigma_FM[i, :]))))
plt.close("all")
# Plot theta_FM
theta_Ref_plotting = np.ones(len(timespan)) * theta_Ref
thetaInit_plotting = np.ones(len(timespan)) * thetaInit
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, theta_FM, label=r'$\Phi$')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * theta_Ref_plotting, '--', label=r'$\Phi_{Ref}$')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * thetaInit_plotting, '--', label=r'$\Phi_{0}$')
plt.title(r'$\Phi_{\mathcal{F}/\mathcal{M}}$ Profiled Trajectory', fontsize=14)
plt.ylabel('(deg)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='center right', prop={'size': 16})
# Plot omega_FM_F
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omega_FM_F[:, 0], label=r'$\omega_{1}$')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omega_FM_F[:, 1], label=r'$\omega_{2}$')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omega_FM_F[:, 2], label=r'$\omega_{3}$')
plt.title(r'${}^\mathcal{F} \omega_{\mathcal{F}/\mathcal{M}}$ Profiled Trajectory', fontsize=14)
plt.ylabel('(deg/s)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='upper right', prop={'size': 16})
# Plotting omegaPrime_FM_F
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omegaPrime_FM_F[:, 0], label='1')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omegaPrime_FM_F[:, 1], label='2')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omegaPrime_FM_F[:, 2], label='3')
plt.title(r'${}^\mathcal{F} \omega Prime_{\mathcal{F}/\mathcal{M}}$ Profiled Angular Acceleration', fontsize=14)
plt.ylabel(r'(deg/$s^2$)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='upper right', prop={'size': 16})
# Plot r_BN_N
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, r_BN_N[:, 0], label=r'$r_{1}$')
plt.plot(timespan * macros.NANO2SEC, r_BN_N[:, 1], label=r'$r_{2}$')
plt.plot(timespan * macros.NANO2SEC, r_BN_N[:, 2], label=r'$r_{3}$')
plt.title(r'${}^\mathcal{N} r_{\mathcal{B}/\mathcal{N}}$ Spacecraft Inertial Trajectory', fontsize=14)
plt.ylabel('(m)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='center left', prop={'size': 16})
# Plot sigma_BN
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, sigma_BN[:, 0], label=r'$\sigma_{1}$')
plt.plot(timespan * macros.NANO2SEC, sigma_BN[:, 1], label=r'$\sigma_{2}$')
plt.plot(timespan * macros.NANO2SEC, sigma_BN[:, 2], label=r'$\sigma_{3}$')
plt.title(r'$\sigma_{\mathcal{B}/\mathcal{N}}$ Spacecraft Inertial MRP Attitude', fontsize=14)
plt.ylabel('', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='center left', prop={'size': 16})
# Plot omega_BN_B
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omega_BN_B[:, 0], label=r'$\omega_{1}$')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omega_BN_B[:, 1], label=r'$\omega_{2}$')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omega_BN_B[:, 2], label=r'$\omega_{3}$')
plt.title(r'Spacecraft Hub Angular Velocity ${}^\mathcal{B} \omega_{\mathcal{B}/\mathcal{N}}$', fontsize=14)
plt.xlabel('Time (s)', fontsize=16)
plt.ylabel('(deg/s)', fontsize=16)
plt.legend(loc='lower right', prop={'size': 16})
# Plotting: Conservation quantities
plt.figure()
plt.clf()
plt.plot(orbAngMom_N[:, 0] * macros.NANO2SEC, (orbAngMom_N[:, 1] - orbAngMom_N[0, 1]) / orbAngMom_N[0, 1],
orbAngMom_N[:, 0] * macros.NANO2SEC, (orbAngMom_N[:, 2] - orbAngMom_N[0, 2]) / orbAngMom_N[0, 2],
orbAngMom_N[:, 0] * macros.NANO2SEC, (orbAngMom_N[:, 3] - orbAngMom_N[0, 3]) / orbAngMom_N[0, 3])
plt.title('Orbital Angular Momentum Relative Difference', fontsize=14)
plt.ylabel('(Nms)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.figure()
plt.clf()
plt.plot(orbEnergy[:, 0] * macros.NANO2SEC, (orbEnergy[:, 1] - orbEnergy[0, 1]) / orbEnergy[0, 1])
plt.title('Orbital Energy Relative Difference', fontsize=14)
plt.ylabel('Energy (J)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.figure()
plt.clf()
plt.plot(rotAngMom_N[:, 0] * macros.NANO2SEC, (rotAngMom_N[:, 1] - rotAngMom_N[0, 1]),
rotAngMom_N[:, 0] * macros.NANO2SEC, (rotAngMom_N[:, 2] - rotAngMom_N[0, 2]),
rotAngMom_N[:, 0] * macros.NANO2SEC, (rotAngMom_N[:, 3] - rotAngMom_N[0, 3]))
plt.title('Rotational Angular Momentum Difference', fontsize=14)
plt.ylabel('(Nms)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.figure()
plt.clf()
plt.plot(rotEnergy[:, 0] * macros.NANO2SEC, (rotEnergy[:, 1] - rotEnergy[0, 1]))
plt.title('Total Energy Difference', fontsize=14)
plt.ylabel('Energy (J)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
if show_plots:
plt.show()
plt.close("all")
# Begin the test analysis
finalOrbAngMom = np.delete(finalOrbAngMom, 0, axis=1) # remove the time column
finalRotAngMom = np.delete(finalRotAngMom, 0, axis=1) # remove the time column
finalOrbEnergy = np.delete(finalOrbEnergy, 0, axis=1) # remove the time column
for i in range(0, len(initialOrbAngMom_N)):
if not unitTestSupport.isArrayEqualRelative(finalOrbAngMom[i], initialOrbAngMom_N[i], 3, accuracy):
testFailCount += 1
testMessages.append(
"FAILED: Prescribed Motion integrated test failed orbital angular momentum unit test")
for i in range(0, len(initialRotAngMom_N)):
if not unitTestSupport.isArrayEqual(finalRotAngMom[i], initialRotAngMom_N[i], 3, accuracy):
testFailCount += 1
testMessages.append(
"FAILED: Prescribed Motion integrated test failed rotational angular momentum unit test")
for i in range(0, len(initialOrbEnergy)):
if not unitTestSupport.isArrayEqualRelative(finalOrbEnergy[i], initialOrbEnergy[i], 1, accuracy):
testFailCount += 1
testMessages.append("FAILED: Prescribed Motion integrated test failed orbital energy unit test")
# Check to ensure the initial angle rate converged to the reference angle rate
if not unitTestSupport.isDoubleEqual(thetaDot_Final, thetaDot_Ref, accuracy):
testFailCount += 1
testMessages.append("FAILED: " + PrescribedRot1DOF.ModelTag + "thetaDot_Final and thetaDot_Ref do not match")
# Check to ensure the initial angle converged to the reference angle
if not unitTestSupport.isDoubleEqual(theta_FM_Final, theta_Ref, accuracy):
testFailCount += 1
testMessages.append("FAILED: " + PrescribedRot1DOF.ModelTag + "theta_FM_Final and theta_Ref do not match")
# testMessages.append("theta_FM_Final: " + str(theta_FM_Final) + " theta_Ref: " + str(theta_Ref))
if testFailCount == 0:
print("PASSED: " + "prescribedMotion and prescribedRot1DOF integrated test")
else:
# ** ** ** ** ** TRANSLATIONAL INTEGRATED TEST ** ** ** ** **
# Create an instance of the prescribedLinearTranslation module to be tested
PrescribedTrans = prescribedLinearTranslation.PrescribedLinearTranslation()
PrescribedTrans.ModelTag = "prescribedLinearTranslation"
# Add the prescribedLinearTranslation test module to runtime call list
unitTestSim.AddModelToTask(unitTaskName, PrescribedTrans)
# Initialize the prescribedLinearTranslation test module configuration data
accelMax = 0.005 # [m/s^2]
PrescribedTrans.setTransHat_M(transAxis_M)
PrescribedTrans.setTransAccelMax(accelMax)
PrescribedTrans.setTransPosInit(posInit)
# Create the prescribedTrans input message
velRef = 0.0 # [m/s]
linearTranslationRigidBodyMessageData = messaging.LinearTranslationRigidBodyMsgPayload()
linearTranslationRigidBodyMessageData.rho = posRef
linearTranslationRigidBodyMessageData.rhoDot = velRef
linearTranslationRigidBodyMessage = messaging.LinearTranslationRigidBodyMsg().write(linearTranslationRigidBodyMessageData)
PrescribedTrans.linearTranslationRigidBodyInMsg.subscribeTo(linearTranslationRigidBodyMessage)
platform.prescribedTranslationInMsg.subscribeTo(PrescribedTrans.prescribedTranslationOutMsg)
# Add Earth gravity to the simulation
earthGravBody = gravityEffector.GravBodyData()
earthGravBody.planetName = "earth_planet_data"
earthGravBody.mu = 0.3986004415E+15
earthGravBody.isCentralBody = True
scObject.gravField.gravBodies = spacecraft.GravBodyVector([earthGravBody])
# Add energy and momentum variables to log
scObjectLog = scObject.logger(["totOrbEnergy", "totOrbAngMomPntN_N", "totRotAngMomPntC_N", "totRotEnergy"])
unitTestSim.AddModelToTask(unitTaskName, scObjectLog)
# Add other states to log
scStateData = scObject.scStateOutMsg.recorder()
prescribedTransStateData = platform.prescribedTranslationOutMsg.recorder()
unitTestSim.AddModelToTask(unitTaskName, scStateData)
unitTestSim.AddModelToTask(unitTaskName, prescribedTransStateData)
# Initialize the simulation
unitTestSim.InitializeSimulation()
# Set the simulation time
simTime = np.sqrt(((0.5 * np.abs(posRef - posInit)) * 8) / accelMax) + 3 * testIncrement
unitTestSim.ConfigureStopTime(macros.sec2nano(simTime))
# Begin the simulation
unitTestSim.ExecuteSimulation()
# Extract the logged data
orbEnergy = unitTestSupport.addTimeColumn(scObjectLog.times(), scObjectLog.totOrbEnergy)
orbAngMom_N = unitTestSupport.addTimeColumn(scObjectLog.times(), scObjectLog.totOrbAngMomPntN_N)
rotAngMom_N = unitTestSupport.addTimeColumn(scObjectLog.times(), scObjectLog.totRotAngMomPntC_N)
rotEnergy = unitTestSupport.addTimeColumn(scObjectLog.times(), scObjectLog.totRotEnergy)
r_BN_N = scStateData.r_BN_N
sigma_BN = scStateData.sigma_BN
omega_BN_B = scStateData.omega_BN_B
r_FM_M = prescribedTransStateData.r_FM_M
rPrime_FM_M = prescribedTransStateData.rPrime_FM_M
rPrimePrime_FM_M = prescribedTransStateData.rPrimePrime_FM_M
timespan = prescribedTransStateData.times()
r_FM_M_Final = r_FM_M[-1, :]
rPrime_FM_M_Final = rPrime_FM_M[-1, :]
# Setup the conservation quantities
initialOrbAngMom_N = [[orbAngMom_N[0, 1], orbAngMom_N[0, 2], orbAngMom_N[0, 3]]]
finalOrbAngMom = [orbAngMom_N[-1]]
initialRotAngMom_N = [[rotAngMom_N[0, 1], rotAngMom_N[0, 2], rotAngMom_N[0, 3]]]
finalRotAngMom = [rotAngMom_N[-1]]
initialOrbEnergy = [[orbEnergy[0, 1]]]
finalOrbEnergy = [orbEnergy[-1]]
initialRotEnergy = [[rotEnergy[0, 1]]]
finalRotEnergy = [rotEnergy[-1]]
# Plot r_FM_F
r_FM_M_Ref = posRef * transAxis_M
r_FM_M_1_Ref = np.ones(len(timespan)) * r_FM_M_Ref[0]
r_FM_M_2_Ref = np.ones(len(timespan)) * r_FM_M_Ref[1]
r_FM_M_3_Ref = np.ones(len(timespan)) * r_FM_M_Ref[2]
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, r_FM_M[:, 0], label=r'$r_{1}$')
plt.plot(timespan * macros.NANO2SEC, r_FM_M[:, 1], label=r'$r_{2}$')
plt.plot(timespan * macros.NANO2SEC, r_FM_M[:, 2], label=r'$r_{3}$')
plt.plot(timespan * macros.NANO2SEC, r_FM_M_1_Ref, '--', label=r'$r_{1 Ref}$')
plt.plot(timespan * macros.NANO2SEC, r_FM_M_2_Ref, '--', label=r'$r_{2 Ref}$')
plt.plot(timespan * macros.NANO2SEC, r_FM_M_3_Ref, '--', label=r'$r_{3 Ref}$')
plt.title(r'${}^\mathcal{M} r_{\mathcal{F}/\mathcal{M}}$ Profiled Trajectory', fontsize=14)
plt.ylabel('(m)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='center left', prop={'size': 16})
# Plot rPrime_FM_F
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, rPrime_FM_M[:, 0], label='1')
plt.plot(timespan * macros.NANO2SEC, rPrime_FM_M[:, 1], label='2')
plt.plot(timespan * macros.NANO2SEC, rPrime_FM_M[:, 2], label='3')
plt.title(r'${}^\mathcal{M} rPrime_{\mathcal{F}/\mathcal{M}}$ Profiled Trajectory', fontsize=14)
plt.ylabel('(m/s)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='upper left', prop={'size': 16})
# Plotting rPrimePrime_FM_M
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * rPrimePrime_FM_M[:, 0], label='1')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * rPrimePrime_FM_M[:, 1], label='2')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * rPrimePrime_FM_M[:, 2], label='3')
plt.title(r'${}^\mathcal{M} rPrimePrime_{\mathcal{F}/\mathcal{M}}$ Profiled Acceleration', fontsize=14)
plt.ylabel(r'(m/s$^2$)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='lower left', prop={'size': 16})
# Plot r_BN_N
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, r_BN_N[:, 0], label=r'$r_{1}$')
plt.plot(timespan * macros.NANO2SEC, r_BN_N[:, 1], label=r'$r_{2}$')
plt.plot(timespan * macros.NANO2SEC, r_BN_N[:, 2], label=r'$r_{3}$')
plt.title(r'${}^\mathcal{N} r_{\mathcal{B}/\mathcal{N}}$ Spacecraft Inertial Trajectory', fontsize=14)
plt.ylabel('(m)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='center left', prop={'size': 16})
# Plot sigma_BN
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, sigma_BN[:, 0], label=r'$\sigma_{1}$')
plt.plot(timespan * macros.NANO2SEC, sigma_BN[:, 1], label=r'$\sigma_{2}$')
plt.plot(timespan * macros.NANO2SEC, sigma_BN[:, 2], label=r'$\sigma_{3}$')
plt.title(r'$\sigma_{\mathcal{B}/\mathcal{N}}$ Spacecraft Inertial MRP Attitude', fontsize=14)
plt.ylabel('', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='lower left', prop={'size': 16})
# Plot omega_BN_B
plt.figure()
plt.clf()
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omega_BN_B[:, 0], label=r'$\omega_{1}$')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omega_BN_B[:, 1], label=r'$\omega_{2}$')
plt.plot(timespan * macros.NANO2SEC, (180 / np.pi) * omega_BN_B[:, 2], label=r'$\omega_{3}$')
plt.title(r'Spacecraft Hub Angular Velocity ${}^\mathcal{B} \omega_{\mathcal{B}/\mathcal{N}}$', fontsize=14)
plt.ylabel('(deg/s)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.legend(loc='lower left', prop={'size': 16})
# Plotting: Conservation quantities
plt.figure()
plt.clf()
plt.plot(orbAngMom_N[:, 0] * macros.NANO2SEC, (orbAngMom_N[:, 1] - orbAngMom_N[0, 1]) / orbAngMom_N[0, 1],
orbAngMom_N[:, 0] * macros.NANO2SEC, (orbAngMom_N[:, 2] - orbAngMom_N[0, 2]) / orbAngMom_N[0, 2],
orbAngMom_N[:, 0] * macros.NANO2SEC, (orbAngMom_N[:, 3] - orbAngMom_N[0, 3]) / orbAngMom_N[0, 3])
plt.title('Orbital Angular Momentum Relative Difference', fontsize=14)
plt.ylabel('(Nms)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.figure()
plt.clf()
plt.plot(orbEnergy[:, 0] * macros.NANO2SEC, (orbEnergy[:, 1] - orbEnergy[0, 1]) / orbEnergy[0, 1])
plt.title('Orbital Energy Relative Difference', fontsize=14)
plt.ylabel('Energy (J)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.figure()
plt.clf()
plt.plot(rotAngMom_N[:, 0] * macros.NANO2SEC, (rotAngMom_N[:, 1] - rotAngMom_N[0, 1]),
rotAngMom_N[:, 0] * macros.NANO2SEC, (rotAngMom_N[:, 2] - rotAngMom_N[0, 2]),
rotAngMom_N[:, 0] * macros.NANO2SEC, (rotAngMom_N[:, 3] - rotAngMom_N[0, 3]))
plt.title('Rotational Angular Momentum Difference', fontsize=14)
plt.ylabel('(Nms)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
plt.figure()
plt.clf()
plt.plot(rotEnergy[:, 0] * macros.NANO2SEC, (rotEnergy[:, 1] - rotEnergy[0, 1]))
plt.title('Total Energy Difference', fontsize=14)
plt.ylabel('Energy (J)', fontsize=16)
plt.xlabel('Time (s)', fontsize=16)
if show_plots:
plt.show()
plt.close("all")
# Begin the test analysis
finalOrbAngMom = np.delete(finalOrbAngMom, 0, axis=1) # remove the time column
finalRotAngMom = np.delete(finalRotAngMom, 0, axis=1) # remove the time column
finalOrbEnergy = np.delete(finalOrbEnergy, 0, axis=1) # remove the time column
for i in range(0, len(initialOrbAngMom_N)):
if not unitTestSupport.isArrayEqualRelative(finalOrbAngMom[i], initialOrbAngMom_N[i], 3, accuracy):
testFailCount += 1
testMessages.append(
"FAILED: Prescribed Motion integrated test failed orbital angular momentum unit test")
for i in range(0, len(initialRotAngMom_N)):
if not unitTestSupport.isArrayEqual(finalRotAngMom[i], initialRotAngMom_N[i], 3, accuracy):
testFailCount += 1
testMessages.append(
"FAILED: Prescribed Motion integrated test failed rotational angular momentum unit test")
for i in range(0, len(initialOrbEnergy)):
if not unitTestSupport.isArrayEqualRelative(finalOrbEnergy[i], initialOrbEnergy[i], 1, accuracy):
testFailCount += 1
testMessages.append("FAILED: Prescribed Motion integrated test failed orbital energy unit test")
# Check to ensure the initial velocity converged to the reference velocity
rPrime_FM_M_Ref = np.array([0.0, 0.0, 0.0])
if not unitTestSupport.isArrayEqual(rPrime_FM_M_Final, rPrime_FM_M_Ref, 3, accuracy):
testFailCount += 1
testMessages.append("FAILED: " + PrescribedTrans.ModelTag + "rPrime_FM_M_Final and rPrime_FM_M_Ref do not match")
testMessages.append("rPrime_FM_M_Final: " + str(rPrime_FM_M_Final) + " rPrime_FM_M_Ref: " + str(rPrime_FM_M_Ref))
# Check to ensure the initial position converged to the reference position
r_FM_M_Ref = np.array([posRef, 0.0, 0.0])
if not unitTestSupport.isArrayEqual(r_FM_M_Final, r_FM_M_Ref, 3, accuracy):
testFailCount += 1
testMessages.append("FAILED: " + PrescribedTrans.ModelTag + "r_FM_M_Final and r_FM_M_Ref do not match")
testMessages.append("r_FM_M_Final: " + str(r_FM_M_Final) + " r_FM_M_Ref: " + str(r_FM_M_Ref))
if testFailCount == 0:
print("PASSED: " + "prescribedMotion and prescribedLinearTranslation integrated test")
return [testFailCount, ''.join(testMessages)]
#
# This statement below ensures that the unitTestScript can be run as a
# stand-along python script
#
if __name__ == "__main__":
PrescribedMotionTestFunction(
True, # show_plots
False, # rotTest, (True: prescribedRot1DOF integrated test,
# False: prescribedTrans integrated test)
0.0, # thetaInit [rad]
np.pi / 12, # theta_Ref [rad]
0.0, # posInit [m]
0.5, # posRef [m]
1e-8 # accuracy
)