Source code for scenarioDeployingPanel

#
#  ISC License
#
#  Copyright (c) 2022, 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 scenario demonstrates how to set up a spacecraft with deploying panels. The panel modules
are set up with individual profilers and motors as shown in the following diagram.

.. image:: /_images/static/test_scenario_DeployingPanel.svg
   :align: center

The script is found in the folder ``basilisk/examples`` and executed by using::

    python3 scenarioDeployingPanel.py

The simulation includes two deploying panels that start undeployed. The first panel deploys fully, 
but the second panel deploys off-nominally (to 80%), leading to a reduced power output.


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


::

    show_plots = True

Five plots are shown. The first two show the body attitude and rate relative to the inertial frame,
demonstrating the dynamic effect of the deploying panels on the system. Because the second panel
fails to fully deploy, the body attitude does not return to the original state.

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

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

The next two plots show the panel angles and angle rates. Because the panels are modeled as
rigid bodies attached by a torsional spring, oscillations about the time-varying reference
angle are seen.

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

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

The final plot shows the power output. As the first panel deploys, power starts to be generated.
The second panel also generates power, but stops short of full deployment and thus generates less power.

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


"""

#
# Basilisk Scenario Script and Integrated Test
#
# Purpose:  Scenario script and test of the hingedRigidBodyStateEffector with changes
#           made to permit deployment.
# Author:   Galen Bascom
# Creation Date:  Sept. 12, 2022
#

import os

import matplotlib.pyplot as plt
import numpy as np
from Basilisk import __path__

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

from Basilisk.simulation import spacecraft
from Basilisk.utilities import (SimulationBaseClass, macros, orbitalMotion,
                                simIncludeGravBody, unitTestSupport, vizSupport)
from Basilisk.simulation import hingedRigidBodyStateEffector, simpleSolarPanel
from Basilisk.simulation import hingedBodyLinearProfiler, hingedRigidBodyMotor
import math


[docs]def run(show_plots): """ Args: show_plots (bool): Determines if the script should display plots. """ # 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(.1) dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep)) # create the spacecraft hub scObject = spacecraft.Spacecraft() scObject.ModelTag = "bskSat" scObject.hub.mHub = 750.0 scObject.hub.IHubPntBc_B = [[900.0, 0.0, 0.0], [0.0, 800.0, 0.0], [0.0, 0.0, 600.0]] # setup Earth Gravity Body gravFactory = simIncludeGravBody.gravBodyFactory() gravBodies = gravFactory.createBodies('earth', 'sun') gravBodies['earth'].isCentralBody = True mu = gravBodies['earth'].mu sun = 1 gravFactory.addBodiesTo(scObject) timeInitString = "2012 MAY 1 00:28:30.0" spiceObject = gravFactory.createSpiceInterface(time=timeInitString, epochInMsg=True) spiceObject.zeroBase = 'earth' scSim.AddModelToTask(simTaskName, spiceObject) # setup the orbit using classical orbit elements oe = orbitalMotion.ClassicElements() rLEO = 7000. * 1000 # meters rGEO = 42000. * 1000 # meters oe.a = (rLEO + rGEO) / 2.0 oe.e = 1.0 - rLEO / oe.a 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(mu, oe) oe = orbitalMotion.rv2elem(mu, rN, vN) # this stores consistent initial orbit elements # To set the spacecraft initial conditions, the following initial position and velocity variables are set: scObject.hub.r_CN_NInit = rN # m - r_BN_N scObject.hub.v_CN_NInit = vN # m/s - v_BN_N # point the body 3 axis towards the sun in the inertial n2 direction scObject.hub.sigma_BNInit = [[math.tan(-90. / 4. * macros.D2R)], [0.0], [0.0]] # sigma_BN_B scObject.hub.omega_BN_BInit = [[0.0], [0.0], [0.0]] # rad/s - omega_BN_B # configure panels panel1 = hingedRigidBodyStateEffector.HingedRigidBodyStateEffector() panel1.ModelTag = "panel1" panel1.mass = 100.0 panel1.IPntS_S = [[100.0, 0.0, 0.0], [0.0, 50.0, 0.0], [0.0, 0.0, 50.0]] panel1.d = 1.5 panel1.k = 200. panel1.c = 20. panel1.r_HB_B = [[-.5], [0.0], [-1.0]] panel1.dcm_HB = [[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]] panel1.thetaInit = -np.pi panel1.thetaDotInit = 0 panel2 = hingedRigidBodyStateEffector.HingedRigidBodyStateEffector() panel2.ModelTag = "panel2" panel2.mass = 100.0 panel2.IPntS_S = [[100.0, 0.0, 0.0], [0.0, 50.0, 0.0], [0.0, 0.0, 50.0]] panel2.d = 1.5 panel2.k = 200. panel2.c = 20. panel2.r_HB_B = [[.5], [0.0], [-1.0]] panel2.dcm_HB = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] panel2.thetaInit = -np.pi panel2.thetaDotInit = 0 # profilers profiler1 = hingedBodyLinearProfiler.HingedBodyLinearProfiler() profiler1.ModelTag = "deploymentProfiler" profiler1.startTime = macros.sec2nano(5) # [ns] start the deployment profiler1.endTime = macros.sec2nano(85) # [ns] profiler1.startTheta = -np.pi # [rad] starting angle in radians profiler1.endTheta = -np.pi / 2 # [rad] profiler2 = hingedBodyLinearProfiler.HingedBodyLinearProfiler() profiler2.ModelTag = "deploymentProfiler2" profiler2.startTime = macros.sec2nano(100) # [ns] start the deployment profiler2.endTime = macros.sec2nano(164) # [ns] profiler2.startTheta = -np.pi # [rad] starting angle in radians profiler2.endTheta = -np.pi / 1.75 # [rad] ending angle is not all the way deployed panel1.hingedRigidBodyRefMsg.subscribeTo(profiler1.hingedRigidBodyReferenceOutMsg) panel2.hingedRigidBodyRefMsg.subscribeTo(profiler2.hingedRigidBodyReferenceOutMsg) # motors motor1 = hingedRigidBodyMotor.HingedRigidBodyMotor() motor1.ModelTag = "hingedRigidBodyMotor" motor1.K = 20 # proportional gain constant motor1.P = 10 # derivative gain constant motor2 = hingedRigidBodyMotor.HingedRigidBodyMotor() motor2.ModelTag = "hingedRigidBodyMotor2" motor2.K = 20 # proportional gain constant motor2.P = 10 # derivative gain constant motor1.hingedBodyStateSensedInMsg.subscribeTo(panel1.hingedRigidBodyOutMsg) motor1.hingedBodyStateReferenceInMsg.subscribeTo(profiler1.hingedRigidBodyReferenceOutMsg) panel1.motorTorqueInMsg.subscribeTo(motor1.motorTorqueOutMsg) motor2.hingedBodyStateSensedInMsg.subscribeTo(panel2.hingedRigidBodyOutMsg) motor2.hingedBodyStateReferenceInMsg.subscribeTo(profiler2.hingedRigidBodyReferenceOutMsg) panel2.motorTorqueInMsg.subscribeTo(motor2.motorTorqueOutMsg) # add panel to spacecraft hub scObject.addStateEffector(panel1) # in order to affect dynamics scObject.addStateEffector(panel2) # in order to affect dynamics # power solarPanel1 = simpleSolarPanel.SimpleSolarPanel() solarPanel1.ModelTag = "pwr1" solarPanel1.nHat_B = [0, 0, 1] solarPanel1.panelArea = 2.0 # m^2 solarPanel1.panelEfficiency = 0.9 # 90% efficiency in power generation solarPanel1.stateInMsg.subscribeTo(panel1.hingedRigidBodyConfigLogOutMsg) solarPanel1.sunInMsg.subscribeTo(spiceObject.planetStateOutMsgs[sun]) solarPanel2 = simpleSolarPanel.SimpleSolarPanel() solarPanel2.ModelTag = "pwr2" solarPanel2.nHat_B = [0, 0, 1] solarPanel2.panelArea = 2.0 # m^2 solarPanel2.panelEfficiency = 0.9 # 90% efficiency in power generation solarPanel2.stateInMsg.subscribeTo(panel2.hingedRigidBodyConfigLogOutMsg) solarPanel2.sunInMsg.subscribeTo(spiceObject.planetStateOutMsgs[sun]) # # add modules to simulation task list # scSim.AddModelToTask(simTaskName, scObject) scSim.AddModelToTask(simTaskName, panel1) scSim.AddModelToTask(simTaskName, profiler1) scSim.AddModelToTask(simTaskName, motor1) scSim.AddModelToTask(simTaskName, panel2) scSim.AddModelToTask(simTaskName, profiler2) scSim.AddModelToTask(simTaskName, motor2) scSim.AddModelToTask(simTaskName, solarPanel1) scSim.AddModelToTask(simTaskName, solarPanel2) # set the simulation time simulationTime = macros.sec2nano(240) # Setup data logging before the simulation is initialized numDataPoints = 1000 samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints) dataLog = scObject.scStateOutMsg.recorder(samplingTime) p1Log = panel1.hingedRigidBodyOutMsg.recorder(samplingTime) prof1Log = profiler1.hingedRigidBodyReferenceOutMsg.recorder(samplingTime) motor1Log = motor1.motorTorqueOutMsg.recorder(samplingTime) p2Log = panel2.hingedRigidBodyOutMsg.recorder(samplingTime) prof2Log = profiler2.hingedRigidBodyReferenceOutMsg.recorder(samplingTime) motor2Log = motor2.motorTorqueOutMsg.recorder(samplingTime) pwr1Log = solarPanel1.nodePowerOutMsg.recorder(samplingTime) pwr2Log = solarPanel2.nodePowerOutMsg.recorder(samplingTime) scSim.AddModelToTask(simTaskName, dataLog) scSim.AddModelToTask(simTaskName, p1Log) scSim.AddModelToTask(simTaskName, prof1Log) scSim.AddModelToTask(simTaskName, motor1Log) scSim.AddModelToTask(simTaskName, p2Log) scSim.AddModelToTask(simTaskName, prof2Log) scSim.AddModelToTask(simTaskName, motor2Log) scSim.AddModelToTask(simTaskName, pwr1Log) scSim.AddModelToTask(simTaskName, pwr2Log) if vizSupport.vizFound: viz = vizSupport.enableUnityVisualization(scSim, simTaskName, [scObject , [panel1.ModelTag, panel1.hingedRigidBodyConfigLogOutMsg] , [panel2.ModelTag, panel2.hingedRigidBodyConfigLogOutMsg] ] # , saveFile=__file__ ) vizSupport.createCustomModel(viz , simBodiesToModify=[panel1.ModelTag] , modelPath="CUBE" , scale=[3, 1, 0.1] , color=vizSupport.toRGBA255("blue")) vizSupport.createCustomModel(viz , simBodiesToModify=[panel2.ModelTag] , modelPath="CUBE" , scale=[3, 1, 0.1] , color=vizSupport.toRGBA255("blue")) viz.settings.orbitLinesOn = -1 scSim.InitializeSimulation() scSim.ConfigureStopTime(simulationTime) scSim.ExecuteSimulation() # retrieve the logged data dataSigmaBN = dataLog.sigma_BN dataOmegaBN_B = dataLog.omega_BN_B panel1thetaLog = p1Log.theta panel1thetaDotLog = p1Log.thetaDot panel2thetaLog = p2Log.theta panel2thetaDotLog = p2Log.thetaDot pwrLog1 = pwr1Log.netPower pwrLog2 = pwr2Log.netPower np.set_printoptions(precision=16) figureList = plotOrbits(dataLog.times(), dataSigmaBN, dataOmegaBN_B, panel1thetaLog, panel1thetaDotLog, panel2thetaLog, panel2thetaDotLog, pwrLog1, pwrLog2) if show_plots: plt.show() # close the plots being saved off to avoid over-writing old and new figures plt.close("all") return figureList
def plotOrbits(timeAxis, dataSigmaBN, dataOmegaBN, panel1thetaLog, panel1thetaDotLog, panel2thetaLog, panel2thetaDotLog, pwrLog1, pwrLog2): plt.close("all") # clears out plots from earlier test runs # sigma B/N figCounter = 1 plt.figure(figCounter) fig = plt.gcf() ax = fig.gca() ax.ticklabel_format(useOffset=False, style='plain') timeData = timeAxis * macros.NANO2SEC for idx in range(3): plt.plot(timeData, dataSigmaBN[:, idx], color=unitTestSupport.getLineColor(idx, 3), label=r'$\sigma_' + str(idx) + '$') plt.legend(loc='lower right') plt.xlabel('Time [s]') plt.ylabel(r'MRP Attitude $\sigma_{B/N}$') figureList = {} pltName = fileName + str(figCounter) figureList[pltName] = plt.figure(figCounter) # omega B/N figCounter += 1 plt.figure(figCounter) fig = plt.gcf() ax = fig.gca() ax.ticklabel_format(useOffset=False, style='plain') for idx in range(3): plt.plot(timeData, dataOmegaBN[:, idx], color=unitTestSupport.getLineColor(idx, 3), label=r'$\omega_' + str(idx) + '$') plt.legend(loc='lower right') plt.xlabel('Time [s]') plt.ylabel(r'Rate $\omega_{B/N}$') pltName = fileName + str(figCounter) figureList[pltName] = plt.figure(figCounter) # rotating panel hinge angle: panel 1 figCounter += 1 plt.figure(figCounter) ax1 = plt.figure(figCounter).add_subplot(111) ax1.plot(timeData, panel1thetaLog, color='royalblue') plt.xlabel('Time [s]') plt.ylabel('Panel 1 Angle [rad]', color='royalblue') ax2 = plt.figure(figCounter).add_subplot(111, sharex=ax1, frameon=False) ax2.plot(timeData, panel1thetaDotLog, color='indianred') ax2.yaxis.tick_right() ax2.yaxis.set_label_position("right") plt.ylabel('Panel 1 Angle Rate [rad/s]', color='indianred') pltName = fileName + str(figCounter) figureList[pltName] = plt.figure(figCounter) # rotating panel hinge angle: panel 2 figCounter += 1 plt.figure(figCounter) ax1 = plt.figure(figCounter).add_subplot(111) ax1.plot(timeData, panel2thetaLog, color='royalblue') plt.xlabel('Time [s]') plt.ylabel('Panel 2 Angle [rad]', color='royalblue') ax2 = plt.figure(figCounter).add_subplot(111, sharex=ax1, frameon=False) ax2.plot(timeData, panel2thetaDotLog, color='indianred') ax2.yaxis.tick_right() ax2.yaxis.set_label_position("right") plt.ylabel('Panel 2 Angle Rate [rad/s]', color='indianred') pltName = fileName + str(figCounter) figureList[pltName] = plt.figure(figCounter) # power: panel 1 figCounter += 1 plt.figure(figCounter) ax1 = plt.figure(figCounter).add_subplot(111) ax1.plot(timeData, pwrLog1, color='goldenrod', label="Panel 1") ax1.plot(timeData, pwrLog2, '--', color='goldenrod', label="Panel 2") plt.xlabel('Time [s]') plt.ylabel('Panel Power [W]') plt.legend(loc='lower right') pltName = fileName + str(figCounter) figureList[pltName] = plt.figure(figCounter) return figureList if __name__ == "__main__": run( True # show_plots )