Rodney Dunning's Home Page | Research and Scholarship | VPython | The Twin Paradox


Introduction

Alice and Bob are twins. On their twentieth birthday, Alice departs the Earth in a rocket that carries her to Alpha Centauri, 4.3 light-years from the Earth, at a very high speed. When Alice returns, she is younger than Bob.

This animation was inspired by a short film I saw many years ago, made by Paul Hewitt.

Description

The animation shows the Earth, Alpha Centauri, and Alice's rocket ship. Text boxes serve to report the master clock reading on Earth, and Alice's x-coordinate and clock readings. Both the Earth and Alice generate a light signal each year. But of course, Alice's clock runs slow compared to the Earth's clock, so her signals occur with less frequency. The light signals are represented by rings. The entire animation is viewed from the Home Frame, which is attached to the Earth. The animation ends when Alice returns to the Earth.

When you execute the program, you are asked to enter the speed of Alice's rocket.

Screen Shots

Alice travels to Alpha Centauri.
Alice travels to Alpha Centauri. Signals are sent from the Earth and from Alice whenever either clock has measured a full year's time. In this image, we see that three signals have been sent from the Earth, but only one from Alice. The animation is viewed from a reference frame attached to the Earth and Alpha Centauri. Relative to clocks in this frame, Alice's clock is running slow by a factor of γ. In the simulation shown here, γ = 1.5118.

Suggested Use

In my modern physics class, I had my students imagine the light signals represented email messages sent by Bob (on Earth) and Alice. Each observer sends an email when he or she celebrates a birthday. I led students to first focus on Alice's rocket, and count off the number of emails she receives from Bob. The frequency increases considerably for the inbound portion of her journey. We repeated the exercise by focusing on Bob, and counting off the number of emails he receives from Alice. Again, the frequency changes between the outbound and inbound portions of the trip.

Hints:

Source Code

The source code appears between the bars below. Copy and paste the code into the Python IDLE environment, and hit F5 to run the code.


from visual import *
from __future__ import division
from Labels import labels

"""

Rodney Dunning
Assistant Professor of Physics
Longwood University

The twin paradox.

Alice travels to Alpha Centauri.
Bob remains at home.

Each twin generates a light pulse
when one year has passed on his or
her master clock.

Each twin records the number of pulses
received from the other twin, and
thus records the other twin's age.

"""
#------------------------------------------------
# The clock class
#------------------------------------------------

class clock:
    def __init__(self,
                 t = 0.0,
                 dt = 0.01,
                 t_max = 1000.0):

        self.t = t
        self.dt = dt
        self.t_max = t_max
        self.label = label(pos=(0,0,0),
                           text="Clock")

    def tick(self):
        self.t += self.dt

    def full_time(self):
        return self.t >= self.t_max

    def set_time(self,t):
        self.t = t

    def reset(self):
        self.t = 0

#----------------------------------
# The ship class
#----------------------------------

class ship:
    def __init__(self,
                 pos = vector(0,0,0), # position
                 vel = vector(0,0,0), # velocity
                 acc = vector(0,0,0), # acceleration
                 force = vector(0,0,0), # force
                 mass = 1.0, # common mass of the ships
                 L = 25.0, # common length of the ships
                 W = 10.0,  # common width of the ships
                 H = 5.0, # common height of the ships
                 fL = 25.0/7.0, #flag length
                 fW = 10.0/20.0, # flag width
                 fH = 5.0, #flag height
                 pR = 0.5, # common radius of the flag poles
                 theta = 0, # initial angle of the flag
                 d_theta = 0.01, #step size for rotating the flag
                 label_text = "Ship"):
                                  
        self.Lproper = L

        self.ship = frame(pos=pos,
                          vel=vel,
                          acc=acc,
                          mass=mass,
                          force=force)

        self.body = box(frame=self.ship,
                        length = L,
                        width = W,
                        height = H)
        
        self.flag = frame(frame=self.ship,
                          angle=0,
                          step=d_theta)

        self.pole = cylinder(frame=self.flag,
                             axis=(L/1.25,0,0),
                             radius = pR,
                             color = color.green)

        self.cloth = box(frame=self.flag,
                         height = fH,
                         width = fW,
                         length = fL,
                         pos = (L/1.25 - H/2, H/2,0), #within the frame!
                         color=color.red)

        self.label = label(frame = self.ship,
                           pos = vector(0,-1.5*H,0), #within the frame!
                           box = 0,
                           opacity = 0,
                           text = label_text)

    def move(self,dt): # moves the ship foward one step
        self.ship.pos += self.ship.vel * dt

    def rotate_flag(self):
        pi = acos(-1.0) #3.14159....
        self.flag.rotate(angle=self.flag.step,
                         axis=(0,0,1))
        self.flag.angle += self.flag.step

#----------------------------------------
# The light signal class.
#----------------------------------------

class light_signal:
    def __init__(self,
                 pos = vector(0,0,0), # initial position
                 thickness = 0.5, # thickness of the ring
                 radius = 0, # initial radius
                 rate = 1.0, # speed of light
                 color = color.white # color of the ring
                 ):

        self.body = ring(pos=pos, # initial position
                         thickness = thickness,
                         axis=(0,0,1),
                         radius = radius,
                         rate = rate,
                         color = color)

    def propagate(self,dt):
        self.body.radius += self.body.rate * dt


#-------------------------
# The labels class
#-------------------------


class labels:
    def __init__(self,
                 list = [] #empty list of labels
                 ):

        self.list = list

    def toggle(self):
        for x in self.list:
            x.visible = abs(1 - x.visible)

#-----------------------------------------
# Useful quantities
#
# SR units throughout
#-----------------------------------------

c = 1 # speed of light

earth_position = vector(-150.0, 0.0, 0.0)
earth_radius = 7.0

alpha_centauri_position = vector(150.0, 0.0, 0.0)
alpha_centauri_radius = earth_radius

trip_distance = mag(earth_position - alpha_centauri_position)

# The following is the pixels to light-years conversion,
# based on a 300 pixel separation between the Earth
# and Alpha Centauri.  This is also the number of time
# units in one year.

light_year = trip_distance / 4.3 # pixel conversion

signal_time = light_year # create a signal every year

#--------------------------------------------
# The user enters the speed of Alice's rocket
#--------------------------------------------

user_finished = 0 # set to 1 when finished

while(not user_finished):
    beta = input("Enter the speed of Alice's rocket (beta): ")
    print " " # spacer
    if(0 <= beta and beta < 1):
        user_finished = 1
        gamma = 1 / sqrt(1 - beta**2)
    else:
        print " " #spacer
        print "Input error.  Please enter a value between 0 and 1."

# time needed for the round-trip:
t_max =  2 * trip_distance / (beta * light_year)

print " " # spacer
print "1 light year = ", light_year, " pixels."
print "beta = ",beta
print "gamma = ",gamma
print "time needed = ",t_max
print " " # spacer
print "Thank you. Please find the animation window."

#----------------------------------------
# Specify the scene attributes
#----------------------------------------

#--------------------------------
# Scene definitions and functions
#--------------------------------

scene.title = "The Twin Paradox"
scene.x = 0
scene.y = 0
scene.width = 800
scene.height = 600
scene.range = (200,200,200)
scene.autoscale = 0 ##0 means autoscaling is OFF
scene.userzoom = 1 ##0 means user cannot zoom
scene.userspin = 1 ##0 means user cannot spin
scene.lights = [vector(0,0,1)]
scene.ambient = 0.5
scene.label = label(visible=0,
                    pos=(0,0,0),
                    xoffset = 0,
                    yoffset = 0,
                    text = ("Click to begin.\n"))
scene.mouse_label = label(visible=1,
                          pos=(0,100,0),
                          text="Mouse position")

def return_mouse_pos(scene):
    scene.mouse_label.text = ("mouse\n x: %3.2f   y: %3.2f"
                              %(scene.mouse.pos.x, scene.mouse.pos.y))

def check_for_pause(scene): #checks for pause request
    if scene.mouse.clicked:
        scene.mouse.getclick()
        pause(scene)

def pause(scene): #pauses the animation
    while 1:
        return_mouse_pos(scene)
        if scene.mouse.clicked:
            scene.mouse.getclick()
            break

#----------------------------------------
# Create a clock.
#----------------------------------------

earth_clock = clock(t = 0)
earth_clock.t_max = t_max * light_year
earth_clock.label.pos.x = earth_position.x
earth_clock.label.pos.y = earth_position.y - 20.0
earth_clock.label.text = "Earth Clock"

#----------------------------------------
# Create the Earth and Alpha Centauri
#----------------------------------------

earth = sphere(pos=earth_position,
               radius=earth_radius,
               color=(0.15,0.30,0.50))

alpha_centauri = sphere(pos=alpha_centauri_position,
                        radius = alpha_centauri_radius,
                        color = (0.70, 0.15, 0.25),
                        label = label(pos = (alpha_centauri_position.x,
                                             alpha_centauri_position.y -
                                                 2.1*alpha_centauri_radius,
                                             alpha_centauri_position.z),
                                      opacity = 0,
                                      text = "Alpha Centauri"))

#----------------------------------------
# Create Alice's rocket
#----------------------------------------

alice_rocket = ship(pos=earth_position,
                    vel=vector(beta*c,0,0),
                    acc=vector(0,0,0),
                    L = 10.0,
                    label_text="Alice")

# Alice will not need her flag for this animation.
alice_rocket.pole.visible = 0
alice_rocket.cloth.visible = 0

#----------------------------------------
# Create a list of labels to toggle on
# and off
#----------------------------------------

label_list = labels(list=(earth_clock.label,
                          scene.label,
                          scene.mouse_label,
                          alice_rocket.label))

#----------------------------------------
# Animate the scene
#----------------------------------------

ready = 0
label_list.toggle()
while(not ready):
    ready = scene.mouse.getclick()
    label_list.toggle()

EN = 1 # index number for the next signal from Earth
AN = 1 # index number for the next signal from Alice

E_signals = [] # list of signals from Earth
A_signals = [] # list of signals from Alice

while(not earth_clock.full_time()):
    rate(1600)
    check_for_pause(scene)
    return_mouse_pos(scene)

    earth_clock.tick()
    earth_clock.label.text = "Clock t = %3.2f" %(earth_clock.t / light_year)

    alice_position = alice_rocket.ship.pos.x - earth.pos.x
    alice_time = earth_clock.t / gamma

    alice_rocket.move(earth_clock.dt)
    alice_rocket.label.text = ("Alice \n x = %3.2f \n t = %3.2f"
                               %( alice_position / light_year,
                                  alice_time / light_year))

    if(mag(alice_rocket.ship.pos - earth.pos) > trip_distance):
        alice_rocket.ship.vel *= -1

    if(earth_clock.t > EN * signal_time):
        E_signals.append(light_signal(pos = earth.pos))
        EN += 1

    for x in E_signals:
        x.propagate(earth_clock.dt)
        if(x.body.radius > 1.05*trip_distance): x.body.visible = 0

    if(alice_time > AN * signal_time):
        A_signals.append(light_signal(pos = alice_rocket.ship.pos,
                                      color = color.yellow))
        AN += 1

    for x in A_signals:
        x.propagate(earth_clock.dt)
        if(x.body.radius > 1.05*mag(x.body.pos - earth.pos)): x.body.visible = 0