Source code for scenario_BasicOrbitMultiSat_MT
#
# 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 demonstrates how to use the Basilisk v2.1 multi-threading to simulate a constellation
of 16 spacecraft using 4 threads. The simulation scenario setup is very similar to
:ref:`scenario_BasicOrbitMultiSat`. To setup the unique satellite orbits, the script here loops
over all satellites an incrementally changes the orbit elements. The following Vizard screen capture
illustrate the family of orbits being simulated.
.. image:: /_images/static/scenario_BasicOrbitMultiSat_MT.jpg
:align: center
Inside the ``run()`` command the number of threads is specified using::
TheScenario.TotalSim.resetThreads(numThreads)
With this basic setup it is assumed that each BSK process can run independently in a thread. Note that this script
does not use any spacecraft flight software which is running in a separate process. The Basilisk v2.1
multi-threading only functions for simulations where each Basilisk process can be evaluated independently.
.. warning::
The Basilisk v2.1 multi-threading does not have a thread-safe messaging system. This will be
added in a later release.
Illustration of Simulation Results
----------------------------------
::
showPlots = True, numberSpacecraft=16, environment = 'Earth', numThreads = 4
.. image:: /_images/Scenarios/scenario_BasicOrbitMultiSat_MT_spacecraft_orbits.svg
:align: center
"""
# Get current file path
import inspect
import os
import sys
from Basilisk.architecture import messaging
# Import utilities
from Basilisk.utilities import orbitalMotion, macros, vizSupport
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
# Import master classes: simulation base class and scenario base class
sys.path.append(path + '/../')
sys.path.append(path + '/../modelsMultiSat')
sys.path.append(path + '/../plottingMultiSat')
from BSK_MultiSatMasters import BSKSim, BSKScenario
import BSK_EnvironmentEarth, BSK_EnvironmentMercury, BSK_MultiSatDynamics
# Import plotting files for your scenario
import BSK_MultiSatPlotting as plt
# Create your own scenario child class
[docs]
class scenario_BasicOrbitFormationFlying(BSKSim, BSKScenario):
def __init__(self, numberSpacecraft, environment):
super(scenario_BasicOrbitFormationFlying, self).__init__(numberSpacecraft, fswRate=10, dynRate=10, envRate=10)
self.name = 'scenario_BasicOrbitFormationFlying'
if environment == "Earth":
self.set_EnvModel(BSK_EnvironmentEarth)
elif environment == "Mercury":
self.set_EnvModel(BSK_EnvironmentMercury)
# Here we are setting the list of spacecraft dynamics to be a homogenous formation.
# To use a heterogeneous spacecraft formation, this list can contain different classes
# of type BSKDynamicModels
self.set_DynModel([BSK_MultiSatDynamics] * numberSpacecraft)
# declare empty class variables
self.samplingTime = []
self.snTransLog = []
self.snAttLog = []
self.rwSpeedLog = []
self.rwLogs = [[] for _ in range(self.numberSpacecraft)]
# declare empty containers for orbital elements
self.oe = []
self.configure_initial_conditions()
self.log_outputs()
if vizSupport.vizFound:
# if this scenario is to interface with the BSK Viz, uncomment the saveFile line
DynModelsList = []
rwStateEffectorList = []
for i in range(self.numberSpacecraft):
DynModelsList.append(self.DynModels[i].scObject)
rwStateEffectorList.append(self.DynModels[i].rwStateEffector)
batteryPanel = vizSupport.vizInterface.GenericStorage()
batteryPanel.label = "Battery"
batteryPanel.units = "Ws"
batteryPanel.color = vizSupport.vizInterface.IntVector(
vizSupport.toRGBA255("red") + vizSupport.toRGBA255("green"))
batteryPanel.thresholds = vizSupport.vizInterface.IntVector([20])
batteryInMsg = messaging.PowerStorageStatusMsgReader()
batteryInMsg.subscribeTo(self.DynModels[0].powerMonitor.batPowerOutMsg)
batteryPanel.batteryStateInMsg = batteryInMsg
batteryPanel.this.disown()
tankPanel = vizSupport.vizInterface.GenericStorage()
tankPanel.label = "Tank"
tankPanel.units = "kg"
tankPanel.color = vizSupport.vizInterface.IntVector(vizSupport.toRGBA255("cyan"))
tankInMsg = messaging.FuelTankMsgReader()
tankInMsg.subscribeTo(self.DynModels[0].fuelTankStateEffector.fuelTankOutMsg)
tankPanel.fuelTankStateInMsg = tankInMsg
tankPanel.this.disown()
storageList = [None] * self.numberSpacecraft
storageList[0] = [batteryPanel, tankPanel]
viz = vizSupport.enableUnityVisualization(self, self.DynModels[0].taskName, DynModelsList
# , saveFile=__file__
, rwEffectorList=rwStateEffectorList
, genericStorageList=storageList
)
vizSupport.setInstrumentGuiSetting(viz, showGenericStoragePanel=True)
[docs]
def configure_initial_conditions(self):
EnvModel = self.get_EnvModel()
DynModels = self.get_DynModel()
# Configure Dynamics initial conditions
for i in range(self.numberSpacecraft):
self.oe.append(orbitalMotion.ClassicElements())
self.oe[i].a = 1.1 * EnvModel.planetRadius + 1E5 * (i + 1) # meters
self.oe[i].e = 0.01 + 0.001 * (i)
self.oe[i].i = 45.0 * macros.D2R
self.oe[i].Omega = (48.2 + 5.0 * i) * macros.D2R
self.oe[i].omega = 347.8 * macros.D2R
self.oe[i].f = 85.3 * macros.D2R
rN, vN = orbitalMotion.elem2rv(EnvModel.mu, self.oe[i])
orbitalMotion.rv2elem(EnvModel.mu, rN, vN)
DynModels[i].scObject.hub.r_CN_NInit = rN # m - r_CN_N
DynModels[i].scObject.hub.v_CN_NInit = vN # m/s - v_CN_N
DynModels[i].scObject.hub.sigma_BNInit = [[0.1], [0.2], [-0.3]] # sigma_BN_B
DynModels[0].scObject.hub.omega_BN_BInit = [[0.0], [0.0], [0.0]] # rad/s - omega_BN_B
[docs]
def log_outputs(self):
# Process outputs
DynModels = self.get_DynModel()
# Set the sampling time
self.samplingTime = macros.sec2nano(1)
# Loop through every spacecraft
for spacecraft in range(self.numberSpacecraft):
# log the navigation messages
self.snTransLog.append(DynModels[spacecraft].simpleNavObject.transOutMsg.recorder(self.samplingTime))
self.snAttLog.append(DynModels[spacecraft].simpleNavObject.attOutMsg.recorder(self.samplingTime))
self.AddModelToTask(DynModels[spacecraft].taskName, self.snTransLog[spacecraft])
self.AddModelToTask(DynModels[spacecraft].taskName, self.snAttLog[spacecraft])
# log the RW wheel speed information
self.rwSpeedLog.append(DynModels[spacecraft].rwStateEffector.rwSpeedOutMsg.recorder(self.samplingTime))
self.AddModelToTask(DynModels[spacecraft].taskName, self.rwSpeedLog[spacecraft])
# log addition RW information (power, etc)
for item in range(DynModels[spacecraft].numRW):
self.rwLogs[spacecraft].append(
DynModels[spacecraft].rwStateEffector.rwOutMsgs[item].recorder(self.samplingTime))
self.AddModelToTask(DynModels[spacecraft].taskName, self.rwLogs[spacecraft][item])
[docs]
def pull_outputs(self, show_plots):
#
# Retrieve the logged data
#
r_BN_N = []
for i in range(self.numberSpacecraft):
r_BN_N.append(self.snTransLog[i].r_BN_N)
#
# Plot results
#
plt.clear_all_plots()
plt.plot_orbits(r_BN_N, self.numberSpacecraft, 1)
figureList = {}
if show_plots:
plt.show_all_plots()
else:
fileName = os.path.basename(os.path.splitext(__file__)[0])
figureNames = ["spacecraft_orbits"]
figureList = plt.save_all_plots(fileName, figureNames)
# close the plots being saved off to avoid over-writing old and new figures
plt.clear_all_plots()
return figureList
def runScenario(scenario):
# Initialize simulation
scenario.InitializeSimulation()
# Configure run time and execute simulation
simulationTime = macros.hour2nano(24)
scenario.ConfigureStopTime(simulationTime)
scenario.ExecuteSimulation()
[docs]
def run(show_plots, numberSpacecraft, environment, numThreads):
"""
The scenarios can be run with the followings setups parameters:
Args:
show_plots (bool): Determines if the script should display plots
numberSpacecraft (int): Number of spacecraft in the simulation
environment (string): Chooses which environment to set the simulation in. Options are "Earth" or "Mercury"
numThreads (int): number of threads
"""
# Configure a scenario in the base simulation
TheScenario = scenario_BasicOrbitFormationFlying(numberSpacecraft, environment)
# This call allocates out the requested number of threads into the simulation
# Note that unless the user does something further, processes will be assigned
# in a round-robin fashion to the allocated threads. If you desire to specify
# which processes execute on a given thread, then the addProcessToThread in
# the SimModel (TotalSim in SimulationBaseClass) should be used.
TheScenario.TotalSim.resetThreads(numThreads)
runScenario(TheScenario)
figureList = TheScenario.pull_outputs(show_plots)
return figureList
if __name__ == "__main__":
run(show_plots=True,
numberSpacecraft=32,
environment="Earth", # Earth or Mercury
numThreads=4
)