Earth Moon Distance

From wikiluntti

Introduction

The code

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider

# ============================================================
# PHYSICAL PARAMETERS
# ============================================================

G = 6.67430e-11
M_EARTH = 5.972e24
# Real Earth-Moon distance
R = 384_400e3              # metres
# Approximate circular orbital velocity
V = 1022.0                 # m/s
DISTANCE_SCALE = 1e9       # metres -> million km

# ============================================================
# VISUAL PARAMETERS
# ============================================================
EARTH_DRAW_RADIUS = 0.064
VECTOR_SCALE0 = 0.15
VECTOR_EVERY = 1

# ============================================================
# ORBITAL PERIOD
# ============================================================
ORBIT_PERIOD = 2 * np.pi * R / V

# ============================================================
# GEOMETRIC CONSTRUCTION
# ============================================================
def calculate_orbit(steps):

    # --------------------------------------------------------
    # One complete orbit is divided into equal angular steps.
    # --------------------------------------------------------
    dtheta = 2 * np.pi / steps
    tangential_distance = R * np.tan(dtheta)
    DT = tangential_distance / V

    # Start Moon at x = R
    moon = np.array([R, 0.0])

    starting_positions = []
    tangent_positions = []
    moon_positions = []

    velocity_vectors = []
    gravity_vectors = []

    for step in range(steps):
        moon_before = moon.copy()

        starting_positions.append( moon_before.copy() )

        radial = ( moon_before/ np.linalg.norm(moon_before) )
        tangent = np.array([-radial[1], radial[0] ])

        velocity_vector = tangent * V * DT
        velocity_vectors.append( velocity_vector.copy() )

        tangent_moon = ( moon_before + velocity_vector )
        tangent_positions.append( tangent_moon.copy() )
        tangent_distance = np.linalg.norm( tangent_moon )

        correction_distance = ( tangent_distance - R )
        gravity_direction = ( -tangent_moon / tangent_distance )

        gravity_vector = ( gravity_direction * correction_distance )
        gravity_vectors.append( gravity_vector.copy() )

        moon = (tangent_moon + gravity_vector )
        moon_positions.append( moon.copy() )

    return (
        np.array(starting_positions),
        np.array(tangent_positions),
        np.array(moon_positions),
        np.array(velocity_vectors),
        np.array(gravity_vectors),
        DT
    )


# ============================================================
# INITIAL VALUES
# ============================================================

STEPS0 = 10

(
    starting_positions,
    tangent_positions,
    moon_positions,
    velocity_vectors,
    gravity_vectors,
    DT
) = calculate_orbit(STEPS0)


# ============================================================
# FIGURE
# ============================================================

fig, ax = plt.subplots( figsize=(10, 10) )
plt.subplots_adjust(    bottom=0.20)
ax.set_aspect("equal")
ax.set_xlabel("x [million km]")
ax.set_ylabel("y [million km]")
ax.grid( True, alpha=0.3)
ax.set_title( "Geometric construction of one complete Moon orbit")

earth_circle = plt.Circle( (0, 0), EARTH_DRAW_RADIUS, color="royalblue", alpha=0.9, zorder=10)
ax.add_patch( earth_circle)

theta = np.linspace( 0, 2 * np.pi, 500)

orbit_line, = ax.plot(
    R * np.cos(theta) / DISTANCE_SCALE,
    R * np.sin(theta) / DISTANCE_SCALE,
    "--",
    color="gray",
    linewidth=1,
    alpha=0.6,
    label="Circular orbit"
)


# ============================================================
# MOON POSITIONS
# ============================================================

moon_plot, = ax.plot(
    moon_positions[:, 0] / DISTANCE_SCALE,
    moon_positions[:, 1] / DISTANCE_SCALE,
    "o",
    color="lightgray",
    markersize=8,
    alpha=0.8,
    label="Moon positions"
)


# Highlight starting Moon
moon_initial_plot, = ax.plot(
    [starting_positions[0, 0] / DISTANCE_SCALE],
    [starting_positions[0, 1] / DISTANCE_SCALE],
    "o",
    color="dimgray",
    markersize=11,
    zorder=20
)


# ============================================================
# VECTOR INDICES
# ============================================================

indices = np.arange( 0, STEPS0, VECTOR_EVERY )

velocity_positions = ( starting_positions[indices] / DISTANCE_SCALE)
velocity_u = ( velocity_vectors[indices, 0] / DISTANCE_SCALE * VECTOR_SCALE0 )
velocity_v = ( velocity_vectors[indices, 1] / DISTANCE_SCALE * VECTOR_SCALE0 )

velocity_quiver = ax.quiver(
    velocity_positions[:, 0],
    velocity_positions[:, 1],
    velocity_u,
    velocity_v,
    color="green",
    angles="xy",
    scale_units="xy",
    scale=1,
    width=0.004,
    alpha=0.7,
    label="Tangential velocity"
)

gravity_positions = ( tangent_positions[indices] / DISTANCE_SCALE)
gravity_u = ( gravity_vectors[indices, 0] / DISTANCE_SCALE * VECTOR_SCALE0)
gravity_v = ( gravity_vectors[indices, 1] / DISTANCE_SCALE * VECTOR_SCALE0)

gravity_quiver = ax.quiver(
    gravity_positions[:, 0],
    gravity_positions[:, 1],
    gravity_u,
    gravity_v,
    color="red",
    angles="xy",
    scale_units="xy",
    scale=1,
    width=0.004,
    alpha=0.7,
    label="Gravity / correction"
)

info = ax.text(
    0.02,
    0.98,
    "",
    transform=ax.transAxes,
    verticalalignment="top",
    fontsize=10,
    bbox=dict(
        boxstyle="round",
        facecolor="white",
        alpha=0.85
    )
)

# Steps slider
ax_steps = plt.axes( [0.15, 0.10, 0.70, 0.03])

slider_steps = Slider(
    ax_steps,
    "Steps / orbit",
    5,
    200,
    valinit=STEPS0,
    valstep=1
)


# Vector scale slider
ax_scale = plt.axes(    [0.15, 0.05, 0.70, 0.03])

slider_scale = Slider(
    ax_scale,
    "Vector scale",
    1,
    1.0,
    valinit=VECTOR_SCALE0
)


# ============================================================
# UPDATE FUNCTION
# ============================================================

def update(_):
    global velocity_quiver, gravity_quiver

    steps = int( slider_steps.val)
    vector_scale = ( slider_scale.val)

    (
        starting_positions,
        tangent_positions,
        moon_positions,
        velocity_vectors,
        gravity_vectors,
        DT
    ) = calculate_orbit(steps)

    moon_plot.set_data( moon_positions[:, 0] / DISTANCE_SCALE, moon_positions[:, 1] / DISTANCE_SCALE )

    # Starting Moon position
    moon_initial_plot.set_data([starting_positions[0, 0] / DISTANCE_SCALE],[starting_positions[0, 1] / DISTANCE_SCALE] )

    indices = np.arange( 0, steps, VECTOR_EVERY)

    velocity_quiver.remove()
    gravity_quiver.remove()

    velocity_positions = ( starting_positions[indices] / DISTANCE_SCALE )
    velocity_u = ( velocity_vectors[indices, 0] / DISTANCE_SCALE * vector_scale )
    velocity_v = ( velocity_vectors[indices, 1] / DISTANCE_SCALE * vector_scale )

    velocity_quiver = ax.quiver(
        velocity_positions[:, 0],
        velocity_positions[:, 1],
        velocity_u,
        velocity_v,
        color="green",
        angles="xy",
        scale_units="xy",
        scale=1,
        width=0.004,
        alpha=0.7
    )

    gravity_positions = ( tangent_positions[indices] / DISTANCE_SCALE )
    gravity_u = ( gravity_vectors[indices, 0] / DISTANCE_SCALE * vector_scale)
    gravity_v = ( gravity_vectors[indices, 1] / DISTANCE_SCALE * vector_scale)

    gravity_quiver = ax.quiver(
        gravity_positions[:, 0],
        gravity_positions[:, 1],
        gravity_u,
        gravity_v,
        color="red",
        angles="xy",
        scale_units="xy",
        scale=1,
        width=0.004,
        alpha=0.7
    )

    green_length = np.linalg.norm(velocity_vectors[0])
    red_length = np.linalg.norm(gravity_vectors[0])
    green_fraction = (green_length / R * 100)
    red_fraction = (red_length / R * 100)

    # ========================================================
    # INFORMATION
    # ========================================================

    circular_velocity = np.sqrt(G * M_EARTH / R)

    info.set_text(
        f"Earth-Moon distance = {R / 1000:,.0f} km\n"
        f"Moon velocity = {V:.1f} m/s\n"
        f"Circular velocity = {circular_velocity:.1f} m/s\n"
        f"Orbit period = {ORBIT_PERIOD / 86400:.2f} days\n"
        f"Steps / orbit = {steps}\n"
        f"DT per step = {DT / 86400:.2f} days\n"
        f"\n"
        f"Green vector = {green_length / 1000:,.0f} km"
        f" ({green_fraction:.1f}% of R)\n"
        f"Red vector = {red_length / 1000:,.0f} km"
        f" ({red_fraction:.1f}% of R)"
    )


    # ========================================================
    # REDRAW
    # ========================================================

    fig.canvas.draw_idle()


slider_steps.on_changed(update)
slider_scale.on_changed(update)

update(None)

ax.legend(loc="lower right")

plt.show()