#
# 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.
#
r"""
.. raw:: html
<iframe width="560" height="315" src="https://www.youtube.com/embed/hkeL50pq0L0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Overview
--------
This script sets up a 3-DOF spacecraft which is orbiting Earth. The purpose
is to illustrate how to start and stop the Basilisk simulation to apply
some :math:`\Delta v`'s for simple orbit maneuvers. Read :ref:`scenarioBasicOrbit`
to learn how to setup an orbit simulation.
The script is found in the folder ``basilisk/examples`` and executed by using::
python3 scenarioOrbitManeuver.py
The simulation layout is shown in the following illustration. A single simulation process is created
which contains the spacecraft object. The BSK simulation is run for a fixed period. After stopping, the
states are changed and the simulation is resumed.
.. image:: /_images/static/test_scenarioOrbitManeuver.svg
:align: center
When the simulation completes 2 plots are shown for each case. One plot always shows
the inertial position vector components, while the second plot either shows a plot
of the radius time history (Hohmann maneuver), or the
inclination angle time history plot (Inclination change maneuver).
Illustration of Simulation Results
----------------------------------
The following images illustrate the expected simulation run returns for a range of script configurations.
::
show_plots = True, maneuverCase = 0
In this case a classical Hohmann transfer is being
simulated to go from LEO to reach and stay at GEO. The math behind such maneuvers can be found
in textbooks such as `Analytical Mechanics of Space Systems <http://arc.aiaa.org/doi/book/10.2514/4.102400>`__.
.. image:: /_images/Scenarios/scenarioOrbitManeuver10.svg
:align: center
.. image:: /_images/Scenarios/scenarioOrbitManeuver20.svg
:align: center
::
show_plots = True, maneuverCase = 1
In this case a classical plane change is being
simulated to go rotate the orbit plane first 8 degrees, then another 4 degrees after
orbiting 90 degrees. The math behind such maneuvers can be found
in textbooks such as `Analytical Mechanics of Space Systems
<http://arc.aiaa.org/doi/book/10.2514/4.102400>`__.
.. image:: /_images/Scenarios/scenarioOrbitManeuver11.svg
:align: center
.. image:: /_images/Scenarios/scenarioOrbitManeuver21.svg
:align: center
"""
#
# Basilisk Scenario Script and Integrated Test
#
# Purpose: Integrated test of the spacecraft() and gravity modules illustrating
# how impulsive Delta_v maneuver can be simulated with stopping and starting the
# simulation.
# Author: Hanspeter Schaub
# Creation Date: Nov. 26, 2016
#
import math
import os
import matplotlib.pyplot as plt
import numpy as np
# The path to the location of Basilisk
# Used to get the location of supporting data.
from Basilisk import __path__
from Basilisk.simulation import spacecraft
from Basilisk.utilities import SimulationBaseClass
from Basilisk.utilities import macros
from Basilisk.utilities import orbitalMotion
from Basilisk.utilities import simIncludeGravBody
from Basilisk.utilities import unitTestSupport # general support file with common unit test functions
# attempt to import vizard
from Basilisk.utilities import vizSupport
bskPath = __path__[0]
fileName = os.path.basename(os.path.splitext(__file__)[0])
[docs]
def run(show_plots, maneuverCase):
"""
The scenarios can be run with the followings setups parameters:
Args:
show_plots (bool): Determines if the script should display plots
maneuverCase (int):
====== ============================
Int Definition
====== ============================
0 Hohmann maneuver
1 Inclination change maneuver
====== ============================
"""
# Create simulation variable names
simTaskName = "simTask"
simProcessName = "simProcess"
# Create a sim module as an empty container
scSim = SimulationBaseClass.SimBaseClass()
#
# create the simulation process
#
dynProcess = scSim.CreateNewProcess(simProcessName)
# create the dynamics task and specify the integration update time
simulationTimeStep = macros.sec2nano(10.)
dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep))
#
# setup the simulation tasks/objects
#
# initialize spacecraft object and set properties
scObject = spacecraft.Spacecraft()
scObject.ModelTag = "spacecraftBody"
# add spacecraft object to the simulation process
scSim.AddModelToTask(simTaskName, scObject)
# setup Gravity Body and SPICE definitions
gravFactory = simIncludeGravBody.gravBodyFactory()
earth = gravFactory.createEarth()
earth.isCentralBody = True # ensure this is the central gravitational body
# attach gravity model to spacecraft
gravFactory.addBodiesTo(scObject)
# setup spice library for Earth ephemeris
timeInitString = "2024 September 21, 21:00:00.0 TDB"
spiceObject = gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True)
spiceObject.zeroBase = 'Earth'
# need spice to run before spacecraft module as it provides the spacecraft translational states
scSim.AddModelToTask(simTaskName, spiceObject)
#
# setup orbit and simulation time
#
# setup the orbit using classical orbit elements
oe = orbitalMotion.ClassicElements()
rLEO = 7000. * 1000 # meters
rGEO = math.pow(earth.mu / math.pow((2. * np.pi) / (24. * 3600.), 2), 1. / 3.)
oe.a = rLEO
oe.e = 0.0001
oe.i = 0.0 * macros.D2R
oe.Omega = 48.2 * macros.D2R
oe.omega = 347.8 * macros.D2R
oe.f = 85.3 * macros.D2R
rN, vN = orbitalMotion.elem2rv(earth.mu, oe)
scObject.hub.r_CN_NInit = rN # m - r_CN_N
scObject.hub.v_CN_NInit = vN # m - v_CN_N
# set the simulation time
n = np.sqrt(earth.mu / oe.a / oe.a / oe.a)
P = 2. * np.pi / n
simulationTime = macros.sec2nano(0.25 * P)
#
# Setup data logging before the simulation is initialized
#
numDataPoints = 100
samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints)
dataRec = scObject.scStateOutMsg.recorder(samplingTime)
scSim.AddModelToTask(simTaskName, dataRec)
if vizSupport.vizFound:
# if this scenario is to interface with the BSK Viz, uncomment the following lines
viz = vizSupport.enableUnityVisualization(scSim, simTaskName, scObject
, oscOrbitColorList=[vizSupport.toRGBA255("yellow")]
, trueOrbitColorList=[vizSupport.toRGBA255("turquoise")]
# , saveFile=fileName
)
viz.settings.mainCameraTarget = "earth"
viz.settings.trueTrajectoryLinesOn = 1
#
# initialize Simulation
#
scSim.InitializeSimulation()
#
# get access to dynManager translational states for future access to the states
#
posRef = scObject.dynManager.getStateObject(scObject.hub.nameOfHubPosition)
velRef = scObject.dynManager.getStateObject(scObject.hub.nameOfHubVelocity)
# The dynamics simulation is setup using a Spacecraft() module with the Earth's
# gravity module attached. Note that the rotational motion simulation is turned off to leave
# pure 3-DOF translation motion simulation. After running the simulation for 1/4 of a period
# the simulation is stopped to apply impulsive changes to the inertial velocity vector.
scSim.ConfigureStopTime(simulationTime)
scSim.ExecuteSimulation()
# Next, the state manager objects are called to retrieve the latest inertial position and
# velocity vector components:
rVt = unitTestSupport.EigenVector3d2np(posRef.getState())
vVt = unitTestSupport.EigenVector3d2np(velRef.getState())
# compute maneuver Delta_v's
if maneuverCase == 1:
# inclination change
Delta_i = 8.0 * macros.D2R
rHat = rVt / np.linalg.norm(rVt)
hHat = np.cross(rVt, vVt)
hHat = hHat / np.linalg.norm(hHat)
vHat = np.cross(hHat, rHat)
v0 = np.dot(vHat, vVt)
vVt = vVt - (1. - np.cos(Delta_i)) * v0 * vHat + np.sin(Delta_i) * v0 * hHat
# After computing the maneuver specific Delta_v's, the state managers velocity is updated through
velRef.setState(vVt)
T2 = macros.sec2nano(P * 0.25)
else:
# Hohmann Transfer to GEO
v0 = np.linalg.norm(vVt)
r0 = np.linalg.norm(rVt)
at = (r0 + rGEO) * .5
v0p = np.sqrt(earth.mu / at * rGEO / r0)
n1 = np.sqrt(earth.mu / at / at / at)
T2 = macros.sec2nano((np.pi) / n1)
vHat = vVt / v0
vVt = vVt + vHat * (v0p - v0)
# After computing the maneuver specific Delta_v's, the state managers velocity is updated through
velRef.setState(vVt)
# To start up the simulation again, note that the total simulation time must be provided,
# not just the next incremental simulation time.
scSim.ConfigureStopTime(simulationTime + T2)
scSim.ExecuteSimulation()
# This process is then repeated for the second maneuver.
# get the current spacecraft states
rVt = unitTestSupport.EigenVector3d2np(posRef.getState())
vVt = unitTestSupport.EigenVector3d2np(velRef.getState())
# compute maneuver Delta_v's
if maneuverCase == 1:
# inclination change
Delta_i = 4.0 * macros.D2R
rHat = rVt / np.linalg.norm(rVt)
hHat = np.cross(rVt, vVt)
hHat = hHat / np.linalg.norm(hHat)
vHat = np.cross(hHat, rHat)
v0 = np.dot(vHat, vVt)
vVt = vVt - (1. - np.cos(Delta_i)) * v0 * vHat + np.sin(Delta_i) * v0 * hHat
velRef.setState(vVt)
T3 = macros.sec2nano(P * 0.25)
else:
# Hohmann Transfer to GEO
v1 = np.linalg.norm(vVt)
v1p = np.sqrt(earth.mu / rGEO)
n1 = np.sqrt(earth.mu / rGEO / rGEO / rGEO)
T3 = macros.sec2nano(0.25 * (np.pi) / n1)
vHat = vVt / v1
vVt = vVt + vHat * (v1p - v1)
velRef.setState(vVt)
# run simulation for 3rd chunk
scSim.ConfigureStopTime(simulationTime + T2 + T3)
scSim.ExecuteSimulation()
# unload Spice kernel
gravFactory.unloadSpiceKernels()
#
# retrieve the logged data
#
posData = dataRec.r_BN_N
velData = dataRec.v_BN_N
np.set_printoptions(precision=16)
#
# plot the results
#
# draw the inertial position vector components
plt.close("all") # clears out plots from earlier test runs
plt.figure(1)
fig = plt.gcf()
ax = fig.gca()
ax.ticklabel_format(useOffset=False, style='plain')
for idx in range(3):
plt.plot(dataRec.times() * macros.NANO2HOUR, posData[:, idx] / 1000.,
color=unitTestSupport.getLineColor(idx, 3),
label='$r_{BN,' + str(idx) + '}$')
plt.legend(loc='lower right')
plt.xlabel('Time [h]')
plt.ylabel('Inertial Position [km]')
figureList = {}
pltName = fileName + "1" + str(int(maneuverCase))
figureList[pltName] = plt.figure(1)
if maneuverCase == 1:
# show inclination angle
plt.figure(2)
fig = plt.gcf()
ax = fig.gca()
ax.ticklabel_format(useOffset=False, style='plain')
iData = []
for idx in range(0, len(posData)):
oeData = orbitalMotion.rv2elem(earth.mu, posData[idx], velData[idx])
iData.append(oeData.i * macros.R2D)
plt.plot(dataRec.times() * macros.NANO2HOUR, np.ones(len(posData[:, 0])) * 8.93845, '--', color='#444444'
)
plt.plot(dataRec.times() * macros.NANO2HOUR, iData, color='#aa0000'
)
plt.ylim([-1, 10])
plt.xlabel('Time [h]')
plt.ylabel('Inclination [deg]')
else:
# show SMA
plt.figure(2)
fig = plt.gcf()
ax = fig.gca()
ax.ticklabel_format(useOffset=False, style='plain')
rData = []
for idx in range(0, len(posData)):
oeData = orbitalMotion.rv2elem_parab(earth.mu, posData[idx], velData[idx])
rData.append(oeData.rmag / 1000.)
plt.plot(dataRec.times() * macros.NANO2HOUR, rData, color='#aa0000',
)
plt.xlabel('Time [h]')
plt.ylabel('Radius [km]')
pltName = fileName + "2" + str(int(maneuverCase))
figureList[pltName] = plt.figure(2)
if show_plots:
plt.show()
# close the plots being saved off to avoid over-writing old and new figures
plt.close("all")
# each test method requires a single assert method to be called
# this check below just makes sure no sub-test failures were found
dataPos = posRef.getState()
dataPos = [[0.0, dataPos[0][0], dataPos[1][0], dataPos[2][0]]]
return figureList
#
# This statement below ensures that the unit test scrip can be run as a
# stand-along python script
#
if __name__ == "__main__":
run(
True, # show_plots
0 # Maneuver Case (0 - Hohmann, 1 - Inclination)
)