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. 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:
- Alice's clock runs slow because of time dilation. This is programmed into the animation.
- The mouse coordinates are pixel coordinates of the cursor.
- Click anywhere in the animation scene to pause the animation. Click again to restart the animation.
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