scenarioMJEarthMoonGravity

Overview

This tutorial demonstrates how to add Earth and Moon gravity to a MJScene with simIncludeGravBody. The same MuJoCo spacecraft and gravity-factory setup can be driven by either:

Select the ephemeris source through the useSpice argument to run(). The default is True. For example, the analytic ephemeris case is run with:

run(showPlots=True, useSpice=False)

Gravity-factory setup

Both cases begin by creating the same shared gravity-body descriptors. Earth is marked as the central body because the MuJoCo spacecraft state is initialized relative to Earth:

gravFactory = simIncludeGravBody.gravBodyFactory()
earth = gravFactory.createEarth()
earth.isCentralBody = True
moon = gravFactory.createMoon()

Each descriptor contains the body’s gravity model, gravitational parameter, central-body flag, and a planet-state input message. The only difference between the two cases is which ephemeris output is connected to those input messages.

Using SPICE planet states

The SPICE helper uses the bodies already present in the factory to configure and connect its output messages:

spiceObject = gravFactory.createSpiceInterface(
    time="2025 NOVEMBER 15 12:00:00.000",
    epochInMsg=True,
)
spiceObject.zeroBase = "Earth"
scene.AddModelToDynamicsTask(spiceObject, 75)

The factory creates the SPICE output messages in gravity-body insertion order, so the first output is Earth and the second is the Moon in this example. createSpiceInterface() subscribes each gravity body’s planetBodyInMsg to the corresponding output automatically.

Setting zeroBase to Earth makes the planet states and the spacecraft state Earth-relative. This is convenient but is not required by NBodyGravity; it only requires all gravity-source states to use one consistent inertial origin.

Using analytic PlanetEphemeris states

The analytic alternative names its output bodies and supplies one heliocentric classical-element set for each body:

planetObject = planetEphemeris.PlanetEphemeris()
planetObject.setPlanetNames(
    planetEphemeris.StringVector(["earth", "moon"])
)
planetObject.planetElements = planetEphemeris.classicElementVector(
    [earthElements, moonElements]
)
planetObject.zeroBase = "earth"
earth.planetBodyInMsg.subscribeTo(planetObject.planetOutMsgs[0])
moon.planetBodyInMsg.subscribeTo(planetObject.planetOutMsgs[1])
scene.AddModelToDynamicsTask(planetObject, 75)

Unlike the factory’s SPICE helper, a separately created PlanetEphemeris module does not know which gravity descriptors it should drive. Its output messages are therefore connected explicitly.

PlanetEphemeris propagates every supplied element set independently about the Sun. It does not implement a hierarchical Earth-Moon model. To provide a reasonable local approximation, this example constructs an initial heliocentric Moon state by adding a geocentric lunar state to Earth’s heliocentric state, then converts the result to heliocentric elements. This is useful for short examples and deterministic tests. SPICE should generally be preferred when accurate Earth-Moon geometry over long intervals is required.

Setting zeroBase to Earth translates both outputs after propagation. The Earth message is therefore zero and the Moon message is Earth-relative. This keeps the analytic planet messages in the same frame as the Earth-relative MuJoCo state, which is required for correct Vizard placement. The selected zero-base body must be configured on this same PlanetEphemeris instance.

Execution ordering and committed-state outputs

The ephemeris execution order is critical. Basilisk executes models with higher numeric priorities first; model insertion order in the Python script does not determine execution order. This example explicitly enforces the following sequence:

Top-level task:
    MJScene (0) -> recorders and Vizard

MJScene dynamics task:
    forward kinematics (10000) -> ephemeris (75) -> NBodyGravity (-1)

MuJoCo’s adaptive integrator evaluates dynamics between top-level task ticks. The ephemeris must therefore run in the scene dynamics task before NBodyGravity so that every force evaluation uses planet positions for the current integrator time:

scene.AddModelToDynamicsTask(
    ephemeris, EPHEMERIS_PRIORITY
)

Integrator stages use provisional states. Without an additional evaluation, messages written by dynamics-task models can therefore describe the last integrator stage rather than the state committed at the end of the step. This example enables:

scene.extraEoMCall = True

After integration, MJScene then executes its dynamics task once more at the top-level task time using the committed MuJoCo state. This final evaluation refreshes the forward-kinematics, ephemeris, and gravity outputs before the recorders and Vizard execute. It does not advance or otherwise change the integrated state. Because this final dynamics-task evaluation supplies the current ephemeris messages, the ephemeris module does not also need to be added to the top-level task.

Attaching gravity to MuJoCo

After either ephemeris has been configured, one factory call completes the MuJoCo gravity setup:

gravity = gravFactory.addBodiesTo(scene)

The factory creates one NBodyGravity model, adds Earth and the Moon as gravity sources, and adds every MuJoCo body as a gravity target. For the articulated CubeSat used here, gravity is applied at the center of mass of both the hub and the reaction wheel.

Illustration of Simulation Results

The scenario test runs both ephemeris cases and saves three figures per case. The first shows the spacecraft trajectory in its orbital plane, including the Earth surface and the required \(R_E + 400\) km minimum-radius boundary. The initial orbit has a 700 km periapsis altitude, and the scenario checks the recorded three-dimensional radius to ensure it remains above the 400 km boundary. The second figure shows the Earth-Moon geometry during the simulation, and the third shows osculating spacecraft elements and the magnitude of the Moon’s third-body acceleration.

../../_images/scenarioMJEarthMoonGravity_spice_orbit.svg ../../_images/scenarioMJEarthMoonGravity_spice_moonGeometry.svg ../../_images/scenarioMJEarthMoonGravity_spice_diagnostics.svg ../../_images/scenarioMJEarthMoonGravity_planetEphemeris_orbit.svg ../../_images/scenarioMJEarthMoonGravity_planetEphemeris_moonGeometry.svg ../../_images/scenarioMJEarthMoonGravity_planetEphemeris_diagnostics.svg
scenarioMJEarthMoonGravity.run(showPlots: bool = False, useSpice: bool = True) Dict[str, Figure][source]

Run the Earth-Moon gravity tutorial.

Parameters:
  • showPlots (bool, optional) – If True, display the generated figures. Defaults to False.

  • useSpice (bool, optional) – If True, use SPICE planet states. If False, use analytic PlanetEphemeris states. Defaults to True.

Returns:

Generated figures keyed by documentation image name.

Return type:

Dict[str, plt.Figure]