Content-Type: multipart/mixed;
boundary="Boundary-01=_V5crNIKNIDnGF0v"
Content-Transfer-Encoding: 7bit
--Boundary-01=_V5crNIKNIDnGF0v
Content-Type: Text/Plain;
charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
Hi list,
I've been working on using PyX to plot Minkowski spacetime diagrams for wor=
k.=20
My first attempt used the basic plotting functions to do the job, but I was=
a=20
unsatisfied with not making use of PyX's powerful graphing capabilities. So=
=20
I've made a second attempt, this time implementing classes that extend thos=
e=20
defined by PyX, so that it integrates nicer.
I'm posting this because (a) someone else might also find it useful, and (b=
)=20
I'd appreciate feedback.
It uses scipy (for speed of light) and numpy (for dot product and array),=20
neither of which is probably necessary (but I'm lazy). The way it works is=
=20
that you define events (represented by the event class) that occur at a=20
location in 4D spacetime in some inertial frame (represented by the frame=20
class). (It might seem like overkill to consider all 4 dimensions, but it w=
as=20
necessary in my case.)
The events are collected in a chronology which acts as a data provider (by=
=20
projecting the 4D events down to a time and position along a line in space)=
=2E=20
The minkowski class inherits graphxy and keeps track of the inertial frame =
in=20
which the events are drawn, and the line in space onto which the events are=
=20
projected.
I've also created a style that draws a forward and backward lightcone (whic=
h=20
can even be filled; transparent fills work nicely). I've included a test fi=
le to=20
see how it basically works.
One thing that I am a bit stumped on is how I would have this system draw t=
he=20
axes of an alternative frame that is moving at some velocity relative to th=
e=20
one in which the events are drawn (the "local" frame). The axes for such a=
=20
frame "squeeze" together, i.e. the x and t axes rotate in opposite directio=
ns.=20
I'm not sure how to approach that. One idea I had was to extend the functio=
n=20
of minkowski to draw these axes manually, but even that has me unsure of ho=
w=20
to proceed. Any suggestions?
Peace,
Brendon
--Boundary-01=_V5crNIKNIDnGF0v
Content-Type: text/x-python;
charset="UTF-8";
name="minkowski.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="minkowski.py"
# -*- coding: utf-8 -*-
from pyx import *
from math import *
from scipy import constants
from numpy import array, dot
# Trafos
def lorentz_alpha(v):
return sqrt(1 - (v/constants.c)**2)
def lorentz_gamma(v):
return 1.0/lorentz_alpha(v)
#def lorentz_space(x, t, v):
#return lorentz_gamma(v)*(x - v*t)
#def lorentz_time(x, t, v):
#return lorentz_gamma(v)*(t - v*x/constants.c**2)
def lorentz_trafo(v):
s =3D sqrt(dot(v, v))
nv =3D v/s
g =3D lorentz_gamma(s)
mgv =3D -g*v
gm1nv =3D (g - 1.)*nv
return array([[g, mgv[0], mgv[1], mgv[2]],
[mgv[0], gm1nv[0]*nv[0] + 1., gm1nv[1]*nv[0], gm1nv[2]*nv[0]],
[mgv[1], gm1nv[0]*nv[1], gm1nv[1]*nv[1] + 1., gm1nv[2]*nv[1]],
[mgv[2], gm1nv[0]*nv[2], gm1nv[1]*nv[2], gm1nv[2]*nv[2] + 1.]])
class frame:
"""An inertial reference frame in which events may take place. Frames have
common origins but may be moving relative to each other."""
=09
def __init__(self):
# These have to be initialized in the constructor otherwise every
# instance points to the same dictionary object.
self.relatives_v =3D dict() #: Relative velocities of other frames
self.relatives_t =3D dict() #: Lorentz transformation matrices to other =
frames
=09
def equivalent(self, other):
return self is other or self.relatives_v =3D=3D other.relatives_v
=09
def relate(self, other, v):
"""Relative to this frame, the other frame is moving at v."""
self.relatives_v[other] =3D v
self.relatives_t[other] =3D lorentz_trafo(v)
other.relatives_v[self] =3D -v
other.relatives_t[self] =3D lorentz_trafo(-v)
=09
def related(self, other):
return other in self.relatives
class event:
"""Some event that occurs in spacetime, defined in terms of some reference
frame."""
=09
def __init__(self, f, x, label =3D None):
"""An event is a point in spacetime.
=09
f: the reference frame in which the event is defined
x: location of the event, i.e. array([t, x, y, z])
label: The label to draw on the Minkowski diagram, or None
"""
=09
self.f =3D f
self.x =3D x
self.label =3D label
=09
def is_local(self, other):
"""Does the event occur within the lightcone of another, such that they c=
ould communicate locally?"""
d =3D self.x - other.x
# Even though position changes in different frames, the lightcone test is=
valid in all
return (constants.c * d[0])**2 >=3D (d[1]**2 + d[2]**2 + d[3]**2)
=09
def in_frame(self, f):
"""Return an event equivalent to this defined in the given reference
frame f. Will return self if the frame is the one used for definition.
Will fail if the frames are not related.
"""
if f.equivalent(self.f):
return self
return event(f, dot(self.f.relatives_t[f], self.x))
=09
def lightcone_timelead(self, other):
"""Returns how many seconds this event occurs before the lightcone of
an other event reaches this location. Time is given in self's frame.
May be negative if this event is within the lightcone of the other
event."""
d =3D self.x - other.in_frame(self.f).x
dist =3D sqrt(d[1]**2 + d[2]**2 + d[3]**2)
return dist/constants.c - d[0]
# An issue:
# 1) How to draw the slanted axes of moving frames?
# Old code to illustrate:
# def axes(self, frame =3D None, xlabel =3D '$x$', tlabel =3D '$t$', attrs =
=3D [deco.earrow.normal]):
# alpha1 =3D 0.0
# alpha2 =3D pi/2.0
# if frame is not None and not frame.equivalent(self.frame):
# # Determine the velocity in the projected direction
# v =3D dot(self.frame.relatives_v[frame], self.space)
# alpha1 =3D atan(v/constants.c*self.tfactor/constants.c/self.xfactor)
# alpha2 =3D atan(constants.c/v*self.tfactor/constants.c/self.xfactor)
# hsize =3D self.size/2.0
# hsca1 =3D hsize*cos(alpha1)
# hssa1 =3D hsize*sin(alpha1)
# hsca2 =3D hsize*cos(alpha2)
# hssa2 =3D hsize*sin(alpha2)
# self.canvas.insert(vector(-hsca1, -hssa1, hsca1, hssa1, xlabel, pos =3D =
1, attrs =3D attrs))
# self.canvas.insert(vector(-hsca2, -hssa2, hsca2, hssa2, tlabel, pos =3D =
1, attrs =3D attrs))
class chronology(graph.data._data):
"""A data provider for Minkowski diagrams: A story of events in spacetime,
automatically adjusted to the inertial frame of the diagram."""
=09
defaultstyles =3D graph.data.defaultsymbols
=09
def __init__(self, events, title=3DNone):
self.events =3D events
self.title =3D title
self.xname =3D "x"
self.yname =3D "y"
self.columns =3D {}
self.columnnames =3D [self.xname, self.yname]
=09
def dynamiccolumns(self, graph):
# NOTE graph must be a minkowski, so we can know what frame and
# projection line (spaceline) to use
frame =3D graph.frame
spaceline =3D graph.spaceline
dynamiccolumns =3D {self.xname: [], self.yname: []}
for e in self.events:
x =3D e.in_frame(frame).x
dynamiccolumns[self.xname].append(dot(x.take([1, 2, 3]), spaceline))
dynamiccolumns[self.yname].append(x[0])
return dynamiccolumns
class lightcone(graph.style._styleneedingpointpos):
"""A style to draw lightcones of events on a Minkowski diagram."""
=09
needsdata =3D ["vpos", "vposavailable"]
=09
def __init__(self, attrs=3D[deco.stroked([style.linestyle.dashed])]):
self.attrs =3D attrs
=09
def initdrawpoints(self, privatedata, sharedata, graph):
x0, y0 =3D graph.vpos_pt(0, 0)
x1, y1 =3D graph.vpos_pt(1, 1)
privatedata.lightconecanvas =3D canvas.canvas([canvas.clip(path.rect_pt(
x0, y0, x1 - x0, y1 - y0))])
=09
def drawpoint(self, privatedata, sharedata, graph, point):
if sharedata.vposavailable:
x_pt, y_pt =3D graph.vpos_pt(*sharedata.vpos)
# Figure out the graph extents
gx0, gy0 =3D graph.vpos_pt(0, 0)
gx1, gy1 =3D graph.vpos_pt(1, 1)
# Guess the lightcone directions
llx, lly =3D graph.pos_pt(point["x"] + constants.c, point["y"] - 1)
lux, luy =3D graph.pos_pt(point["x"] + constants.c, point["y"] + 1)
# Project them to the top/bottom of the graph
llhwidth =3D (llx - x_pt)*(gy0 - y_pt)/(lly - y_pt)
luhwidth =3D (lux - x_pt)*(gy1 - y_pt)/(luy - y_pt)
# Draw them as open triangles
privatedata.lightconecanvas.draw(path.path(
path.moveto_pt(x_pt - llhwidth, gy0),
path.lineto_pt(x_pt, y_pt),
path.lineto_pt(x_pt + llhwidth, gy0)), self.attrs)
privatedata.lightconecanvas.draw(path.path(
path.moveto_pt(x_pt - luhwidth, gy1),
path.lineto_pt(x_pt, y_pt),
path.lineto_pt(x_pt + luhwidth, gy1)), self.attrs)
=09
def donedrawpoints(self, privatedata, sharedata, graph):
graph.insert(privatedata.lightconecanvas)
class minkowski(graph.graphxy):
"""Construct a Minkowski spacetime diagram."""
=09
def __init__(self, frame, spaceline, xpos=3D0, ypos=3D0, width=3DNone, hei=
ght=3DNone,
ratio=3Dgraph.graph.goldenmean, backgroundattrs=3DNone,
x=3Dgraph.axis.linear(title=3D"$x$"), t=3Dgraph.axis.linear(title=3D"$t$=
")):
"""We use standard 0-centred axes. TODO No key, for now, but maybe it
would be useful."""
graph.graphxy.__init__(self, xpos=3Dxpos, ypos=3Dypos, width=3Dwidth, hei=
ght=3Dheight,
ratio=3Dratio, key=3DNone, backgroundattrs=3DNone, xaxisat=3D0, yaxisat=
=3D0,
x=3Dx, y=3Dt)
self.frame =3D frame
self.spaceline =3D spaceline
--Boundary-01=_V5crNIKNIDnGF0v
Content-Type: text/x-python;
charset="UTF-8";
name="minkowski_test.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="minkowski_test.py"
# -*- coding: utf-8 -*-
from minkowski import *
from pyx import *
from math import *
from scipy import constants
from numpy import array
f1 = frame()
f2 = frame()
f1.relate(f2, array([0.5*constants.c, 0.0, 0.0]))
e1 = event(f1, array([0.0, 0.0, 0.0, 0.0]))
e2 = event(f1, array([0.0001, 60000, 0.0, 0.0]))
m = minkowski(f1, array([1.0, 0.0, 0.0]), width=8.5,
x=graph.axis.linear(title="$x$", min=-120000, max=120000),
t=graph.axis.linear(title="$t$", min=-0.0002, max=0.0002))
d = chronology([e1, e2])
m.plot(d, styles=[graph.style.symbol(graph.style.symbol.circle), lightcone()])
#m.plot(d, styles=[graph.style.symbol(graph.style.symbol.circle)])
m.writePDFfile("minkowski_test")
--Boundary-01=_V5crNIKNIDnGF0v--