The three-body problem is a classic orbital dynamics situation. You have three bodies, each with significant mass, all interacting gravitationally. This turns out to be a chaotic system, with no general closed-form solution. There are however a few stable configurations of the three-body problem.

In this tutorial, we will model one of the stable configurations from R. A. Broucke’s technical report “Period Orbits in the Restricted Three-Body Problem with Earth Moon Masses”.


Import Elodin and JAX

Our first step is to import Elodin and Jax into our environment:

from jax import numpy as np
from jax.numpy import linalg as la
import elodin as el

TIME_STEP = 1.0 / 120.0

TIME_STEP value is the duration of each tick in the simulation.


Setup Gravity Constraints

With all dependencies prepared, we can set Gravity Constraints:

GravityEdge = el.Annotated[el.Edge, el.Component("gravity_edge", el.ComponentType.Edge)]
G = 6.6743e-11

class GravityConstraint(el.Archetype):
    a: GravityEdge

    def __init__(self, a: el.EntityId, b: el.EntityId):
        self.a = el.Edge(a, b)

def gravity(
    graph: el.GraphQuery[GravityEdge],
    query: el.Query[el.WorldPos, el.Inertia],
) -> el.Query[el.Force]:
    def gravity_inner(force, a_pos, a_inertia, b_pos, b_inertia):
        r = a_pos.linear() - b_pos.linear()
        m = a_inertia.mass()
        M = b_inertia.mass()
        norm = la.norm(r)
        f = G * M * m * r / (norm * norm * norm)
        return el.SpatialForce.from_linear(force.force() - f)

    return graph.edge_fold(query, query, el.Force,, gravity_inner)

Add 1st & 2nd Body

Before we can do anything we’ll need an instance of a WorldBuilder, and with that we can spawn our first entities:

w = el.World()
a = w.spawn(
        world_pos=el.WorldPos.from_linear(np.array([0.8822391241, 0, 0])),
        world_vel=el.WorldVel.from_linear(np.array([0, 1.0042424155, 0])),
        inertia=el.SpatialInertia(1.0 / G),
            el.Pbr(el.Mesh.sphere(0.2), el.Material.color(25.3, 18.4, 1.0))
b = w.spawn(
        world_pos=el.WorldPos.from_linear(np.array([-0.6432718586, 0, 0])),
        world_vel=el.WorldVel.from_linear(np.array([0, -1.6491842814, 0])),
        inertia=el.SpatialInertia(1.0 / G),
            el.Pbr(el.Mesh.sphere(0.2), el.Material.color(10.0, 0.0, 10.0))

w.spawn(GravityConstraint(a, b), name="A -> B")
w.spawn(GravityConstraint(b, a), name="B -> A")

GravityConstraint tells the simulation to calculate gravity between the two objects.


Let’s try running the simulation. But first, let’s add a view port so we can observe the world:

        pos=[0.0, 0.0, 5.0],
        looking_at=[0.0, 0.0, 0.0],
    name="Viewport 1",

Now, we’re ready to start simulating:

sys = six_dof(TIME_STEP, gravity)
sim =, TIME_STEP)

At this moment, bodies will be flying off into space, so feel free to remove these last 2 lines for now.


Add the Third Body

And last but not least, we will add the third body which will make this a stable orbit.

c = w.spawn(
        world_pos=el.WorldPos.from_linear(np.array([-0.2389672654, 0, 0])),
        world_vel=el.WorldVel.from_linear(np.array([0, 0.6449418659, 0.0])),
        inertia=el.SpatialInertia(1.0 / G),
            el.Pbr(el.Mesh.sphere(0.2), el.Material.color(0.0, 10.0, 10.0))

w.spawn(GravityConstraint(a, c), name="A -> C")
w.spawn(GravityConstraint(b, c), name="B -> C")

w.spawn(GravityConstraint(c, a), name="C -> A")
w.spawn(GravityConstraint(c, b), name="C -> B")

Start the Simulation!

You can now update the simulation by pressing Update Sim or hitting Cmd-Enter.

sys = six_dof(TIME_STEP, gravity)
sim =, TIME_STEP)