# Special Relativity Grapher
A Python library for visualizing special relativity effects including time dilation, length contraction, loss of simultaneity, and classic paradoxes.
## Authors
- Albert Han
- Stephen He
- David Zhang
**For the interactive tutorial, see `tutorial.ipynb`**
## Features
- **Core Physics Simulations**: Accurate relativistic transformations and coordinate systems
- **Interactive Visualizations**: Animated demonstrations of relativistic effects
- **Classic Paradoxes**: Implementations of the pole-in-barn paradox and twin paradox
- **Minkowski Diagrams**: 3D spacetime visualizations
- **Educational Examples**: Ready-to-use demonstrations for teaching special relativity
This project provides a Python-based simulation environment to visualize fundamental concepts and classic paradoxes of special relativity. Using a custom `Simulation` class, it allows for the creation of objects in different inertial frames, transforms them to a ground frame, and animates their interactions. The simulations are primarily in 2D to simplify visualization while still being able to effectively demonstrate relativistic effects. Natural units are used, where the speed of light, c, is equal to 1.
-----
## 1\. Setting up the simulation:
### a). Simulation Class
For this project, we will use a `Simulation` Class to set up everything. The simulation class will consist of objects represented as a collection of points. We will be able to add objects at a given speed (relative to the base frame), i.e. in a certain frame, and the simulation class will transform everything into the ground frame. Our simulation will be based in 2D, just for simplicity. We are doing this, as opposed to 1D, so we can better simulate the paradoxes.
To Initialize the class, we will have these variables:
- `objects`
- `velocities`
- `frameVelocity`
- `events`
- `eventVelos`
`objects` is a 3D array, each entry of the array stores an object, represented as a 2D array, where each entry is a point that specifies points in the object. For our animation, we will just linearly interpolate and connect all the points to each other.
`velocities` is a 2D array, which has the same size as `objects`. Velocities stores the velocity of the object. But, you may be wondering, which frame are we measuring in? The velocity of this frame, relative to the ground frame, is stored in `frameVelocity`.
`events` stores space-time events, which will be plotted separately.
Finally, something to note is that we will be using natural units, where $c = 1$.
```python
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
from IPython.display import HTML
class Simulation():
def __init__(self):
self.objects = []
self.velocieis = []
self.frameVelocity =[]
self.events = []
self.eventVelos = []
#Adding the origin to the simulation when we initinalize
self.objects.append([[0,0,0]])
self.velocieis.append([0,0])
self.frameVelocity.append([0,0])
```
-----
### b). Adding Objects
Now, to add some simple objects. `addObject` is a generic class that can add any object. The other functions make it easier to add specific objects, and will ensure that they will be written in the right order for our animation.
```python
class Simulation(Simulation):
def addObject(self, ptList, velocity, frameVelo):
self.objects.append(ptList)
self.velocieis.append(velocity)
self.frameVelocity.append(frameVelo)
def addPt(self, fVec, velocity, frameVelo):
self.objects.append([fVec])
self.velocieis.append(velocity)
self.frameVelocity.append(frameVelo)
def addLine(self, startPt, L, velocity, frameVelo, doX, t):
line = [[t,startPt[0], startPt[1]]]
if(doX):
line.append([t, startPt[0] + L, startPt[1]])
if(not doX):
line.append([t, startPt[0], startPt[1] + L])
self.objects.append(line)
self.velocieis.append(velocity)
self.frameVelocity.append(frameVelo)
def addTrain(self, bottomLeft, L, H, velocity, frameVelo, t):
startPt = bottomLeft
train = [[t, bottomLeft[0], bottomLeft[1]], [t, startPt[0] + L, startPt[1]], \
[t, startPt[0] + L, startPt[1] + H], [t, startPt[0], startPt[1] + H], [t, bottomLeft[0], bottomLeft[1]]]
self.objects.append(train)
self.velocieis.append(velocity)
self.frameVelocity.append(frameVelo)
def addPerson(self, pos, velocity, frameVelo, size,t):
h = 0.5 * size
person = [[t,pos[0],pos[1]], [t, pos[0] + 0.5 * h, pos[1]], [t,pos[0] + 0.5 * h, pos[1] + h],\[t, pos[0] - 0.5 * h, pos[1]+h], [t,pos[0] - 0.5 * h, pos[1]], [t,pos[0],pos[1]],
[t,pos[0] + 0.5 * h, pos[1] - 1.5 * h], [t,pos[0] - 0.5 * h, pos[1] - 1.5 * h]]
self.objects.append(person)
self.velocieis.append(velocity)
self.frameVelocity.append(frameVelo)
def addEvent(self,event, fVelo):
self.events.append(event)
self.eventVelos.append(fVelo)
#A pulse is like a clock, where very dT seconds it adds an event
def addPulse(self, pt, dT, Ts, Tend, frameVelo):
range = np.linspace(Ts, Tend, dT, endpoint = True)
for i in range:
self.events.append([i, pt[0], pt[1]])
self.eventVelos.append(frameVelo)
```
-----
### c). Switching Frames
Now, some helper functions to help switch frame.
Some notes on the functions:
- `getGamma` returns the gamma value for the given velocity.
- `lorentzTransformPt` transforms a 4-vector from frame S to frame S', where S' is traveling at V w.r.t S.
- `addVelocities` returns the velocity of an object moving at `objectVelo` in Frame 1 (moving at `Vf1` w.r.t the ground frame) into Frame 2 (moving at `Vf2` w.r.t the ground frame). To do this, we will use `lorentzTranformPt` to transform the velocity 4-Vector from Frame 1 to the ground frame then to Frame 2.
- `lorentzTransformObject` applies a Lorentz transformation to every point in the object at a given time t and returns a new 4-vector with all the points. The main purpose of this function is to help with `getEOM`. Note: in the new frame, all the points may not be at the same time, which is why this is just a helper function.
- `getEOM` will return the equations of motion for an object, i.e. the x and y position as a function of the proper time. It takes an object traveling at `objectVelo` in Frame 1 (which is moving at `Vf1` w.r.t the ground frame). It returns the equations of motion, in the same order (which will be linear because everything has constant velocity) as a function of the proper time in Frame 2.
The EOMs are returned in the following form: If the EOMs are $$x = at + b$$ $$y = ct + d$$
For point n, then `getEOM` will return a vector where `vec[n-1]` stores `[[a,b],[c,d]]`.
```python
#Where Vf is relative to the ground grame
def getGamma(Vf):
return (1 - Vf[0] * Vf[0] - Vf[1] * Vf[1]) ** (-0.5)
def lorentzTranformPt(fourVec, V):
newFourVec = np.array(fourVec)
g = getGamma(V)
bx = V[0]
by = V[1]
g2 = g**2/(1 + g)
LTMat = np.array([
[g, -g * bx, -g * by],
[-g * bx, 1 + g2 * bx * bx, g2 * bx * by],
[-g * by, g2 * bx * by, 1 + g2 * by * by]
])
return LTMat @ newFourVec
#Vf1, Vf2 are relative to the ground frame
#We are assuming that the object is in frame 1
#Returns the velocity of object in Vf2
def addVelocities(objectVelo, Vf1, Vf2):
gamma = getGamma(objectVelo)
Vvec = [gamma, gamma * objectVelo[0], gamma * objectVelo[1]]
#Using -Vf1 since Frame 1 is moving at Vf1 wrt the ground, so the ground is
#moving at -Vf1 wrt Frame 1
GroundVec = lorentzTranformPt(Vvec, [-1 * Vf1[0], -1 * Vf1[1]])
Frame2Vec = lorentzTranformPt(GroundVec, Vf2)
return [Frame2Vec[1]/Frame2Vec[0], Frame2Vec[2]/Frame2Vec[0]]
def lorentzTransformObject(Object, V):
newObject4Vectors = []
for pt in Object:
newFVec = lorentzTranformPt(pt, V)
newObject4Vectors.append(newFVec)
return newObject4Vectors
def getEOM(Object, objectVelo, Vf1, Vf2):
Vf1wrtVf2 = addVelocities([0,0], Vf1, Vf2)
#We are using Vf1wrtVf2 since we are treating every endpoint of Object1 as
#a point in spacetime, so it has a coordinate in Frame 1, which we transform
#to frame 2. Then, we wait a few seconds and then transform the object to
#frame 2. This gives us two points in frame 2 we can interpolate
#We shoudln't run into any issues with similenatity since points on the two
#Events will be timelikely separated
Object1 = lorentzTransformObject(Object, Vf1wrtVf2)
dT = 1
Object2 = []
for pt in Object:
newPt = [pt[0] + dT, pt[1] + dT * objectVelo[0], pt[2] + dT*objectVelo[1]]
Object2.append(newPt)
Object2 = lorentzTransformObject(Object2, Vf1wrtVf2)
EOMS = []
for i in range(len(Object1)):
Tps = [Object1[i][0], Object2[i][0]]
Xps = [Object1[i][1], Object2[i][1]]
Yps = [Object1[i][2], Object2[i][2]]
xLine = np.polyfit(np.array(Tps), np.array(Xps), 1)
yLine = np.polyfit(np.array(Tps), np.array(Yps), 1)
EOMS.append([xLine, yLine])
return EOMS
```
-----
### d). Running the Simulation
Now, to actually run the simulation. To do this, we will make some simplifying assumptions for our model. Every object added has a constant velocity (something usually done in intro relativity). However, this is useful because this means that the velocity of every object is constant in every frame\! This allows us to calculate two points in each object's space-time path, and linearly interpolate for any frame. The high-level overview is as follows:
1. The function, `runSimulation(frameVelocity, dT, Ts, Tend, condition)` takes the velocity of the frame, the time step, the total time, and a condition to stop at.
2. Transform the coordinates and velocities of every object and event to the given frame, using the helper functions above.
3. Generate an equation for the position of the object, as a function of the proper time.
4. Get as many points as necessary, this part will just consist of plugging everything into the equation.
Some caveats to this method are we can't have discontinuous changes in velocity, ex, starting and stopping. To fix this, we can splice multiple animations together (Making sure our frames are consistent). To do this, we can stop our simulation until a condition, read out the values (in a single frame), change the velocity of an object, and start a new simulation.
`condition` will be a function that returns true or false and takes the current time. It can be specified with a lambda function before the simulation is run. For example, if we wanted to stop when an event occurred, we can find the space-time coordinate of the event beforehand, then have `condition` return false after the event occurs.
The simulation will return an array of points for all the objects. This array will look like `objects[t][i][j][pos]`, where `t` is the time step, `i` is the object number, `j` is the number of each point in the object, and `pos` is $x$ or $y$. Events will also be stored in this array, and they will have a point for a small time interval (\~50 frames after they occur).
```python
class Simulation(Simulation):
def runSimulation(self, frameVelocity, dT, Ts, Tend, condition):
newEvents = []
objectEOMS = []
for i in range(len(self.events)):
relVel = addVelocities([0,0], self.eventVelos[i], frameVelocity)
newEvents.append(lorentzTranformPt(self.events[i], relVel))
for i in range(len(self.objects)):\
EOM = getEOM(self.objects[i], self.velocieis[i],\\
self.frameVelocity[i], frameVelocity)
objectEOMS.append(EOM)
times = []
objects = []
currentT = Ts
while(currentT < Tend and condition(currentT)):
times.append(currentT)
timeStepObjects = []
for EOM in objectEOMS:
currentObject = []
for pt in EOM:
Xpos = pt[0][0] * currentT + pt[0][1]
Ypos = pt[1][0] * currentT + pt[1][1]
vec = [Xpos, Ypos]
currentObject.append(vec)
timeStepObjects.append(currentObject)
for event in newEvents:
if(np.abs(event[0] - currentT) < 10 * dT):
timeStepObjects.append([[event[1], event[2]]])
# times.append(currentT)
objects.append(timeStepObjects)
currentT = currentT + dT
return times, objects
```
-----
### e). Minkowski Diagrams
Since our simulation can have a lot of points and frames, especially with the objects and since our simulation is 2D (So the Minkowski diagram is 3D), we're going to make a Minkowski diagram class that will plot points and two different space-time axes. The first frame is assumed to be at rest. To plot moving objects, we can tell it to plot multiple points. The objects should be entered in the same format as before, a list of space-time coordinates with $(t,x,y)$.
Just to make the plots easier to see, the Minkowski function will only show the axis if the velocity has a positive x and y component (Since otherwise the axis get squished the other way, and plotting all 8 quadrants is hard to see).
```python
def Minkowski(frame2Velo, frame1Objects, frame2Objects, Ielev=30, Iazi=45):
vPar = frame2Velo
vPerp = np.array([0, frame2Velo[1], -1 * frame2Velo[0]])
vMag = (vPar[0] * vPar[0] + vPar[1] * vPar[1])** 0.5
vUnit = np.array(vPar)/vMag
vPerpUnit = vPerp/vMag
vParUnit = np.array(vPar)/vMag
newTime = np.array([1, vPar[0], vPar[1]])
newV = np.array([vMag, vUnit[0], vUnit[1]])
tUnit = newTime/(np.linalg.norm(newTime))
vUnit = newV/np.array(np.linalg.norm(newV))
Uprime = ( (1 + vMag ** 2) /(1 - vMag**2 ) ) ** 0.5
tUnit = Uprime * tUnit
vUnit = Uprime * vUnit
newX = (vParUnit[0] * vUnit + vParUnit[1] * vPerpUnit)
newY = (vParUnit[1] * vUnit - vParUnit[0] * vPerpUnit )
newT = tUnit
xAxis = []
yAxis = []
tAxis = []
for i in range(50):
xAxis.append(newX * i)
yAxis.append(newY * i)
tAxis.append(newT * i)
xAxis = np.array(xAxis)
yAxis = np.array(yAxis)
tAxis = np.array(tAxis)
newFrame2 = []
for obj in frame2Objects:
objArr = []
for pt in obj:
newPt = pt[0] * newT + pt[1] * newX + pt[2] * newY
objArr.append(newPt)
newFrame2.append(objArr)
newFrame2 = np.array(newFrame2)
Axis = np.linspace(0,50, 50)
Zeros = np.zeros(50)
#Plotting
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_xlim(0, 50)
ax.set_ylim(0, 50)
ax.set_zlim(0, 50)
ax.plot(Axis, Zeros, Zeros, label = "x", color = "dodgerblue")
ax.plot(Zeros, Axis, Zeros, label = "y", color = "deepskyblue")
ax.plot(Zeros, Zeros, Axis, label = "t", color = "lightskyblue")
ax.plot(xAxis[:,1], xAxis[:,2], xAxis[:,0], label = "x'", color = "red")
ax.plot(yAxis[:,1], yAxis[:,2], yAxis[:,0], label = "y'" , color = "firebrick")
ax.plot(tAxis[:,1], tAxis[:,2], tAxis[:,0], label = "t'" , color = "salmon")
for obj in frame1Objects:
obj = np.array(obj)
ax.plot(obj[:,0], obj[:,1], obj[:,2], 'o', markersize = 2)
for obj in newFrame2:
obj = np.array(obj)
ax.plot(obj[:,0], obj[:,1], obj[:,2], 'o', markersize = 2)
ax.legend()
ax.view_init(elev=Ielev, azim=Iazi)
plt.title("Minkowski Diagram")
plt.show()
Minkowski([0.5, 0.5] , [[[10,10,10], [15,10,10], [20,10,10]], [[5,20,20]]], [[[15,15,15]] ], 30, 25)
```
-----
### f). Animation
Finally, it's time to animate\! `RelatavisticAnimation` will take a list of simulations, all of which are outputs from `runSimulation`. It takes a list so we can stitch together multiple simulations to get discontinuities in the velocity.
Note: for the stitching to work properly, the objects must be in the same order in all simulations.
```python
#This animaition code is based off the chaos assignment
#plotlimits[0] - xmin, xmax, ymin, ymax
#This is so lenth contraction will properly show
def RelatavisticAnimation(simulations, plotLimits, title):
maxObjectNum = 0
AllSimulations = []
for simulation in simulations:
for timeStep in simulation:
AllSimulations.append(timeStep)
if(len(timeStep) > maxObjectNum):
maxObjectNum = len(timeStep)
fig, ax = plt.subplots()
ax.set_xlim(plotLimits[0],plotLimits[1])
ax.set_ylim(plotLimits[2],plotLimits[3])
plt.title(title)
lines=[]
for i in range(maxObjectNum):
line, = ax.plot([], [], '-', marker='o')
lines.append(line)
def update(i, AllSimulations,lines, maxObjNum):
currentTs = AllSimulations[i]
for j in range(maxObjNum):
if(j < len(currentTs)):
obj = np.array(currentTs[j])
lines[j].set_data(obj[:,0],obj[:,1])
else:
lines[j].set_data([], [])
return lines
ll=1
ani = animation.FuncAnimation(fig, update, len(AllSimulations), fargs=[AllSimulations, lines, maxObjectNum],
interval=20, blit=True,repeat=False)
plt.close()
return ani
```
-----
## 2\. The Fundamental Effects:
Now, it's time for the classic experiments, starting with Time Dilation. For this, we won't use laser pulses (since it would be too hard to animate), instead, we'll show a relativistic "train" (visualized with a box) with a clock which will fire a pulse at consistent intervals (green dot). Note the origin is the blue dot.
### a). Time Dilation
As we can see, the pulse looks slower in the ground frame. We also only see two pulses in the same proper time (and we also see the train length contracted\!).
### b). Length Contraction
Now for length contraction. We'll show a few fixed length rods moving at different speeds. The blue point is the origin and all the rods have the same proper length. All the rods have proper length 5c\*s, but they are going at different speeds. The orange rod is moving at 0.99c and the other two are moving at ± 0.5c respectively. Now, in a moving frame, going at 0.5c. We can also see that transverse length contraction doesn't exist.
### c). Loss of Simultaneity
For this simulation, we will have two events simultaneous in the ground frame, and show that they happen at different times in every other frame. Blue is again the origin and the red and green points are two different events. These events will occur in another "train". Now, for a different frame. (But now both green and orange because of matplotlib). We lose simultaneity even if we move in both x and y\!
-----
## 3\. The Classic Paradoxes
### a). Pole in a Barn
Now, for a classic paradox. Imagine someone (who's really fast) running at relativistic speeds. They are holding a pole with proper length L and run into a barn with proper length L. In the ground frame, the pole fits in the barn because of length contraction and close the doors, but in the moving frame, the barn is contracted and the pole is the usual length, so what happens? Let's find out\!
As we can see, the pole fits in the barn in the stationary frame, but in the moving frame, the barn doors don't close simultaneously\!
### b). The Twin Paradox
Finally, we will see the twin paradox. Imagine two twins, one which stays on Earth and one who goes on a rocket which goes out to a distant and then comes back to Earth. Each twin sees the other as younger because of time dilation, so when they meet back on Earth, who's older?
We will track how old the twins are by having each twin send uniformly spaced signals in their own frame, and see how many signals each twin sends in an inertial frame.
As we can see, since the twin on Earth sends more signals, the one on Earth is older! The key is that the spaceship frame is not an inertial frame, since the spaceship turns around.
---
## Library Usage
This project is now organized as a Python library. Here's how to use it:
### Installation
#### Development Installation
```bash
git clone https://github.com/bdavidzhang/Special-Relativity-Grapher.git
cd Special-Relativity-Grapher
pip install -e .
```
#### Development Dependencies
```bash
pip install -e ".[dev]"
```
### Quick Start
```python
from special_relativity_grapher import Simulation
from special_relativity_grapher.visualization import RelatavisticAnimation
from special_relativity_grapher.utils import trueCond
# Create a simulation
sim = Simulation()
# Add a moving rod
sim.addLine([0, 0], 5, [0, 0], [-0.8, 0], True, 0)
# Run simulation in ground frame
times, objects = sim.runSimulation([0, 0], 0.1, 0, 10, trueCond)
# Create animation
plot_limits = [-5, 10, -2, 3]
animation = RelatavisticAnimation([objects], plot_limits, "Length Contraction Demo")
# Save as gif
animation.save('length_contraction.gif', writer='pillow', fps=30)
```
### Examples
The library includes several complete examples demonstrating key relativistic effects:
#### Time Dilation
```python
from examples.time_dilation import time_dilation_demo, light_clock_demo
# Standard time dilation with moving clocks
train_ani, ground_ani = time_dilation_demo()
# Light clock demonstration
light_train_ani, light_ground_ani = light_clock_demo()
```
#### Length Contraction
```python
from examples.length_contraction import length_contraction_demo
# Rods at different velocities
ground_ani, moving_ani = length_contraction_demo()
```
#### Classic Paradoxes
```python
from examples.pole_in_barn import pole_in_barn_demo
from examples.twin_paradox import twin_paradox_demo
# Pole-in-barn paradox
ground_ani, pole_ani = pole_in_barn_demo()
# Twin paradox
earth_ani, spaceship_ani = twin_paradox_demo()
```
#### Loss of Simultaneity
```python
from examples.simultaneity import simultaneity_demo
# Events simultaneous in one frame but not another
ground_ani, moving_x_ani, moving_diag_ani = simultaneity_demo()
```
### Core API
#### Simulation Class
The main simulation class for managing objects, events, and reference frames.
**Key Methods:**
- `addLine(startPt, L, velocity, frameVelo, doX, t)`: Add a line segment
- `addTrain(bottomLeft, L, H, velocity, frameVelo, t)`: Add a rectangular object
- `addEvent(event, fVelo)`: Add a spacetime event
- `runSimulation(frameVelocity, dT, Ts, Tend, condition)`: Execute the simulation
#### Transformation Functions
- `getGamma(Vf)`: Calculate Lorentz gamma factor
- `lorentzTranformPt(fourVec, V)`: Apply Lorentz transformation to a 4-vector
- `addVelocities(objectVelo, Vf1, Vf2)`: Relativistic velocity addition
#### Visualization Functions
- `RelatavisticAnimation(simulations, plotLimits, title)`: Create animated visualizations
- `Minkowski(frame2Velo, frame1Objects, frame2Objects)`: Generate 3D Minkowski diagrams
### Testing
Run the test suite:
```bash
pytest tests/
```
### Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests for new functionality
5. Run the test suite
6. Create a Pull Request
## License
This project is licensed under the MIT License.
Raw data
{
"_id": null,
"home_page": null,
"name": "special-relativity-grapher",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "physics, relativity, visualization, education",
"author": "Albert Han, Stephen He",
"author_email": "David Zhang <davidzhang@example.com>",
"download_url": "https://files.pythonhosted.org/packages/cc/ec/c33ff53f8f93a567d9857dd3149884510c22c8c4782c840c8008dc29fce7/special_relativity_grapher-0.1.2.tar.gz",
"platform": null,
"description": "# Special Relativity Grapher\n\nA Python library for visualizing special relativity effects including time dilation, length contraction, loss of simultaneity, and classic paradoxes.\n\n## Authors\n- Albert Han\n- Stephen He \n- David Zhang\n\n**For the interactive tutorial, see `tutorial.ipynb`**\n\n## Features\n\n- **Core Physics Simulations**: Accurate relativistic transformations and coordinate systems\n- **Interactive Visualizations**: Animated demonstrations of relativistic effects\n- **Classic Paradoxes**: Implementations of the pole-in-barn paradox and twin paradox\n- **Minkowski Diagrams**: 3D spacetime visualizations\n- **Educational Examples**: Ready-to-use demonstrations for teaching special relativity\n\nThis project provides a Python-based simulation environment to visualize fundamental concepts and classic paradoxes of special relativity. Using a custom `Simulation` class, it allows for the creation of objects in different inertial frames, transforms them to a ground frame, and animates their interactions. The simulations are primarily in 2D to simplify visualization while still being able to effectively demonstrate relativistic effects. Natural units are used, where the speed of light, c, is equal to 1.\n\n\n\n-----\n\n\n\n## 1\\. Setting up the simulation:\n\n\n\n### a). Simulation Class\n\n\n\nFor this project, we will use a `Simulation` Class to set up everything. The simulation class will consist of objects represented as a collection of points. We will be able to add objects at a given speed (relative to the base frame), i.e. in a certain frame, and the simulation class will transform everything into the ground frame. Our simulation will be based in 2D, just for simplicity. We are doing this, as opposed to 1D, so we can better simulate the paradoxes.\n\n\n\nTo Initialize the class, we will have these variables:\n\n\n\n\u00a0 - `objects`\n\n\u00a0 - `velocities`\n\n\u00a0 - `frameVelocity`\n\n\u00a0 - `events`\n\n\u00a0 - `eventVelos`\n\n\n\n`objects` is a 3D array, each entry of the array stores an object, represented as a 2D array, where each entry is a point that specifies points in the object. For our animation, we will just linearly interpolate and connect all the points to each other.\n\n\n\n`velocities` is a 2D array, which has the same size as `objects`. Velocities stores the velocity of the object. But, you may be wondering, which frame are we measuring in? The velocity of this frame, relative to the ground frame, is stored in `frameVelocity`.\n\n\n\n`events` stores space-time events, which will be plotted separately.\n\n\n\nFinally, something to note is that we will be using natural units, where $c = 1$.\n\n\n\n```python\n\nimport numpy as np\n\nimport matplotlib.pyplot as plt\n\nfrom mpl_toolkits.mplot3d import Axes3D\n\nimport matplotlib.animation as animation\n\nfrom IPython.display import HTML\n\n\n\nclass Simulation():\n\n\u00a0 def __init__(self):\n\n\u00a0 \u00a0 self.objects = []\n\n\u00a0 \u00a0 self.velocieis = []\n\n\u00a0 \u00a0 self.frameVelocity =[]\n\n\u00a0 \u00a0 self.events = []\n\n\u00a0 \u00a0 self.eventVelos = []\n\n\u00a0 \u00a0 #Adding the origin to the simulation when we initinalize\n\n\u00a0 \u00a0 self.objects.append([[0,0,0]])\n\n\u00a0 \u00a0 self.velocieis.append([0,0])\n\n\u00a0 \u00a0 self.frameVelocity.append([0,0])\n\n```\n\n\n\n-----\n\n\n\n### b). Adding Objects\n\n\n\nNow, to add some simple objects. `addObject` is a generic class that can add any object. The other functions make it easier to add specific objects, and will ensure that they will be written in the right order for our animation.\n\n\n\n```python\n\nclass Simulation(Simulation):\n\n\u00a0 def addObject(self, ptList, velocity, frameVelo):\n\n\u00a0 \u00a0 self.objects.append(ptList)\n\n\u00a0 \u00a0 self.velocieis.append(velocity)\n\n\u00a0 \u00a0 self.frameVelocity.append(frameVelo)\n\n\n\n\u00a0 def addPt(self, fVec, velocity, frameVelo):\n\n\u00a0 \u00a0 self.objects.append([fVec])\n\n\u00a0 \u00a0 self.velocieis.append(velocity)\n\n\u00a0 \u00a0 self.frameVelocity.append(frameVelo)\n\n\n\n\u00a0 def addLine(self, startPt, L, velocity, frameVelo, doX, t):\n\n\u00a0 \u00a0 line = [[t,startPt[0], startPt[1]]]\n\n\u00a0 \u00a0 if(doX):\n\n\u00a0 \u00a0 \u00a0 line.append([t, startPt[0] + L, startPt[1]])\n\n\u00a0 \u00a0 if(not doX):\n\n\u00a0 \u00a0 \u00a0 line.append([t, startPt[0], startPt[1] + L])\n\n\u00a0 \u00a0 self.objects.append(line)\n\n\u00a0 \u00a0 self.velocieis.append(velocity)\n\n\u00a0 \u00a0 self.frameVelocity.append(frameVelo)\n\n\n\n\u00a0 def addTrain(self, bottomLeft, L, H, velocity, frameVelo, t):\n\n\u00a0 \u00a0 startPt = bottomLeft\n\n\u00a0 \u00a0 train = [[t, bottomLeft[0], bottomLeft[1]], [t, startPt[0] + L, startPt[1]], \\\n\n\u00a0 \u00a0 [t, startPt[0] + L, startPt[1] + H], [t, startPt[0], startPt[1] + H], [t, bottomLeft[0], bottomLeft[1]]]\n\n\n\n\u00a0 \u00a0 self.objects.append(train)\n\n\u00a0 \u00a0 self.velocieis.append(velocity)\n\n\u00a0 \u00a0 self.frameVelocity.append(frameVelo)\n\n\n\n\u00a0 def addPerson(self, pos, velocity, frameVelo, size,t):\n\n\u00a0 \u00a0 h = 0.5 * size\n\n\n\n\u00a0 \u00a0 person = [[t,pos[0],pos[1]], [t, pos[0] + 0.5 * h, pos[1]], [t,pos[0] + 0.5 * h, pos[1] + h],\\[t, pos[0] - 0.5 * h, pos[1]+h], [t,pos[0] - 0.5 * h, pos[1]], [t,pos[0],pos[1]],\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 [t,pos[0] + 0.5 * h, pos[1] - 1.5 * h], [t,pos[0] - 0.5 * h, pos[1] - 1.5 * h]]\n\n\n\n\u00a0 \u00a0 self.objects.append(person)\n\n\u00a0 \u00a0 self.velocieis.append(velocity)\n\n\u00a0 \u00a0 self.frameVelocity.append(frameVelo)\n\n\n\n\u00a0 def addEvent(self,event, fVelo):\n\n\u00a0 \u00a0 self.events.append(event)\n\n\u00a0 \u00a0 self.eventVelos.append(fVelo)\n\n\n\n\u00a0 #A pulse is like a clock, where very dT seconds it adds an event\n\n\u00a0 def addPulse(self, pt, dT, Ts, Tend, frameVelo):\n\n\u00a0 \u00a0 range = np.linspace(Ts, Tend, dT, endpoint = True)\n\n\u00a0 \u00a0 for i in range:\n\n\u00a0 \u00a0 \u00a0 self.events.append([i, pt[0], pt[1]])\n\n\u00a0 \u00a0 \u00a0 self.eventVelos.append(frameVelo)\n\n```\n\n\n\n-----\n\n\n\n### c). Switching Frames\n\n\n\nNow, some helper functions to help switch frame.\n\n\n\nSome notes on the functions:\n\n\n\n\u00a0 - `getGamma` returns the gamma value for the given velocity.\n\n\u00a0 - `lorentzTransformPt` transforms a 4-vector from frame S to frame S', where S' is traveling at V w.r.t S.\n\n\u00a0 - `addVelocities` returns the velocity of an object moving at `objectVelo` in Frame 1 (moving at `Vf1` w.r.t the ground frame) into Frame 2 (moving at `Vf2` w.r.t the ground frame). To do this, we will use `lorentzTranformPt` to transform the velocity 4-Vector from Frame 1 to the ground frame then to Frame 2.\n\n\u00a0 - `lorentzTransformObject` applies a Lorentz transformation to every point in the object at a given time t and returns a new 4-vector with all the points. The main purpose of this function is to help with `getEOM`. Note: in the new frame, all the points may not be at the same time, which is why this is just a helper function.\n\n\u00a0 - `getEOM` will return the equations of motion for an object, i.e. the x and y position as a function of the proper time. It takes an object traveling at `objectVelo` in Frame 1 (which is moving at `Vf1` w.r.t the ground frame). It returns the equations of motion, in the same order (which will be linear because everything has constant velocity) as a function of the proper time in Frame 2.\n\n\n\nThe EOMs are returned in the following form: If the EOMs are $$x = at + b$$ $$y = ct + d$$\n\nFor point n, then `getEOM` will return a vector where `vec[n-1]` stores `[[a,b],[c,d]]`.\n\n\n\n```python\n\n#Where Vf is relative to the ground grame\n\ndef getGamma(Vf):\n\n\u00a0 return (1 - Vf[0] * Vf[0] - Vf[1] * Vf[1]) ** (-0.5)\n\n\n\n\n\n\n\ndef lorentzTranformPt(fourVec, V):\n\n\u00a0 newFourVec = np.array(fourVec)\n\n\u00a0 g = getGamma(V)\n\n\u00a0 bx = V[0]\n\n\u00a0 by = V[1]\n\n\u00a0 g2 = g**2/(1 + g)\n\n\u00a0 LTMat = np.array([\n\n\u00a0 \u00a0 \u00a0 [g, -g * bx, -g * by],\n\n\u00a0 \u00a0 \u00a0 [-g * bx, 1 + g2 * bx * bx, g2 * bx * by],\n\n\u00a0 \u00a0 \u00a0 [-g * by, g2 * bx * by, 1 + g2 * by * by]\n\n\u00a0 ])\n\n\u00a0 return LTMat @ newFourVec\n\n\n\n\n\n#Vf1, Vf2 are relative to the ground frame\n\n#We are assuming that the object is in frame 1\n\n#Returns the velocity of object in Vf2\n\ndef addVelocities(objectVelo, Vf1, Vf2):\n\n\u00a0 gamma = getGamma(objectVelo)\n\n\u00a0 Vvec = [gamma, gamma * objectVelo[0], gamma * objectVelo[1]]\n\n\u00a0 #Using -Vf1 since Frame 1 is moving at Vf1 wrt the ground, so the ground is\n\n\u00a0 #moving at -Vf1 wrt Frame 1\n\n\u00a0 GroundVec = lorentzTranformPt(Vvec, [-1 * Vf1[0], -1 * Vf1[1]])\n\n\u00a0 Frame2Vec = lorentzTranformPt(GroundVec, Vf2)\n\n\u00a0 return [Frame2Vec[1]/Frame2Vec[0], Frame2Vec[2]/Frame2Vec[0]]\n\n\n\ndef lorentzTransformObject(Object, V):\n\n\u00a0 newObject4Vectors = []\n\n\u00a0 for pt in Object:\n\n\u00a0 \u00a0 newFVec = lorentzTranformPt(pt, V)\n\n\u00a0 \u00a0 newObject4Vectors.append(newFVec)\n\n\u00a0 return newObject4Vectors\n\n\n\n\n\ndef getEOM(Object, objectVelo, Vf1, Vf2):\n\n\n\n\u00a0 Vf1wrtVf2 = addVelocities([0,0], Vf1, Vf2)\n\n\u00a0 #We are using Vf1wrtVf2 since we are treating every endpoint of Object1 as\n\n\u00a0 #a point in spacetime, so it has a coordinate in Frame 1, which we transform\n\n\u00a0 #to frame 2. Then, we wait a few seconds and then transform the object to\n\n\u00a0 #frame 2. This gives us two points in frame 2 we can interpolate\n\n\n\n\u00a0 #We shoudln't run into any issues with similenatity since points on the two\n\n\u00a0 #Events will be timelikely separated\n\n\u00a0 Object1 = lorentzTransformObject(Object, Vf1wrtVf2)\n\n\u00a0 dT = 1\n\n\u00a0 Object2 = []\n\n\u00a0 for pt in Object:\n\n\u00a0 \u00a0 newPt = [pt[0] + dT, pt[1] + dT * objectVelo[0], pt[2] + dT*objectVelo[1]]\n\n\u00a0 \u00a0 Object2.append(newPt)\n\n\u00a0 Object2 = lorentzTransformObject(Object2, Vf1wrtVf2)\n\n\n\n\u00a0 EOMS = []\n\n\u00a0 for i in range(len(Object1)):\n\n\u00a0 \u00a0 Tps = [Object1[i][0], Object2[i][0]]\n\n\u00a0 \u00a0 Xps = [Object1[i][1], Object2[i][1]]\n\n\u00a0 \u00a0 Yps = [Object1[i][2], Object2[i][2]]\n\n\n\n\u00a0 \u00a0 xLine = np.polyfit(np.array(Tps), np.array(Xps), 1)\n\n\u00a0 \u00a0 yLine = np.polyfit(np.array(Tps), np.array(Yps), 1)\n\n\u00a0 \u00a0 EOMS.append([xLine, yLine])\n\n\u00a0 return EOMS\n\n```\n\n\n\n-----\n\n\n\n### d). Running the Simulation\n\n\n\nNow, to actually run the simulation. To do this, we will make some simplifying assumptions for our model. Every object added has a constant velocity (something usually done in intro relativity). However, this is useful because this means that the velocity of every object is constant in every frame\\! This allows us to calculate two points in each object's space-time path, and linearly interpolate for any frame. The high-level overview is as follows:\n\n\n\n1.\u00a0 The function, `runSimulation(frameVelocity, dT, Ts, Tend, condition)` takes the velocity of the frame, the time step, the total time, and a condition to stop at.\n\n2.\u00a0 Transform the coordinates and velocities of every object and event to the given frame, using the helper functions above.\n\n3.\u00a0 Generate an equation for the position of the object, as a function of the proper time.\n\n4.\u00a0 Get as many points as necessary, this part will just consist of plugging everything into the equation.\n\n\n\nSome caveats to this method are we can't have discontinuous changes in velocity, ex, starting and stopping. To fix this, we can splice multiple animations together (Making sure our frames are consistent). To do this, we can stop our simulation until a condition, read out the values (in a single frame), change the velocity of an object, and start a new simulation.\n\n\n\n`condition` will be a function that returns true or false and takes the current time. It can be specified with a lambda function before the simulation is run. For example, if we wanted to stop when an event occurred, we can find the space-time coordinate of the event beforehand, then have `condition` return false after the event occurs.\n\n\n\nThe simulation will return an array of points for all the objects. This array will look like `objects[t][i][j][pos]`, where `t` is the time step, `i` is the object number, `j` is the number of each point in the object, and `pos` is $x$ or $y$. Events will also be stored in this array, and they will have a point for a small time interval (\\~50 frames after they occur).\n\n\n\n```python\n\nclass Simulation(Simulation):\n\n\u00a0 def runSimulation(self, frameVelocity, dT, Ts, Tend, condition):\n\n\u00a0 \u00a0 newEvents\u00a0 = []\n\n\u00a0 \u00a0 objectEOMS = []\n\n\u00a0 \u00a0 for i in range(len(self.events)):\n\n\u00a0 \u00a0 \u00a0 relVel = addVelocities([0,0], self.eventVelos[i], frameVelocity)\n\n\u00a0 \u00a0 \u00a0 newEvents.append(lorentzTranformPt(self.events[i], relVel))\n\n\u00a0 \u00a0 for i in range(len(self.objects)):\\\n\n\u00a0 \u00a0 \u00a0 EOM = getEOM(self.objects[i], self.velocieis[i],\\\\\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0self.frameVelocity[i], frameVelocity)\n\n\u00a0 \u00a0 \u00a0 objectEOMS.append(EOM)\n\n\n\n\u00a0 \u00a0 times = []\n\n\u00a0 \u00a0 objects = []\n\n\u00a0 \u00a0 currentT = Ts\n\n\u00a0 \u00a0 while(currentT < Tend and condition(currentT)):\n\n\u00a0 \u00a0 \u00a0 times.append(currentT)\n\n\u00a0 \u00a0 \u00a0 timeStepObjects = []\n\n\u00a0 \u00a0 \u00a0 for EOM in objectEOMS:\n\n\u00a0 \u00a0 \u00a0 \u00a0 currentObject = []\n\n\u00a0 \u00a0 \u00a0 \u00a0 for pt in EOM:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 Xpos = pt[0][0] * currentT + pt[0][1]\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 Ypos = pt[1][0] * currentT + pt[1][1]\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 vec = [Xpos, Ypos]\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 currentObject.append(vec)\n\n\u00a0 \u00a0 \u00a0 \u00a0 timeStepObjects.append(currentObject)\n\n\n\n\u00a0 \u00a0 \u00a0 for event in newEvents:\n\n\u00a0 \u00a0 \u00a0 \u00a0 if(np.abs(event[0] - currentT) < 10 * dT):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 timeStepObjects.append([[event[1], event[2]]])\n\n\n\n\u00a0 \u00a0 \u00a0 # times.append(currentT)\n\n\u00a0 \u00a0 \u00a0 objects.append(timeStepObjects)\n\n\u00a0 \u00a0 \u00a0 currentT = currentT + dT\n\n\n\n\u00a0 \u00a0 return times, objects\n\n```\n\n\n\n-----\n\n\n\n### e). Minkowski Diagrams\n\n\n\nSince our simulation can have a lot of points and frames, especially with the objects and since our simulation is 2D (So the Minkowski diagram is 3D), we're going to make a Minkowski diagram class that will plot points and two different space-time axes. The first frame is assumed to be at rest. To plot moving objects, we can tell it to plot multiple points. The objects should be entered in the same format as before, a list of space-time coordinates with $(t,x,y)$.\n\n\n\nJust to make the plots easier to see, the Minkowski function will only show the axis if the velocity has a positive x and y component (Since otherwise the axis get squished the other way, and plotting all 8 quadrants is hard to see).\n\n\n\n```python\n\ndef Minkowski(frame2Velo, frame1Objects, frame2Objects, Ielev=30, Iazi=45):\n\n\u00a0 vPar = frame2Velo\n\n\u00a0 vPerp = np.array([0, frame2Velo[1], -1 * frame2Velo[0]])\n\n\u00a0 vMag = (vPar[0] * vPar[0] + vPar[1] * vPar[1])** 0.5\n\n\u00a0 vUnit = np.array(vPar)/vMag\n\n\n\n\u00a0 vPerpUnit = vPerp/vMag\n\n\u00a0 vParUnit = np.array(vPar)/vMag\n\n\n\n\n\n\u00a0 newTime = np.array([1, vPar[0], vPar[1]])\n\n\u00a0 newV = np.array([vMag, vUnit[0], vUnit[1]])\n\n\n\n\u00a0 tUnit = newTime/(np.linalg.norm(newTime))\n\n\u00a0 vUnit = newV/np.array(np.linalg.norm(newV))\n\n\u00a0 Uprime =\u00a0 ( (1 + vMag ** 2) /(1 - vMag**2 ) ) ** 0.5\n\n\u00a0 tUnit = Uprime * tUnit\n\n\u00a0 vUnit = Uprime * vUnit\n\n\n\n\u00a0 newX = (vParUnit[0]\u00a0 * vUnit + vParUnit[1] * vPerpUnit)\n\n\u00a0 newY = (vParUnit[1] * vUnit - vParUnit[0] * vPerpUnit )\n\n\u00a0 newT = tUnit\n\n\n\n\u00a0 xAxis = []\n\n\u00a0 yAxis = []\n\n\u00a0 tAxis = []\n\n\u00a0 for i in range(50):\n\n\u00a0 \u00a0 xAxis.append(newX * i)\n\n\u00a0 \u00a0 yAxis.append(newY * i)\n\n\u00a0 \u00a0 tAxis.append(newT * i)\n\n\u00a0 xAxis = np.array(xAxis)\n\n\u00a0 yAxis = np.array(yAxis)\n\n\u00a0 tAxis = np.array(tAxis)\n\n\n\n\u00a0 newFrame2 = []\n\n\u00a0 for obj in frame2Objects:\n\n\u00a0 \u00a0 objArr = []\n\n\u00a0 \u00a0 for pt in obj:\n\n\u00a0 \u00a0 \u00a0 newPt = pt[0] * newT + pt[1] * newX + pt[2] * newY\n\n\u00a0 \u00a0 \u00a0 objArr.append(newPt)\n\n\u00a0 \u00a0 newFrame2.append(objArr)\n\n\u00a0 newFrame2 = np.array(newFrame2)\n\n\n\n\n\n\u00a0 Axis = np.linspace(0,50, 50)\n\n\u00a0 Zeros = np.zeros(50)\n\n\n\n\u00a0 #Plotting\n\n\u00a0 fig = plt.figure()\n\n\u00a0 ax = fig.add_subplot(projection='3d')\n\n\u00a0 ax.set_xlim(0, 50)\n\n\u00a0 ax.set_ylim(0, 50)\n\n\u00a0 ax.set_zlim(0, 50)\n\n\u00a0 ax.plot(Axis, Zeros, Zeros, label = \"x\", color = \"dodgerblue\")\n\n\u00a0 ax.plot(Zeros, Axis, Zeros, label = \"y\", color = \"deepskyblue\")\n\n\u00a0 ax.plot(Zeros, Zeros, Axis, label = \"t\", color = \"lightskyblue\")\n\n\n\n\u00a0 ax.plot(xAxis[:,1], xAxis[:,2], xAxis[:,0], label = \"x'\", color = \"red\")\n\n\u00a0 ax.plot(yAxis[:,1], yAxis[:,2], yAxis[:,0], label = \"y'\" , color = \"firebrick\")\n\n\u00a0 ax.plot(tAxis[:,1], tAxis[:,2], tAxis[:,0], label = \"t'\" , color = \"salmon\")\n\n\n\n\u00a0 for obj in frame1Objects:\n\n\u00a0 \u00a0 obj = np.array(obj)\n\n\u00a0 \u00a0 ax.plot(obj[:,0], obj[:,1], obj[:,2], 'o', markersize = 2)\n\n\n\n\u00a0 for obj in newFrame2:\n\n\u00a0 \u00a0 obj = np.array(obj)\n\n\u00a0 \u00a0 ax.plot(obj[:,0], obj[:,1], obj[:,2], 'o', markersize = 2)\n\n\n\n\n\n\u00a0 ax.legend()\n\n\n\n\n\n\n\n\n\n\u00a0 ax.view_init(elev=Ielev, azim=Iazi)\n\n\u00a0 plt.title(\"Minkowski Diagram\")\n\n\u00a0 plt.show()\n\n\n\nMinkowski([0.5, 0.5] , [[[10,10,10], [15,10,10], [20,10,10]], [[5,20,20]]], [[[15,15,15]] ], 30, 25)\n\n```\n\n\n\n-----\n\n\n\n### f). Animation\n\n\n\nFinally, it's time to animate\\! `RelatavisticAnimation` will take a list of simulations, all of which are outputs from `runSimulation`. It takes a list so we can stitch together multiple simulations to get discontinuities in the velocity.\n\n\n\nNote: for the stitching to work properly, the objects must be in the same order in all simulations.\n\n\n\n```python\n\n#This animaition code is based off the chaos assignment\n\n#plotlimits[0] - xmin, xmax, ymin, ymax\n\n#This is so lenth contraction will properly show\n\ndef RelatavisticAnimation(simulations, plotLimits, title):\n\n\u00a0 \u00a0 maxObjectNum = 0\n\n\u00a0 \u00a0 AllSimulations = []\n\n\u00a0 \u00a0 for simulation in simulations:\n\n\u00a0 \u00a0 \u00a0 for timeStep in simulation:\n\n\u00a0 \u00a0 \u00a0 \u00a0 AllSimulations.append(timeStep)\n\n\u00a0 \u00a0 \u00a0 \u00a0 if(len(timeStep) > maxObjectNum):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 maxObjectNum = len(timeStep)\n\n\u00a0 \u00a0 fig, ax = plt.subplots()\n\n\u00a0 \u00a0 ax.set_xlim(plotLimits[0],plotLimits[1])\n\n\u00a0 \u00a0 ax.set_ylim(plotLimits[2],plotLimits[3])\n\n\u00a0 \u00a0 plt.title(title)\n\n\u00a0 \u00a0 lines=[]\n\n\u00a0 \u00a0 for i in range(maxObjectNum):\n\n\u00a0 \u00a0 \u00a0 \u00a0 line, = ax.plot([], [], '-', marker='o')\n\n\u00a0 \u00a0 \u00a0 \u00a0 lines.append(line)\n\n\n\n\u00a0 \u00a0 def update(i, AllSimulations,lines, maxObjNum):\n\n\u00a0 \u00a0 \u00a0 \u00a0 currentTs = AllSimulations[i]\n\n\u00a0 \u00a0 \u00a0 \u00a0 for j in range(maxObjNum):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 if(j < len(currentTs)):\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 obj = np.array(currentTs[j])\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 lines[j].set_data(obj[:,0],obj[:,1])\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 else:\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 lines[j].set_data([], [])\n\n\n\n\u00a0 \u00a0 \u00a0 \u00a0 return lines\n\n\u00a0 \u00a0 ll=1\n\n\n\n\u00a0 \u00a0 ani = animation.FuncAnimation(fig, update, len(AllSimulations), fargs=[AllSimulations, lines, maxObjectNum],\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 interval=20, blit=True,repeat=False)\n\n\u00a0 \u00a0 plt.close()\n\n\u00a0 \u00a0 return ani\n\n```\n\n\n\n-----\n\n\n\n## 2\\. The Fundamental Effects:\n\n\n\nNow, it's time for the classic experiments, starting with Time Dilation. For this, we won't use laser pulses (since it would be too hard to animate), instead, we'll show a relativistic \"train\" (visualized with a box) with a clock which will fire a pulse at consistent intervals (green dot). Note the origin is the blue dot.\n\n\n\n### a). Time Dilation\n\n\n\nAs we can see, the pulse looks slower in the ground frame. We also only see two pulses in the same proper time (and we also see the train length contracted\\!).\n\n\n\n### b). Length Contraction\n\n\n\nNow for length contraction. We'll show a few fixed length rods moving at different speeds. The blue point is the origin and all the rods have the same proper length. All the rods have proper length 5c\\*s, but they are going at different speeds. The orange rod is moving at 0.99c and the other two are moving at \u00b1 0.5c respectively. Now, in a moving frame, going at 0.5c. We can also see that transverse length contraction doesn't exist.\n\n\n\n### c). Loss of Simultaneity\n\n\n\nFor this simulation, we will have two events simultaneous in the ground frame, and show that they happen at different times in every other frame. Blue is again the origin and the red and green points are two different events. These events will occur in another \"train\". Now, for a different frame. (But now both green and orange because of matplotlib). We lose simultaneity even if we move in both x and y\\!\n\n\n\n-----\n\n\n\n## 3\\. The Classic Paradoxes\n\n\n\n### a). Pole in a Barn\n\n\n\nNow, for a classic paradox. Imagine someone (who's really fast) running at relativistic speeds. They are holding a pole with proper length L and run into a barn with proper length L. In the ground frame, the pole fits in the barn because of length contraction and close the doors, but in the moving frame, the barn is contracted and the pole is the usual length, so what happens? Let's find out\\!\n\n\n\nAs we can see, the pole fits in the barn in the stationary frame, but in the moving frame, the barn doors don't close simultaneously\\!\n\n\n\n### b). The Twin Paradox\n\n\n\nFinally, we will see the twin paradox. Imagine two twins, one which stays on Earth and one who goes on a rocket which goes out to a distant and then comes back to Earth. Each twin sees the other as younger because of time dilation, so when they meet back on Earth, who's older?\n\n\n\nWe will track how old the twins are by having each twin send uniformly spaced signals in their own frame, and see how many signals each twin sends in an inertial frame.\n\n\n\nAs we can see, since the twin on Earth sends more signals, the one on Earth is older! The key is that the spaceship frame is not an inertial frame, since the spaceship turns around.\n\n---\n\n## Library Usage\n\nThis project is now organized as a Python library. Here's how to use it:\n\n### Installation\n\n#### Development Installation\n```bash\ngit clone https://github.com/bdavidzhang/Special-Relativity-Grapher.git\ncd Special-Relativity-Grapher\npip install -e .\n```\n\n#### Development Dependencies\n```bash\npip install -e \".[dev]\"\n```\n\n### Quick Start\n\n```python\nfrom special_relativity_grapher import Simulation\nfrom special_relativity_grapher.visualization import RelatavisticAnimation\nfrom special_relativity_grapher.utils import trueCond\n\n# Create a simulation\nsim = Simulation()\n\n# Add a moving rod\nsim.addLine([0, 0], 5, [0, 0], [-0.8, 0], True, 0)\n\n# Run simulation in ground frame\ntimes, objects = sim.runSimulation([0, 0], 0.1, 0, 10, trueCond)\n\n# Create animation\nplot_limits = [-5, 10, -2, 3]\nanimation = RelatavisticAnimation([objects], plot_limits, \"Length Contraction Demo\")\n\n# Save as gif\nanimation.save('length_contraction.gif', writer='pillow', fps=30)\n```\n\n### Examples\n\nThe library includes several complete examples demonstrating key relativistic effects:\n\n#### Time Dilation\n```python\nfrom examples.time_dilation import time_dilation_demo, light_clock_demo\n\n# Standard time dilation with moving clocks\ntrain_ani, ground_ani = time_dilation_demo()\n\n# Light clock demonstration \nlight_train_ani, light_ground_ani = light_clock_demo()\n```\n\n#### Length Contraction\n```python\nfrom examples.length_contraction import length_contraction_demo\n\n# Rods at different velocities\nground_ani, moving_ani = length_contraction_demo()\n```\n\n#### Classic Paradoxes\n```python\nfrom examples.pole_in_barn import pole_in_barn_demo\nfrom examples.twin_paradox import twin_paradox_demo\n\n# Pole-in-barn paradox\nground_ani, pole_ani = pole_in_barn_demo()\n\n# Twin paradox\nearth_ani, spaceship_ani = twin_paradox_demo()\n```\n\n#### Loss of Simultaneity\n```python\nfrom examples.simultaneity import simultaneity_demo\n\n# Events simultaneous in one frame but not another\nground_ani, moving_x_ani, moving_diag_ani = simultaneity_demo()\n```\n\n### Core API\n\n#### Simulation Class\nThe main simulation class for managing objects, events, and reference frames.\n\n**Key Methods:**\n- `addLine(startPt, L, velocity, frameVelo, doX, t)`: Add a line segment\n- `addTrain(bottomLeft, L, H, velocity, frameVelo, t)`: Add a rectangular object\n- `addEvent(event, fVelo)`: Add a spacetime event\n- `runSimulation(frameVelocity, dT, Ts, Tend, condition)`: Execute the simulation\n\n#### Transformation Functions\n- `getGamma(Vf)`: Calculate Lorentz gamma factor\n- `lorentzTranformPt(fourVec, V)`: Apply Lorentz transformation to a 4-vector\n- `addVelocities(objectVelo, Vf1, Vf2)`: Relativistic velocity addition\n\n#### Visualization Functions\n- `RelatavisticAnimation(simulations, plotLimits, title)`: Create animated visualizations\n- `Minkowski(frame2Velo, frame1Objects, frame2Objects)`: Generate 3D Minkowski diagrams\n\n### Testing\n\nRun the test suite:\n```bash\npytest tests/\n```\n\n### Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Add tests for new functionality\n5. Run the test suite\n6. Create a Pull Request\n\n## License\n\nThis project is licensed under the MIT License.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A Python library for visualizing special relativity effects",
"version": "0.1.2",
"project_urls": {
"Bug Tracker": "https://github.com/bdavidzhang/Special-Relativity-Grapher/issues",
"Documentation": "https://special-relativity-grapher.readthedocs.io",
"Homepage": "https://github.com/bdavidzhang/Special-Relativity-Grapher",
"Repository": "https://github.com/bdavidzhang/Special-Relativity-Grapher"
},
"split_keywords": [
"physics",
" relativity",
" visualization",
" education"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "9719d2b28929cce7273ff04a50aaf4bb96643b61dbadd64054a2b736ea2df24a",
"md5": "fc72affd3ef4672b3a9c5a0f60290d51",
"sha256": "52e3e04042acd58ecef3c0bcf7e34bdeb72ceb4f20c1b8264c0a1efd5b7c9031"
},
"downloads": -1,
"filename": "special_relativity_grapher-0.1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "fc72affd3ef4672b3a9c5a0f60290d51",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 17725,
"upload_time": "2025-08-02T11:33:26",
"upload_time_iso_8601": "2025-08-02T11:33:26.564873Z",
"url": "https://files.pythonhosted.org/packages/97/19/d2b28929cce7273ff04a50aaf4bb96643b61dbadd64054a2b736ea2df24a/special_relativity_grapher-0.1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "ccecc33ff53f8f93a567d9857dd3149884510c22c8c4782c840c8008dc29fce7",
"md5": "a72d54a71a524632573958dddf66431e",
"sha256": "a54fb2b888e6f98311d1ff3317f87017969ef72ad0481f711343aaa1078f8751"
},
"downloads": -1,
"filename": "special_relativity_grapher-0.1.2.tar.gz",
"has_sig": false,
"md5_digest": "a72d54a71a524632573958dddf66431e",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 26172,
"upload_time": "2025-08-02T11:33:28",
"upload_time_iso_8601": "2025-08-02T11:33:28.534302Z",
"url": "https://files.pythonhosted.org/packages/cc/ec/c33ff53f8f93a567d9857dd3149884510c22c8c4782c840c8008dc29fce7/special_relativity_grapher-0.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-02 11:33:28",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "bdavidzhang",
"github_project": "Special-Relativity-Grapher",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "numpy",
"specs": []
},
{
"name": "matplotlib",
"specs": []
}
],
"lcname": "special-relativity-grapher"
}