Skip to content

Commit

Permalink
(NOT FINISHED) Add ObserverSim
Browse files Browse the repository at this point in the history
  • Loading branch information
kurtamohler committed Aug 29, 2023
1 parent 069c0cf commit 5318430
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 0 deletions.
16 changes: 16 additions & 0 deletions examples/observer_sim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import spacetime as st

f = st.Frame()

f.append(st.Worldline([[0, 1, 0]], ends_vel_s=[0.1, 0]), 'observer')
f.append(st.Worldline([
[0, 0, 0],
[2, 1, 0],
[4, 0, 1],
[6, -1, 0],
[8, 0, -1],
], ends_vel_s=[0, 0]))

sim = st.ObserverSim(f)

import pygame
2 changes: 2 additions & 0 deletions spacetime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
)
from .frame import Frame
from .worldline import Worldline
from .observer import ObserverSim
from .error_checking import check

from numpy import asarray

del basic_ops
del frame
del worldline
del observer
del error_checking
76 changes: 76 additions & 0 deletions spacetime/observer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import numpy as np

import spacetime as st
from .error_checking import check, check_type

class ObserverSim:
'''
:class:`ObserverSim` simulates an observer's non-inertial reference frame
over time. This is done by calculating planes of simultaneity at increments
of proper time along an observer's worldline. Each plane of simultaneity
corresponds to the measurable state of all the particles in an inertial
reference frame that instanteneously coincides with the observer's
worldline at a particular event and velocity.
It is possible to update the future worldline of the observer at any
simulation step. This allows the simulation to be run on real time data.
For example, :class:`ObserverSim` can be used to create an interactive game
that follows the rules of special relativity.
In order to preserve floating point accuracy, :class:`ObserverSim` holds
onto a rest frame which is never boosted, though it may be shifted. If we
boost a reference frame repeatedly by some series of velocities, and then
we repeatedly boost it again by the negatives of each in reverse order, the
output is significantly different than the first frame. So to preserve
information that would otherwise be lost with successive boosts, we keep
a rest frame whose velocity never changes.
'''
def __init__(self, frame_rest, observer_name='observer'):
'''
Args:
frame_rest (:class:`Frame`):
A frame to use as a rest frame whose velocity never changes. The
simulation begins where the rest frame's plane of simultaneity at
time 0 intersects with the observer's worldline.
observer_name (str):
Name of the observer :class:`Worldline`, which must be included in
:attr:`frame_rest`.
'''
check_type(frame_rest, st.Frame, 'frame_rest')
check_type(observer_name, str, 'observer_name')

self.observer_name_ = observer_name

# Center the rest frame's origin onto the observer
# TODO: Consider not only doing space offsets, so time coordinates are
# maintained.
offset = frame_rest[self.observer_name_].eval(0)
self.frame_rest_ = frame_rest - offset

self.cur_observer_vel_s_ = self.frame_rest_[self.observer_name_].eval_vel_s(0)

self.frame_observer_ = self.frame_rest_.boost(self.cur_observer_vel_s_)

def step(self, proper_time_delta):
# Find the event, in the observer's frame, that is `proper_time_delta`
# into the future. Then, boost that event's coordinates to the rest
# frame. Then center the rest frame on the event. Finally, boost the
# rest frame by the new

event_observer = self.frame_observer_[self.observer_name_].eval_proper_time(0, proper_time_delta)

offset = st.boost(event_observer, -self.cur_observer_vel_s_)
self.frame_rest_ = self.frame_rest_ - offset
self.cur_observer_vel_s_ = self.frame_rest_[self.observer_name_].eval_vel_s(0)
self.frame_observer_ = self.frame_rest_.boost(self.cur_observer_vel_s_)

def eval(self):
# TODO: This is a bad way to preserve the time offset
time_offset = np.zeros(self.frame_observer_.ndim)
time_offset[0] = self.frame_observer_[self.observer_name_].proper_time(0)
frame_observer_adjusted = self.frame_observer_ + time_offset
return frame_observer_adjusted.eval(time_offset[0])
2 changes: 2 additions & 0 deletions spacetime/worldline.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class Worldline:
# TODO: Try to think of shorter names than `proper_time_origin` and
# `proper_time_offset`. Maybe `tau_origin` and `tau_offset`?

# TODO: Probably `proper_time_origin` should just default to 0

def __init__(self,
vertices,
ends_vel_s=None,
Expand Down
114 changes: 114 additions & 0 deletions tests/test_spacetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@ def maybe_arraylike_close(a, b):
else:
return np.isclose(np.asarray(a), np.asarray(b)).all()

def are_frame_states_close(a_state, b_state):
if len(a_state) != len(b_state):
return False

for (a_name, a_coords, a_tau), (b_name, b_coords, b_tau) in zip(a_state, b_state):
if a_name != b_name:
return False

if not np.isclose(a_coords, b_coords).all():
return False

if not np.isclose(a_tau, b_tau):
return False

return True

def check_boost_event_1D(v, event):
t = event[0]
x = event[1]
Expand Down Expand Up @@ -937,5 +953,103 @@ def test_Frame_boost(self):

self.assertEqual(w1, w1_check)

def test_ObserverSim(self):
f = st.Frame()

f.append(st.Worldline([[0, 1, 0]], ends_vel_s=[0, 0]), 'observer')
f.append(st.Worldline([
[0, 0, 0],
[2, 1, 0],
[4, 0, 1],
[6, -1, 0],
[8, 0, -1],
], ends_vel_s=[0, 0]))

sequence = [
# proper_time, event_check
(0, [0, -1, 0]),
(1, [1, -0.5, 0]),
(2, [2, 0, 0]),
(3, [3, -0.5, 0.5]),
(4, [4, -1, 1]),
(5, [5, -1.5, 0.5]),
]

sim_generators = [
lambda: st.ObserverSim(f),
lambda: st.ObserverSim((f + [0, -1, 0]).boost([.1, -.2]) + [0, -10, 10]),
lambda: st.ObserverSim((f + [0, -1, 0]).boost([-.99, 0]) + [0, -1123.4, 590.234]),
lambda: st.ObserverSim((f + [0, -1, 0]).boost([.99, 0]) + [0, 484.234, -324.2]),
lambda: st.ObserverSim((f + [0, -1, 0]).boost([0, .99]) + [0, -484.234, 324.2]),
lambda: st.ObserverSim((f + [0, -1, 0]).boost([0, -.99]) + [0, -1000093.24, -34.234]),
]

for sim_gen in sim_generators:
sim = sim_gen()
for proper_time, event_check in sequence:
sim_state = sim.eval()
self.assertTrue(np.isclose(sim_state[1][1], event_check).all())
self.assertTrue(np.isclose(sim_state[0][2], proper_time))
sim.step(1)

# Test a frame where the observer accelerates
f = st.Frame()

dist = 4.367
speed = 0.7
total_time = 2 * dist / speed

alice = st.Worldline([
(total_time / 2, dist),
], past_vel_s=[0.7], future_vel_s=[-0.7], proper_time_origin=0)

bob = st.Worldline([
(0, 0),
(total_time, 0)
], [0])

carol = st.Worldline([
(0, -1),
(1, -.1),
(1.9, 0.5),
(3, 1.5),
(3.5, 1.1),
(4, 1.5),
(5, 2.45),
], past_vel_s=[-0.1], future_vel_s=[-.9])

alpha_centauri = st.Worldline([
(0, dist),
], [0])

f.append(alice, 'Alice')
f.append(bob, 'Bob')
f.append(carol, 'Carol')
f.append(alpha_centauri, 'Alpha Centauri')

sim = st.ObserverSim(f, 'Alice')

alice_total_proper_time = alice.proper_time_delta(0, total_time)

f_alice_first_leg = f.boost([speed])

tmp = alice.eval(total_time / 2)
f_alice_second_leg = (f - tmp).boost([-speed]) + [alice_total_proper_time/2, 0]

# An odd number of steps is best, to avoid hitting the middle vertex,
# where we may get one or the other reference frame.
num_steps = 9
for i in range(num_steps):
if i < (num_steps + 1) // 2:
state_check = f_alice_first_leg.eval(
i * alice_total_proper_time / num_steps)
else:
state_check = f_alice_second_leg.eval(
i * alice_total_proper_time / num_steps)
state = sim.eval()
self.assertTrue(are_frame_states_close(state, state_check))

sim.step(alice_total_proper_time / num_steps)

if __name__ == '__main__':
unittest.main()

0 comments on commit 5318430

Please sign in to comment.