Skip to content

Commit dc74723

Browse files
authored
Merge pull request AVSLab#606 from AVSLab/feature/halo
New halo example script
2 parents cc25091 + 866ab00 commit dc74723

File tree

4 files changed

+324
-1
lines changed

4 files changed

+324
-1
lines changed

docs/source/Support/bskReleaseNotes.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ Version |release|
3636
- Fixed a bug where the legacy variable logging API would either, not log at all or log at a rate different to the
3737
requested rate.
3838
- Fixed a python version checking bug that prevented Basilisk from compiling on Windows
39-
- Updated versioning to better follow the `semantic versioning <https://semver.org>`_ standard, in the format
39+
- Created a new example scenario :ref:`scenarioHaloOrbit` demonstrating a near-Halo orbit simulation
40+
- Updated versioning to better follow the `semantic versioning <https://semver.org>`_ standard, in the format
4041
``MAJOR.MINOR.PATCH``. Releases will increment the minor version number, while pull requests into develop will
4142
automatically increment the patch number. This allows users to reference/require specific versions of Basilisk
4243
outside of the release cycle.

examples/_default.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Orbital Simulations
3333
Simulating Trajectory about Multiple Celestial Bodies <scenarioPatchedConics>
3434
Including Custom Gravitational Bodies <scenarioCustomGravBody>
3535
Small Body Waypoint-to-Waypoint Control <scenarioSmallBodyFeedbackControl>
36+
Near-Halo Orbit Simulation <scenarioHaloOrbit>
3637

3738

3839
Attitude Simulations

examples/scenarioHaloOrbit.py

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
2+
# ISC License
3+
#
4+
# Copyright (c) 2024, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
5+
#
6+
# Permission to use, copy, modify, and/or distribute this software for any
7+
# purpose with or without fee is hereby granted, provided that the above
8+
# copyright notice and this permission notice appear in all copies.
9+
#
10+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17+
#
18+
19+
r"""
20+
Overview
21+
--------
22+
23+
This script sets up a 3-DOF spacecraft that is operating near-Halo orbit at L2 Earth-Moon Lagrange points. The purpose
24+
is to illustrate how to set up the spacecraft's initial conditions to create a near-Halo orbit and convert the barycenter focused
25+
non-dimensional ICs to earth-centered inertial frame components.
26+
27+
The script is found in the folder ``basilisk/examples`` and executed by using::
28+
29+
python3 scenarioHaloOrbit.py
30+
31+
For this simulation, the Earth is assumed stationary, and the Moon's trajectory is generated using SPICE. Refer to
32+
:ref:`scenarioOrbitMultiBody` to learn how to create multiple gravity bodies and read a SPICE trajectory.
33+
34+
When the simulation completes, three plots are shown. The first plot shows the orbits of the Moon and spacecraft in
35+
the Earth-centered inertial frame. The second and third plots show the motion of the spacecraft in a frame rotating
36+
with the Moon. In the second plot, the y-axis represents the Moon's velocity direction, and in the third plot, the
37+
y-axis represents the cross product of the Moon's position vector and velocity vector.
38+
39+
Illustration of Simulation Results
40+
----------------------------------
41+
42+
The following images illustrate the simulation run results with the following settings:
43+
44+
::
45+
46+
showPlots = True
47+
48+
.. image:: /_images/Scenarios/scenarioHaloOrbitFig1.svg
49+
:align: center
50+
51+
.. image:: /_images/Scenarios/scenarioHaloOrbitFig2.svg
52+
:align: center
53+
54+
.. image:: /_images/Scenarios/scenarioHaloOrbitFig3.svg
55+
:align: center
56+
57+
58+
"""
59+
60+
#
61+
# Basilisk Scenario Script and Integrated Test
62+
#
63+
# Purpose: This scenario illustrates the near-Halo orbit of a spacecraft.
64+
# Author: Yumeka Nagano
65+
# Creation Date: Feb. 12, 2024
66+
#
67+
68+
import os
69+
from datetime import datetime
70+
71+
import matplotlib.pyplot as plt
72+
import numpy as np
73+
from Basilisk import __path__
74+
from Basilisk.simulation import spacecraft
75+
from Basilisk.topLevelModules import pyswice
76+
from Basilisk.utilities import (SimulationBaseClass, macros, orbitalMotion,
77+
simIncludeGravBody, unitTestSupport, vizSupport)
78+
from Basilisk.utilities.pyswice_spk_utilities import spkRead
79+
80+
bskPath = __path__[0]
81+
fileName = os.path.basename(os.path.splitext(__file__)[0])
82+
83+
def run(showPlots=True):
84+
"""
85+
Args:
86+
showPlots (bool): Determines if the script should display plots
87+
"""
88+
89+
# Create simulation variable names
90+
simTaskName = "dynTask"
91+
simProcessName = "dynProcess"
92+
93+
# Create a sim module as an empty container
94+
scSim = SimulationBaseClass.SimBaseClass()
95+
scSim.SetProgressBar(True)
96+
97+
# Create the simulation process (dynamics)
98+
dynProcess = scSim.CreateNewProcess(simProcessName)
99+
100+
# Add the dynamics task to the dynamics process and specify the integration update time
101+
timestep = 300
102+
simulationTimeStep = macros.sec2nano(timestep)
103+
dynProcess.addTask(scSim.CreateNewTask(simTaskName, simulationTimeStep))
104+
105+
# Setup the spacecraft object
106+
scObject = spacecraft.Spacecraft()
107+
scObject.ModelTag = "HaloSat"
108+
109+
# Add spacecraft object to the simulation process
110+
# Make this model a lower priority than the SPICE object task
111+
scSim.AddModelToTask(simTaskName, scObject, 0)
112+
113+
# Setup gravity factory and gravity bodies
114+
# Include bodies as a list of SPICE names
115+
gravFactory = simIncludeGravBody.gravBodyFactory()
116+
gravBodies = gravFactory.createBodies('moon', 'earth')
117+
gravBodies['earth'].isCentralBody = True
118+
119+
# Add gravity bodies to the spacecraft dynamics
120+
gravFactory.addBodiesTo(scObject)
121+
122+
# Create default SPICE module, specify start date/time.
123+
timeInitString = "2022 August 31 15:00:00.0"
124+
bsk_path = __path__[0]
125+
spiceObject = gravFactory.createSpiceInterface(bsk_path + "/supportData/EphemerisData/", time=timeInitString,
126+
epochInMsg=True)
127+
spiceObject.zeroBase = 'earth'
128+
129+
# Add SPICE object to the simulation task list
130+
scSim.AddModelToTask(simTaskName, spiceObject, 1)
131+
132+
# Import SPICE ephemeris data into the python environment
133+
pyswice.furnsh_c(spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies
134+
pyswice.furnsh_c(spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file
135+
pyswice.furnsh_c(spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses
136+
pyswice.furnsh_c(spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants Kernel
137+
138+
# Set spacecraft ICs
139+
# Get initial moon data
140+
moonSpiceName = 'moon'
141+
moonInitialState = 1000 * spkRead(moonSpiceName, timeInitString, 'J2000', 'earth')
142+
moon_rN_init = moonInitialState[0:3]
143+
moon_vN_init = moonInitialState[3:6]
144+
moon = gravBodies['moon']
145+
earth = gravBodies['earth']
146+
oe = orbitalMotion.rv2elem(earth.mu, moon_rN_init, moon_vN_init)
147+
moon_a = oe.a
148+
149+
# Direction Cosine Matrix (DCM) from earth centered inertial frame to earth-moon rotation frame
150+
DCMInit = np.array([moon_rN_init/np.linalg.norm(moon_rN_init),moon_vN_init/np.linalg.norm(moon_vN_init),
151+
np.cross(moon_rN_init, moon_vN_init) / np.linalg.norm(np.cross(moon_rN_init, moon_vN_init))])
152+
153+
# Set up non-dimensional parameters
154+
T_ND = np.sqrt(moon_a ** 3 / (earth.mu + moon.mu)) # non-dimensional time for one second
155+
mu_ND = moon.mu/(earth.mu + moon.mu) # non-dimensional mass
156+
157+
# Set up initial conditions for the spacecraft
158+
x0 = 1.182212 * moon_a + moon_a * mu_ND
159+
z0 = 0.049 * moon_a
160+
dy0 = -0.167 * moon_a / T_ND
161+
X0 = np.array([[x0], [0], [z0]])
162+
dX0 = np.array([[0], [np.linalg.norm(moon_vN_init) + dy0], [0]])
163+
164+
rN = np.dot(np.transpose(DCMInit), X0)
165+
vN = np.dot(np.transpose(DCMInit), dX0)
166+
167+
scObject.hub.r_CN_NInit = rN
168+
scObject.hub.v_CN_NInit = vN
169+
170+
# Set simulation time
171+
simulationTime = macros.day2nano(17.5)
172+
173+
# Setup data logging
174+
numDataPoints = 1000
175+
samplingTime = unitTestSupport.samplingTime(simulationTime, simulationTimeStep, numDataPoints)
176+
177+
# Setup spacecraft data recorder
178+
scDataRec = scObject.scStateOutMsg.recorder(samplingTime)
179+
MoonDataRec = spiceObject.planetStateOutMsgs[0].recorder(samplingTime)
180+
scSim.AddModelToTask(simTaskName, scDataRec)
181+
scSim.AddModelToTask(simTaskName, MoonDataRec)
182+
183+
viz = vizSupport.enableUnityVisualization(scSim, simTaskName, scObject,
184+
# saveFile=__file__
185+
)
186+
# Initialize simulation
187+
scSim.InitializeSimulation()
188+
189+
# Execute simulation
190+
scSim.ConfigureStopTime(simulationTime)
191+
scSim.ExecuteSimulation()
192+
193+
# Retrieve logged data
194+
posData = scDataRec.r_BN_N
195+
velData = scDataRec.v_BN_N
196+
timeData = scDataRec.times()
197+
moonPos = MoonDataRec.PositionVector
198+
moonVel = MoonDataRec.VelocityVector
199+
200+
# Plot results
201+
np.set_printoptions(precision=16)
202+
plt.close("all")
203+
figureList = {}
204+
b = oe.a * np.sqrt(1 - oe.e * oe.e)
205+
206+
# First plot: Draw orbit in inertial frame
207+
fig = plt.figure(1, figsize=np.array((1.0, b / oe.a)) * 4.75, dpi=100)
208+
plt.axis(np.array([-oe.rApoap, oe.rPeriap, -b, b]) / 1000 * 1.25)
209+
ax = fig.gca()
210+
ax.ticklabel_format(style='scientific', scilimits=[-5, 3])
211+
212+
# Draw 'cartoon' Earth
213+
ax.add_artist(plt.Circle((0, 0), 0.2e5, color='b'))
214+
215+
# Plot spacecraft orbit data
216+
rDataSpacecraft = []
217+
fDataSpacecraft = []
218+
for ii in range(len(posData)):
219+
oeDataSpacecraft = orbitalMotion.rv2elem(earth.mu, posData[ii], velData[ii])
220+
rDataSpacecraft.append(oeDataSpacecraft.rmag)
221+
fDataSpacecraft.append(oeDataSpacecraft.f + oeDataSpacecraft.omega - oe.omega)
222+
plt.plot(rDataSpacecraft * np.cos(fDataSpacecraft) / 1000, rDataSpacecraft * np.sin(fDataSpacecraft) / 1000,
223+
color='g', linewidth=3.0, label='Spacecraft')
224+
225+
# Plot moon orbit data
226+
rDataMoon = []
227+
fDataMoon = []
228+
for ii in range(len(timeData)):
229+
oeDataMoon = orbitalMotion.rv2elem(earth.mu, moonPos[ii], moonVel[ii])
230+
rDataMoon.append(oeDataMoon.rmag)
231+
fDataMoon.append(oeDataMoon.f + oeDataMoon.omega - oe.omega)
232+
plt.plot(rDataMoon * np.cos(fDataMoon) / 1000, rDataMoon * np.sin(fDataMoon) / 1000, color='0.5',
233+
linewidth=3.0, label='Moon')
234+
235+
plt.xlabel(r'$i_e$ Coord. [km]')
236+
plt.ylabel(r'$i_p$ Coord. [km]')
237+
plt.grid()
238+
plt.legend()
239+
pltName = fileName + "Fig1"
240+
figureList[pltName] = plt.figure(1)
241+
242+
# Second plot: Draw orbit in frame rotating with the Moon (the center is L2 point)
243+
# x axis is moon position vector direction and y axis is moon velocity vector direction
244+
fig = plt.figure(2, figsize=np.array((1.0, b / oe.a)) * 4.75, dpi=100)
245+
plt.axis(np.array([-1e5, 5e5, -3e5, 3e5]) * 1.25)
246+
ax = fig.gca()
247+
ax.ticklabel_format(style='scientific', scilimits=[-5, 3])
248+
249+
# Draw 'cartoon' Earth
250+
ax.add_artist(plt.Circle((0, 0), 0.2e5, color='b'))
251+
252+
# Plot spacecraft orbit data
253+
rSpacecraft = np.zeros((len(posData), 3))
254+
255+
for ii in range(len(posData)):
256+
# Get Moon position and velocity
257+
moon_rN = moonPos[ii]
258+
moon_vN = moonVel[ii]
259+
260+
# Direction Cosine Matrix (DCM) from earth centered inertial frame to earth-moon rotation frame
261+
rSpacecraftMag = np.linalg.norm(posData[ii])
262+
rMoonMag = np.linalg.norm(moon_rN)
263+
DCM = [moon_rN / rMoonMag, moon_vN / np.linalg.norm(moon_vN),
264+
np.cross(moon_rN, moon_vN) / np.linalg.norm(np.cross(moon_rN, moon_vN))]
265+
266+
# Spacecraft position in rotating frame
267+
rSpacecraft[ii,:] = np.dot(DCM, posData[ii])
268+
269+
plt.plot(rSpacecraft[:,0] / 1000, rSpacecraft[:,1] / 1000,
270+
color='g', linewidth=3.0, label='Spacecraft')
271+
272+
plt.xlabel('Earth-Moon axis [km]')
273+
plt.ylabel('Moon Velocity axis [km]')
274+
plt.grid()
275+
plt.legend()
276+
pltName = fileName + "Fig2"
277+
figureList[pltName] = plt.figure(2)
278+
279+
# Third plot: Draw orbit in frame rotating with the Moon (the center is L2 point)
280+
# x axis is moon position vector direction and y axis is the cross product direction of the moon position vector and
281+
# velocity vector
282+
fig = plt.figure(3, figsize=np.array((1.0, b / oe.a)) * 4.75, dpi=100)
283+
plt.axis(np.array([-1e5, 5e5, -3e5, 3e5]) * 1.25)
284+
ax = fig.gca()
285+
ax.ticklabel_format(style='scientific', scilimits=[-5, 3])
286+
287+
# Draw 'cartoon' Earth
288+
ax.add_artist(plt.Circle((0, 0), 0.2e5, color='b'))
289+
290+
plt.plot(rSpacecraft[:, 0] / 1000, rSpacecraft[:, 2] / 1000,
291+
color='g', linewidth=3.0, label='Spacecraft')
292+
293+
plt.xlabel('Earth-Moon axis [km]')
294+
plt.ylabel('Earth-Moon perpendicular axis [km]')
295+
plt.grid()
296+
plt.legend()
297+
pltName = fileName + "Fig3"
298+
figureList[pltName] = plt.figure(3)
299+
300+
if showPlots:
301+
plt.show()
302+
303+
plt.close("all")
304+
305+
# Unload spice libraries
306+
gravFactory.unloadSpiceKernels()
307+
pyswice.unload_c(spiceObject.SPICEDataPath + 'de430.bsp') # solar system bodies
308+
pyswice.unload_c(spiceObject.SPICEDataPath + 'naif0012.tls') # leap second file
309+
pyswice.unload_c(spiceObject.SPICEDataPath + 'de-403-masses.tpc') # solar system masses
310+
pyswice.unload_c(spiceObject.SPICEDataPath + 'pck00010.tpc') # generic Planetary Constants Kernel
311+
312+
return figureList
313+
314+
315+
if __name__ == "__main__":
316+
run(
317+
True # Show plots
318+
)
319+
320+

src/tests/test_scenarioTest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
, 'scenarioTwoChargedSC'
6565
, 'scenarioRendezVous'
6666
, 'scenarioSensorThermal'
67+
, 'scenarioHaloOrbit'
6768
])
6869
@pytest.mark.scenarioTest
6970
def test_scenarioBskScenarios(show_plots, scenarioCase):

0 commit comments

Comments
 (0)