|Computational class||Turing complete|
|Reference implementation||On Github|
- You may be looking for the other language named Photon (by Sugarfi).
Photon is an esoteric programming language implemented by User:Quintopia in 2021. It is a geometric computational model based on a particle moving around a 3-dimensional space.
The playfield for a Photon program in execution is an infinite 3-dimensional space. In this space is a moving particle whose location and direction coordinates are arbitrary floating point numbers. The space is divided into a grid of axis-aligned unit cubes, each centered on an integer lattice point. Each cube contains either a hole or a reflector.
A hole simply captures the particle if it enters the cube, halting execution (and printing the particle's final position and direction).
A reflector is the intersection of the cube with a plane through the center of the cube. It is defined by a normal vector (that does not have to be unit). It serves as a two-way mirror for the moving particle. If a particle's trajectory intersects with a cube's reflector inside that cube AND the particle is approaching from within the half-space defined by that plane (i.e. the dot product of the particle's velocity with the plane's normal is negative), it will bounce off of the reflector at the point where it would collide with it. The bounce is a perfect reflection with incoming and outgoing angles matching, exactly as a photon reflecting from a mirror. If the particle's trajectory intersects with a cube's reflector "from behind" (i.e. the aforementioned dot product is positive), its trajectory continues unaltered, as if it had missed the reflector entirely.
This set of interactions completely defines the semantics of an executing Photon program.
What A Program Defines
A program must define both the initial position and velocity of the particle. It must also define a function f:(x,y,z)->(x',y',z'), where (x,y,z) is a vector of integer-valued floats defining the center of a cube and (x',y',z') is a vector of floats normal to the reflector that resides within that cube. If (x',y',z')==(0,0,0), then the cube contains a hole.
Syntax is entirely implementation-dependent and subject to the implementors' whims. The current reference implementation has the following format:
(x,y,z,dx,dy,dz) [a valid Python expression in the variables x, y, and z]
That is, the first line is a 6-tuple of floats defining the particles initial position (x,y,z) and direction (dx,dy,dz). The remainder of the file, beginning on the second line, defines a single valid Python expression that may contain reference to the variables x, y, and z and must evaluate to a 3-tuple of floats. (In this implementation, both expressions are limited to a small subset of Python which is believed not to be Turing-complete to make it clearer that the TCness arises from the particle-bouncing computational model. Using a Turing-complete computation system to calculate the particle position and normal map completely undermines the whole demonstration. Future implementors are encouraged to attempt to ensure the use of a syntax that is as limited as possible for specifying these objects as well.)
If the first line of the file begins with the string
[verbose], the program will be assumed to begin on the second line instead, and the program will be executed in debug mode, rendering to stdout the particle's position every time it hits a reflector. The first line may also contain the string
[showmiss] which will render to stdout every single cube the particle passes through (assuming
[verbose] is also there).
To do addition, it is sufficient to fill a plane with reflectors that reflect along the same direction in the xy plane to the x-axis and filling the x-axis with holes. When the particle reaches the x-axis, it will have an x value equal to the sum of the starting coordinates. However, because it will reach a cube with a hole before it actually reaches the x-axis, it is not possible to get the actual exact sum this way. Instead, we can only get the integer sum of integer starting coordinates by looking at the x-coordinate of this final cube. We can also ensure the x-coordinate of the particle has the same x-coordinate as the cube by offsetting the y-value of the starting point 0.5 away from the x-axis. A program that does this could look like:
(int(input()),eval("copysign(0.5,%d)+%d"%((int(input()),)*2)),-1,0,0,1) (0,0,0) if y==0 else (copysign(1,y)/sqrt(2),-copysign(1,y)/sqrt(2),-1)
In the reference implementation, this takes two integers as input and halts with their sum in the particle's x-coordinate.
If one wishes to do multiplication using only a single reflection, it is impossible for every reflector to reflect along the same line. Instead, every reflector needs to reflect in a slightly different angle corresponding to the gradient of a family of hyperbolas. Furthermore, one needs two separate vector fields to handle multiplications with positive and negative products because otherwise reflections for positive products will interfere with one another. However, the same idea of reflecting toward the x-axis and filling the x-axis with holes will still work. In this case, though, it is mandatory to set up the reflectors so that the particle arrives at the correct cube when its y-value is 0.5 away from the x-axis, or else it would often have to pass through the wrong hole cube to arrive at the correct hole cube. A program that handles all these contingencies correctly looks like this:
(int(input()),int(input()),-1,0,0,1) ((x*y-x)/sqrt((x*y-x)**2+(copysign(0.5,y)-y)**2+1),(copysign(0.5,y)-y)/sqrt((x*y-x)**2+(copysign(0.5,y)-y)**2+1),-1+1/sqrt((x*y-x)**2+(copysign(0.5,y)-y)**2+1)) if z==0 and x*y>=0 else ((x*y-x)/sqrt((x*y-x)**2+(copysign(0.5,y)-y)**2+1),(copysign(0.5,y)-y)/sqrt((x*y-x)**2+(copysign(0.5,y)-y)**2+1),-1-1/sqrt((x*y-x)**2+(copysign(0.5,y)-y)**2+1)) if z==2 and x*y<0 else (0,0,0) if y==0 and z==1 else (0,-y,0)
When run with the reference implementation, this program takes two integer inputs and halt with their product in the particle's x-coordinate.
Here is a program that demonstrates running forever without repetition. The x and y values cycle through the same values forever while the z value increases without bound:
[verbose] (0,0,0,1,0,0) (-1,1-math.sqrt(2),0) if x==2 else (math.sqrt(6)-2,math.sqrt(6)+2,2) if y==-1 and x==1 else (math.sqrt(3)+1,-1,-1) if x==0 else (0,0,1)
Notice that all of the normal vectors are constant and x and y are only used to determine where they are placed. In fact, making this a mandatory condition for the language would not in any way reduce its computational power.
Takes one input for the initial x-value. If it's 0, halts at a cube with x=0. If it's 1, bounces back and forth between cubes with x=1 (which the [verbose] flag happily outputs each time)
[verbose] (eval(input()),0,0,0,0,1) (0,0,-1) if x==1 and z==1 else (0,0,0) if x==0 and z==1 else (0,0,1)
Reduction from Fractran
By letting the x-coordinate of the particle represent the integer numerical state S of the Fractran process and mapping the ith fraction to a set of reflectors in the plane y=i, with a pair at (S,i,0) and (S,i,S/di) (where di is the denominator of the ith fraction) to get the particle moving along the line (x,i,n/d) and another at (S*ni/di (where ni is the numerator) to reflect the particle back towards the yz plane where it will be reflected back to the x-axis and out again into the xy plane, one can exactly perform a multiplication of S by the ith fraction exactly when it is the first such that is divisible by its denominator. A series of holes in the xy plane at y=L+1 (where L is the number of fractions in the Fractran program) will catch the particle only when S is divisible by none of the fractions and so halt the program once this series of computations finishes. A transpiler that performs this conversion can be found on Github.
Reduction from Nopfunge
v symbol in Nopfunge behaves identically with a pair of reflectors
(1,0,0) and, directly to its right (positive x direction),
(-1,1,0). The other three commands have similar pairs of reflectors that replicate their behavior. Thus, we can exactly copy a Nopfunge program by putting pairs of reflectors like this in the xy-plane in positions corresponding to exactly double the x and y values of their positions in the Nopfunge program (using modular arithmetic to repeat the repeating parts infinitely across the xy-plane). Obviously, the halting behavior is handled by placing holes along the x and y axes. The "empty space" in the Nopfunge program can be filled by placing reflectors aimed the same direction the IP would move along the row it is in since Nopfunge is Turing-complete even if the instruction pointer is never allowed to go both left and right on the same section of a row. Thus, since we can let the z value of every normal vector be zero, we avoid using the third dimension entirely. A transpiler that performs this conversion can be found on Github.
Reduction from Iterated Collatz function
A very simple reduction can be achieved using only the xy plane as in the Nopfunge reduction, but simulating iterated Collatz functions. In the first quadrant, the normal vector
(-1,-1,0) is placed precisely at the coordinates
(x,F(x),0) (where F is the Collatz function in question). The remainder of the quadrant (including the upper y-axis) is filled with
(-1,1,0) to allow free passage of the particle in either the up or left directions. This whole quadrant is then rotated around the origin into each of the other three quadrants so that the function is iterated four times for every lap the particle completes around the origin. Halting conditions can be achieved by placing holes on the axes at specific values for which halting should take place.