Source code for scenarioDragRendezvous

#
#  ISC License
#
#  Copyright (c) 2021, 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"""
Overview
--------

This script sets up a formation flying scenario with two spacecraft. The deputy spacecraft attempts to rendezvous with the
chief using attitude-driven differential drag using the strategy outlined in `this paper <https://arc.aiaa.org/doi/10.2514/1.G004521>`_.

This script is found in the folder ``src/examples`` and executed by using::

      python3 scenarioDragRendezvous

The simulation layout is shown in the following illustration. Two spacecraft are orbiting the earth at
close distance. Perturbations from atmospheric drag, provided by :ref:`exponentialAtmosphere` and
:ref:`facetDragDynamicEffector`,  are implemented by default; the :math:`J_2` gravity perturbation
can also be included. Each spacecraft sends a :ref:`simpleNav`
output message of type :ref:`NavAttMsgPayload` continuously to a :ref:`hillStateConverter` module,
which is assumed to be a part of the deputy (maneuvering)
spacecraft's flight software stack. The :ref:`hillStateConverter` module then writes
a :ref:`hillRelStateMsgPayload`, which is read by the :ref:`hillToAttRef` module implementing
the differential drag attitude guidance law. 

.. image:: /_images/static/scenarioDragRendezvousDiagram.png
   :align: center


Illustration of Simulation Results
----------------------------------

::

        0.0, #   altitude offset (m)
        0.1, #  True anomaly offset (deg)
        1, #    Density multiplier (non-dimensional)
        ctrlType='lqr',
        useJ2=False

In this case, the deputy spacecraft attempts to catch up to a reference set ten kilometers ahead of it
along-track using a static LQR control law, without considering the impact of :math:`J_2` perturbations.
The resulting relative attitude and in-plane Hill trajectory are shown below.


.. image:: /_images/Scenarios/scenarioDragRendezvous_relativeAtt.svg
   :align: center

.. image:: /_images/Scenarios/scenarioDragRendezvous_hillTraj.svg
   :align: center

Time trajectories of the in-plane Hill components of the Deputy are shown here:

.. image:: /_images/Scenarios/scenarioDragRendezvous_hillX.svg
   :align: center

.. image:: /_images/Scenarios/scenarioDragRendezvous_hillY.svg
   :align: center
"""

import os
import time

import matplotlib.pyplot as plt
import numpy as np
from Basilisk import __path__
from Basilisk.fswAlgorithms import hillStateConverter, hillToAttRef, hillPoint
from Basilisk.simulation import spacecraft, facetDragDynamicEffector, simpleNav, exponentialAtmosphere
from Basilisk.utilities import RigidBodyKinematics as rbk
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
from Basilisk.utilities import vizSupport

bskPath = __path__[0]
fileName = os.path.basename(os.path.splitext(__file__)[0])


#   Declare some linearized drag HCW dynamics
drag_state_dynamics = [[0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,1.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,1.000000000000000000e+00,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00],
                     [4.134628279603025589e-06,0.000000000000000000e+00,0.000000000000000000e+00,-7.178791202675993545e-10,2.347943292785702706e-03,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,-2.347943292785702706e-03,-1.435758240535198709e-09,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00]]
drag_ctrl_effects = [[0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,4.1681080996120926e-05],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00]
                     ]
drag_sens_effects = [[0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,-7.178791202675151888e-10,0.000000000000000000e+00,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,-1.435758240535030378e-09,0.000000000000000000e+00],
                     [0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00]]
drag_R_inv = [[1e-8,0,0],
              [0,1e-8,0],
              [0,0,1e-8]]
#   Static LQR gain
path = os.path.dirname(os.path.abspath(__file__))
dataFileName = os.path.join(path, "dataForExamples", "static_lqr_controlGain.npz")
lqr_gain_set = np.load(dataFileName)['arr_0.npy']

fileName = os.path.basename(os.path.splitext(__file__)[0])

[docs]def setup_spacecraft_plant(rN, vN, modelName): """ Convenience function to set up a spacecraft and its associated dragEffector and simpleNav module. Args: rN (float(3)): Inertial position vector vN (float(3)): Inertial velocity vector modelName (string): String specifying the spacecraft name """ scObject = spacecraft.Spacecraft() scObject.ModelTag = modelName scObject.hub.mHub = 6.0 scObject.hub.r_BcB_B = [[0.0], [0.0], [0.0]] I = [10., 0., 0., 0., 9., 0., 0., 0., 8.] scObject.hub.IHubPntBc_B = unitTestSupport.np2EigenMatrix3d(I) scObject.hub.r_CN_NInit = rN scObject.hub.v_CN_NInit = vN scNav = simpleNav.SimpleNav() scNav.ModelTag = modelName+'_navigator' scNav.scStateInMsg.subscribeTo(scObject.scStateOutMsg) dragArea = 0.3*0.2 dragCoeff = 2.2 normalVector = -rbk.euler3(np.radians(45.)).dot(np.array([0,-1,0])) panelLocation = [0,0,0] dragEffector = facetDragDynamicEffector.FacetDragDynamicEffector() dragEffector.ModelTag = modelName+'_dragEffector' dragEffector.addFacet(dragArea, dragCoeff, normalVector, panelLocation) scObject.addDynamicEffector(dragEffector) return scObject, dragEffector, scNav
[docs]def drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', useJ2=False): """ Basilisk simulation of a two-spacecraft rendezvous using relative-attitude driven differential drag. Includes both static gain and desensitized time-varying gain options and the option to use simulated attitude control or direct reference inputs. Args: altOffset - double - deputy altitude offset from the chief ('x' hill direction), meters trueAnomOffset - double - deputy true anomaly difference from the chief ('y' direction), degrees """ startTime = time.time() print(f"Starting process execution for altOffset = {altOffset}, trueAnomOffset={trueAnomOffset}, densMultiplier={densMultiplier} with {ctrlType} controls...") scSim = SimulationBaseClass.SimBaseClass() # Configure simulation container and timestep simProcessName = "simProcess" dynTaskName = "dynTask" fswTaskName = "fswTask" simProcess = scSim.CreateNewProcess(simProcessName, 2) dynTimeStep = macros.sec2nano(60.0) # Timestep to evaluate dynamics at fswTimeStep = macros.sec2nano(60.0) # Timestep to evaluate FSW at simProcess.addTask(scSim.CreateNewTask(dynTaskName, dynTimeStep)) simProcess.addTask(scSim.CreateNewTask(fswTaskName, fswTimeStep)) ## Configure environmental parameters # Gravity; includes 2-body plus J2. gravFactory = simIncludeGravBody.gravBodyFactory() earth = gravFactory.createEarth() earth.isCentralBody = True mu = earth.mu if useJ2: earth.useSphericalHarmonicsGravityModel(bskPath + '/supportData/LocalGravData/GGM03S.txt', 2) # timeInitString = '2021 MAY 04 07:47:48.965 (UTC)' # spiceObject = gravFactory.createSpiceInterface(time=timeInitString) # spiceObject.zeroBase = 'earth' # Density atmosphere = exponentialAtmosphere.ExponentialAtmosphere() atmosphere.ModelTag = 'atmosphere' # atmosphere.planetPosInMsg.subscribeTo(spiceObject.planetStateOutMsgs[0]) atmosphere.planetRadius = orbitalMotion.REQ_EARTH*1e3 + 300e3 # m atmosphere.envMinReach = -300e3 atmosphere.envMaxReach = +300e3 atmosphere.scaleHeight = 8.0e3 # m atmosphere.baseDensity = 2.022E-14 * 1000 * densMultiplier # kg/m^3 ## Set up chief, deputy orbits: chief_oe = orbitalMotion.ClassicElements() chief_oe.a = orbitalMotion.REQ_EARTH * 1e3 + 300e3 # meters chief_oe.e = 0. chief_oe.i = np.radians(45.) chief_oe.Omega = np.radians(20.0) chief_oe.omega = np.radians(30.) chief_oe.f = np.radians(20.) chief_rN, chief_vN = orbitalMotion.elem2rv(mu, chief_oe) dep_oe = orbitalMotion.ClassicElements() dep_oe.a = orbitalMotion.REQ_EARTH * 1e3 + 300e3 + (altOffset) # meters dep_oe.e = 0. dep_oe.i = np.radians(45.) dep_oe.Omega = np.radians(20.0) dep_oe.omega = np.radians(30.) dep_oe.f = np.radians(20. - trueAnomOffset) dep_rN, dep_vN = orbitalMotion.elem2rv(mu, dep_oe) # Initialize s/c dynamics, drag, navigation solutions chiefSc, chiefDrag, chiefNav = setup_spacecraft_plant(chief_rN, chief_vN, 'wiggum') depSc, depDrag, depNav = setup_spacecraft_plant(dep_rN,dep_vN, 'lou') # Connect s/c to environment (gravity, density) gravFactory.addBodiesTo(chiefSc) gravFactory.addBodiesTo(depSc) atmosphere.addSpacecraftToModel(depSc.scStateOutMsg) depDrag.atmoDensInMsg.subscribeTo(atmosphere.envOutMsgs[-1]) atmosphere.addSpacecraftToModel(chiefSc.scStateOutMsg) chiefDrag.atmoDensInMsg.subscribeTo(atmosphere.envOutMsgs[-1]) # Add all dynamics stuff to dynamics task scSim.AddModelToTask(dynTaskName, atmosphere,920) # scSim.AddModelToTask(dynTaskName, ephemConverter, 921) scSim.AddModelToTask(dynTaskName, chiefDrag, 890) scSim.AddModelToTask(dynTaskName, depDrag, 891) scSim.AddModelToTask(dynTaskName, chiefSc, 810) scSim.AddModelToTask(dynTaskName, depSc, 811) scSim.AddModelToTask(dynTaskName, chiefNav, 800) scSim.AddModelToTask(dynTaskName, depNav, 801) # scSim.AddModelToTask(dynTaskName, spiceObject, 999) ## FSW setup # Chief S/C # hillPoint - set such that the chief attitude follows its hill frame. chiefAttRef = hillPoint.hillPoint() chiefAttRef.ModelTag = 'chief_att_ref' chiefAttRef.transNavInMsg.subscribeTo(chiefNav.transOutMsg) # chiefAttRefData.celBodyInMsg.subscribeTo(ephemConverter.ephemOutMsgs[-1]) # We shouldn't need this because the planet is the origin chiefSc.attRefInMsg.subscribeTo(chiefAttRef.attRefOutMsg) # Force the chief spacecraft to follow the hill direction depHillRef = hillPoint.hillPoint() depHillRef.ModelTag = 'dep_hill_ref' depHillRef.transNavInMsg.subscribeTo(depNav.transOutMsg) # chiefAttRefData.celBodyInMsg.subscribeTo(ephemConverter.ephemOutMsgs[-1]) # We shouldn't need this because the planet is the origin #chiefSc.attRefInMsg.subscribeTo(chiefAttRefData.attRefOutMsg) # Force the chief spacecraft to follow the hill direction # hillStateConverter hillStateNavObj = hillStateConverter.hillStateConverter() hillStateNavObj.ModelTag = "dep_hillStateNav" hillStateNavObj.depStateInMsg.subscribeTo(depNav.transOutMsg) hillStateNavObj.chiefStateInMsg.subscribeTo(chiefNav.transOutMsg) # hillToAtt guidance law w/ static gain depAttRef = hillToAttRef.hillToAttRef() depAttRef.ModelTag = 'dep_att_ref' depAttRef.gainMatrix = hillToAttRef.MultiArray(lqr_gain_set) # Configure parameters common to relative attitude guidance modules depAttRef.hillStateInMsg.subscribeTo(hillStateNavObj.hillStateOutMsg) # depAttRef.attStateInMsg.subscribeTo(chiefNav.attOutMsg) depAttRef.attRefInMsg.subscribeTo(depHillRef.attRefOutMsg) depAttRef.relMRPMin = -0.2 depAttRef.relMRPMax = 0.2 # Set the deputy spacecraft to directly follow the attRefMessage depSc.attRefInMsg.subscribeTo(depAttRef.attRefOutMsg) scSim.AddModelToTask(dynTaskName, chiefAttRef, 710) scSim.AddModelToTask(dynTaskName, hillStateNavObj,790) scSim.AddModelToTask(dynTaskName, depHillRef,789) scSim.AddModelToTask(dynTaskName, depAttRef, 700) # ----- log ----- # orbit_period = 2*np.pi/np.sqrt(mu/chief_oe.a**3) simulationTime = 40*orbit_period#106920.14366466808 simulationTime = macros.sec2nano(simulationTime) numDataPoints = 21384 samplingTime = simulationTime // (numDataPoints - 1) # Set up recorders and loggers chiefStateRec = chiefSc.scStateOutMsg.recorder() depStateRec = depSc.scStateOutMsg.recorder() hillStateRec = hillStateNavObj.hillStateOutMsg.recorder() depAttRec = depAttRef.attRefOutMsg.recorder() chiefAttRec = chiefAttRef.attRefOutMsg.recorder() chiefDragForceLog = chiefDrag.logger("forceExternal_B") depDragForceLog = depDrag.logger("forceExternal_B") atmoRecs = [] for msg in atmosphere.envOutMsgs: atmoRec = msg.recorder() atmoRecs.append(atmoRec) scSim.AddModelToTask(dynTaskName, chiefStateRec, 700) scSim.AddModelToTask(dynTaskName, depStateRec, 701) scSim.AddModelToTask(dynTaskName, hillStateRec, 702) scSim.AddModelToTask(dynTaskName, depAttRec, 703) scSim.AddModelToTask(dynTaskName, chiefAttRec, 704) scSim.AddModelToTask(dynTaskName, chiefDragForceLog, 705) scSim.AddModelToTask(dynTaskName, depDragForceLog, 706) for ind,rec in enumerate(atmoRecs): scSim.AddModelToTask(dynTaskName, rec, 707+ind) # if this scenario is to interface with the BSK Viz, uncomment the following lines # to save the BSK data to a file, uncomment the saveFile line below viz = vizSupport.enableUnityVisualization(scSim, dynTaskName, [chiefSc, depSc], # saveFile=fileName, ) # ----- execute sim ----- # scSim.InitializeSimulation() scSim.ConfigureStopTime(simulationTime) setupTimeStamp = time.time() setupTime = setupTimeStamp-startTime print(f"Sim setup complete in {setupTime} seconds, executing...") #scSim.ShowExecutionFigure(True) scSim.ExecuteSimulation() execTimeStamp = time.time() execTime = execTimeStamp - setupTimeStamp print(f"Sim complete in {execTime} seconds, pulling data...") # ----- pull ----- # results_dict = {} results_dict['chiefDrag_B'] = chiefDragForceLog.forceExternal_B results_dict['depDrag_B'] = depDragForceLog.forceExternal_B results_dict['dynTimeData'] = chiefStateRec.times() results_dict['fswTimeData'] = depAttRec.times() results_dict['wiggum.r_BN_N'] = chiefStateRec.r_BN_N results_dict['wiggum.v_BN_N'] = chiefStateRec.v_BN_N results_dict['lou.r_BN_N'] = depStateRec.r_BN_N results_dict['lou.v_BN_N'] = depStateRec.v_BN_N results_dict['relState.r_DC_H'] = hillStateRec.r_DC_H results_dict['relState.v_DC_H'] = hillStateRec.v_DC_H results_dict['wiggum.sigma_BN'] = chiefStateRec.sigma_BN results_dict['lou.sigma_BN'] = depStateRec.sigma_BN results_dict['depDensity'] = atmoRecs[0].neutralDensity results_dict['chiefDensity'] = atmoRecs[1].neutralDensity results_dict['mu'] = mu results_dict['dens_mult'] = densMultiplier pullTimeStamp = time.time() pullTime = pullTimeStamp - execTimeStamp overallTime = pullTimeStamp - startTime print(f"Data pulled in {pullTime} seconds. Overall time: {overallTime} seconds.") return results_dict
def run(show_plots, altOffset, trueAnomOffset, densMultiplier, ctrlType='lqr', useJ2=False): results = drag_simulator(altOffset, trueAnomOffset, densMultiplier, ctrlType=ctrlType, useJ2=useJ2) timeData = results['dynTimeData'] fswTimeData = results['fswTimeData'] pos = results['wiggum.r_BN_N'] vel = results['wiggum.v_BN_N'] chiefAtt = results['wiggum.sigma_BN'] pos2 = results['lou.r_BN_N'] vel2= results['lou.v_BN_N'] depAtt = results['lou.sigma_BN'] hillPos = results['relState.r_DC_H'] hillVel = results['relState.v_DC_H'] depDensity = results['depDensity'] chiefDensity = results['chiefDensity'] depDrag = results['depDrag_B'] chiefDrag = results['chiefDrag_B'] densData = results numDataPoints = len(timeData) mu = results['mu'] rel_mrp_hist = np.empty([numDataPoints,3]) for ind in range(0,numDataPoints): rel_mrp_hist[ind,:] = rbk.subMRP(depAtt[ind,:], chiefAtt[ind,:]) figureList = {} # Plots for general consumption plt.figure() plt.plot(timeData[1:], hillPos[1:,0],label="r_1") plt.grid() plt.xlabel('Time') plt.ylabel('Hill X Position (m)') pltName = fileName + "_hillX" figureList[pltName] = plt.figure(1) plt.figure() plt.plot(timeData[1:], hillPos[1:,1],label="r_2") plt.grid() plt.xlabel('Time') plt.ylabel('Hill Y Position (m)') pltName = fileName + "_hillY" figureList[pltName] = plt.figure(2) plt.figure() plt.plot(timeData[1:], hillVel[1:,0],label="v_1") plt.grid() plt.xlabel('Time') plt.ylabel('Hill X Velocity (m/s)') pltName = fileName + "_hilldX" figureList[pltName] = plt.figure(3) plt.figure() plt.plot(timeData[1:], hillVel[1:,1],label="v_2") plt.ylabel('Hill Y Velocity (m/s)') pltName = fileName + "_hilldy" figureList[pltName] = plt.figure(4) plt.figure() plt.semilogy(timeData[1:], chiefDensity[1:],label=r'Chief $\rho$') plt.semilogy(timeData[1:], depDensity[1:],label=r'Deputy $\rho$') plt.grid() plt.legend() plt.xlabel('Time') plt.ylabel('Density (kg/m3)') pltName = fileName + "_densities" figureList[pltName] = plt.figure(5) plt.figure() plt.plot(hillPos[1:,0],hillPos[1:,1]) plt.grid() plt.xlabel('Hill X (m)') plt.ylabel('Hill Y (m)') pltName = fileName + "_hillTraj" figureList[pltName] = plt.figure(6) plt.figure() plt.plot(timeData, rel_mrp_hist) plt.grid() plt.xlabel('Time') plt.ylabel('Relative MRP Value') pltName = fileName + "_relativeAtt" figureList[pltName] = plt.figure(7) # Debug plots plt.figure() plt.plot(timeData[1:], depDrag[1:,0]-chiefDrag[1:,0],label="delta a_1") plt.plot(timeData[1:], depDrag[1:,1]-chiefDrag[1:,1],label="delta a_2") plt.plot(timeData[1:], depDrag[1:,2]-chiefDrag[1:,2],label="delta a_3") plt.grid() plt.legend() plt.xlabel('Time') plt.ylabel('Relative acceleration due to drag, body frame (m/s)') plt.figure() plt.plot(timeData[1:], chiefDrag[1:,0],label="chief a_1") plt.plot(timeData[1:], chiefDrag[1:,1],label="chief a_2") plt.plot(timeData[1:], chiefDrag[1:,2],label="chief a_3") plt.grid() plt.legend() plt.xlabel('Time') plt.ylabel('Relative acceleration due to drag, body frame (m/s)') plt.figure() plt.plot(timeData[1:], depDrag[1:,0],label="dep a_1") plt.plot(timeData[1:], depDrag[1:,1],label="dep a_2") plt.plot(timeData[1:], depDrag[1:,2],label="dep a_3") plt.grid() plt.legend() plt.xlabel('Time') plt.ylabel('Relative acceleration due to drag, body frame (m/s)') if(show_plots): plt.show() plt.close("all") return figureList if __name__ == "__main__": run( True, # show_plots 0.0, # altitude offset (m) 0.1, # True anomaly offset (deg) 1, # Density multiplier (nondimensional) ctrlType='lqr', useJ2=False )