Cosmology and quantum fluctuations in the browser

Let us set a purely practical goal - to realize an endless canvas with the ability to move and scale it with the mouse. Such a canvas, for example, can serve as a moving coordinate system in a graphical editor. The implementation of our idea is not so complicated, but the process of understanding it is associated with fundamental mathematical and physical objects, which we will consider as we develop.







Result



Formulation of the problem



From our canvas, we want to perform only two functions: moving the mouse and the movement of the mouse, as well as zooming when scrolling. The arena of our transformations will choose a browser. Weapons, in this case, do not have to choose.









State machine



The behavior of such systems is conveniently described by transitions between their states, i.e. state machine  delta:S rightarrowS where  delta - the transition function between states, displaying many states in itself.







In our case, the state diagram looks like this:













When implementing the transition function, it is convenient to make the event-dependent. This will become apparent in the future. Similarly, it is convenient to be able to subscribe to a change in the state of the machine.







type State = string | number; type Transition<States = State> = { to: States, where: (event: Event) => Array<boolean>, }; type Scheme<States = State> = { [key: States]: Array<Transition<States>> }; interface FSM<States = State> { constructor(state: States, scheme: Scheme<States>): void; // Returns true if the scheme had a transition, false otherwise get isActive(): boolean; // Dispatch event and try to do transition dispatch(event: Event): void; // subscribe on state change on(state: States, cb: (event: Event) => any): FSM<States>; // remove subscriber removeListener(state: States, cb: (event: Event) => any): void; };
      
      





We postpone the implementation for a while and take up the geometric transformations that underlie our task.







Geometries



If the issue with moving the canvas is so obvious that we will not dwell on it, then stretching should be considered in more detail. First of all, we require that the stretch leaves one single point stationary - the mouse cursor. The reversibility condition must also be satisfied, i.e. the reverse sequence of user actions should bring the canvas to its original position. What geometry is suitable for this? We will consider some group of point transformations of the plane into itself  mathbbR2 rightarrow mathbbR2 , which is generally expressed by the introduction of new variables x,y defined as functions of old:











x= varphi(x,y)y= psi(x,y)







In accordance with the principle of duality in mathematics, such transformations can be interpreted as a change in the coordinate system, as well as a transformation of the space itself with the latter fixed. The second interpretation is convenient for our purposes.







A modern understanding of geometry is different from an understanding of the ancients. According to F. Klein , - geometry studies invariants with respect to certain transformation groups. So, in a group of movements D the invariant is the distance between two points d((x1,y1),((x2,y2))= sqrt(x1x2)2+(y1y2)2 . It includes parallel transfers T(x,y) on vector (x,y) rotation R phi relative to the origin at an angle  phi and reflections Ml relative to some line l . Such movements are called elementary. The composition of the two movements belongs to our group and sometimes comes down to elementary. So, for example, two consecutive mirror reflections relative to straight lines l and n give a rotation around a certain center at a certain angle (check for yourself):









Ml circMn=T(a,b) circR alpha circT(a,b)







Surely you already guessed that such a group of movements D forms Euclidean geometry. However, stretching does not preserve the distance between two points, but their relationship. Therefore, a group of movements, although it should be included in our scheme, but only as a subgroup.







The geometry that suits us is based on a stretch group P to which, in addition to the above movements, homothety is added Sk by coefficient k .







Well, the last. The reverse element must be present in the group. And therefore there is a neutral e (or single) that does not change anything. for example









Sk circS1k=e







means first stretching in k times and then in 1/k .

Now we can describe the stretching, leaving the mouse cursor point fixed, in group-theoretical language:











(x,y) rightarrow(x,y)(T(clientX,clientY) circSk circT(clientX,clientY))







Note 1

In the general case, rearrangements of actions are not commutative (you can first take off your coat and then your shirt, but not vice versa).











 forall(x,y) in mathbbR2 neq0 RightarrowT(x,y) circSk circT(x,y)) neqSk circT(x,y) circT(x,y))









Express the movement T(x,y) and Sk and their composition as functions of a vector in code







 type Scalar = number; type Vec = [number, number]; type Action<A = Vec | Scalar> = (z: Vec) => (v: A) => Vec; // Translate const T: Action<Vec> = (z: Vec) => (d: Vec): Vec => [ z[0] + d[0], z[1] + d[1] ]; // Scale const S: Action<Scalar> = (z: Vec) => (k: Scalar): Vec => [ z[0] * k, z[1] * k ]; const compose = (z: Vec) => (...actions: Array<(z: Vec) => Vec>) => actions.reduce((z, g) => g(z), z);
      
      





Pain 1

It is very strange that JavaScript does not overload operators. It would seem that with such widespread use of vector and raster graphics, it is much more convenient to work with vectors or complex numbers in a "classical" form. The concepts of action would replace arithmetic operations. So, for example, rotation around some vector a on the corner  phi would be expressed in a trivial way:











Ta circR phi circTa Leftrightarrowz rightarrow(za)ei phi+a







Unfortunately, JS does not follow the development of the wonderful Pythagorean idea “The world is a number”, unlike, at least, Python.









Note that so far we have been working with a group of continuous transformations. However, the computer does not work with continuous quantities, therefore, following Poincare, we will understand a continuous group as an infinite group of discrete operations. Now that we have figured out the geometry, we should turn to the relativity of motion.







Cosmology of the canvas. Modular grid



For a century, as humanity, the expansion of the universe has been known. Observing distant objects - galaxies and quasars, we register the shift of the electromagnetic spectrum towards longer waves - the so-called cosmological redshift. Any measurement connects the observer, the observed and the means of measurement in relation to which we make our measurements. Without measuring instruments, it is impossible to establish invariant relations in nature, i.e., to determine the geometry of the Universe. However, geometry loses its meaning without observability. So in our task it’s nice to have landmarks, like galaxies, in relation to the light of which we can determine the relativity of the movement of our canvas. Such a structure can be a periodic lattice, which bifurcates each time when the space is expanded twice.







Since the lattice is periodic, it is convenient to adopt a modular algebra. Thus, we will act as a group P also on the torus T2=S1 timesS1 . Since the monitor screen is not a continuous plane, but an integer lattice  mathbbZ2 (we neglect now that it is finite), then the action of the group P must be considered on an integer torus  mathbbZ2p where p - the size of the edge of the square p timesp :













Thus, once and for all fixing our torus near the origin, we will perform all further calculations on it. Then propagate it using standard canvas library methods. Here's what a one pixel move looks like:













Obviously, the standard operation of taking the module x% p is not suitable for us, since it translates negative values ​​of the argument into negative ones, but there are none on the integer torus. Write your function x modp :









 const mod = (x, p) => x >= 0 ? Math.round(x) % p : p + Math.round(x) % p;
      
      





Now back to the final state machine and







define it
The FSM class is conveniently inherited from EventEmitter, which will provide us with the ability to subscribe.

 class FSM<States> extends EventEmitter { static get TRANSITION() { return '__transition__'; } state: States; scheme: Scheme<States>; constructor(state: States, scheme: Scheme<States>) { super(); this.state = state; this.scheme = scheme; this.on(FSM.TRANSITION, event => this.emit(this.state, event)); } get isActive(): boolean { return typeof(this.scheme[this.state]) === 'object'; } dispatch(event: Event) { if (this.isActive) { const transition = this.scheme[this.state].find(({ where }) => where(event).every(domen => domen) ); if (transition) { this.state = transition.to; this.emit(FSM.TRANSITION, event); } } } }
      
      







Next, define a transition scheme, create a canvas and







initialize everything.
 canvas = document.getElementById('canvas'); ctx = canvas.getContext('2d'); // Create a pattern, offscreen patternCanvas = document.createElement('canvas'); patternContext = patternCanvas.getContext('2d'); type States = | 'idle' | 'pressed' | 'dragging' | 'zooming'; const scheme: Scheme<States> = { 'idle': [ { to: 'pressed', where: event => [event.type === 'mousedown'] }, { to: 'zooming', where: event => [event.type === 'wheel'] }, ], 'pressed': [ { to: 'moving', where: event => [event.type === 'mousemove'] }, { to: 'idle', where: event => [event.type === 'mouseup'] }, ], 'moving': [ { to: 'moving', where: event => [event.type === 'mousemove'] }, { to: 'idle', where: event => [event.type === 'mouseup'] }, ], 'zooming': [ { to: 'zooming', where: event => [event.type === 'wheel'] }, { to: 'pressed', where: event => [event.type === 'mousedown'] }, { to: 'idle', where: event => [true] }, ], }; const fsm: FSM<States> = new FSM('idle', scheme); const dispatch = fsm.dispatch.bind(fsm);
      
      







Then you should determine the rendering function, set the necessary initial values ​​and subscribe to the change of state. Consider the most interesting part of the code:







  fsm.on('zooming', (event: WheelEvent) => { // next scale factor const nk = g >= 1 ? round(k + Math.sign(event.wheelDeltaY) * h * g / 1e2, 1e2) : round(k + Math.sign(event.wheelDeltaY) * h * g / 1e2, 1e12); // gain g = 2 ** Math.trunc(Math.log2(nk)); if (g < min || g > max) return; vec = compose(vec)( T([-event.clientX, -event.clientY]), S(nk / k), T([event.clientX, event.clientY]) ); size = base * nk; patternCanvas.width = Math.round(size / g); patternCanvas.height = Math.round(size / g); xyMod = [ mod(vec[0], patternCanvas.width), mod(vec[1], patternCanvas.height) ]; k = nk; main(); });
      
      





Firstly, we are expanding not by the coefficient k, but by some ratio nk / k. This is due to the fact that the m-step of our mapping at a fixed point (a,b) expressed as











xm=(xm1a)km1+aym=(ym1b)km1+b







or, relative to the initial values x1,y1











xm=(x1a) prodm1i=1ki+aym=(y1b) prodm1i=1ki+b







Obviously the product  prodm1i=1ki there is a nonlinear function of the iteration step and either very quickly converges to zero, or runs away to infinity with small initial deviations.







We introduce the variable g, which is a measure of doubling our canvas. Obviously, it takes on a constant value on a certain interval. To achieve linearity ki use homogeneous substitution











ki+1= fracki+1ki,k1=1







Then all the members in the work, except the first and last, will be reduced:











 prodm1i=1ki= prodm1i=1 frackiki1= frack21 frack3k2... frackm2km3 frackm1km2=km1







Further, the phase jump g reduces the expansion rate in such a way that the fractal structure that unfolds before us always moves linearly. Thus, we obtain an approximated variation of the Hubble power law of the expansion of the Universe.







It remains to understand the limits of accuracy of our model.







Quantum fluctuations. 2-adic number field



Understanding the measurement process led to the concept of a real number. Heisenberg's uncertainty principle points to its limits. A modern computer does not work with real numbers, but with machine words, the length of which is determined by the capacity of the processor. Machine words form a field of 2-adic numbers  mathbbQ2 and are denoted as  mathbbFn2 where n - word length. The measurement process in this case is replaced by the calculation process and is associated with a non-Archimedean metric:











 foralln in mathbbZ,z in mathbbFn2:n cdotz<2n







Thus, our model has a limit of computation. Limitations are described in the IEEE_754 standard. Starting at some point, our base size will go beyond the limit of accuracy and the operation of taking the module will begin to generate errors resembling pseudo-random sequences. This is simply verified by deleting the line







 if (g < min || g > max) return;
      
      





The final limit in our case is calculated by the semi-empirical method, since we work with several parameters.







Conclusion



Thus, seemingly distant at first glance, theories are connected on the canvas in the browser. The concepts of action, measurement and calculation are closely related to each other. The issue of combining them is still not resolved.







Result



PS
It was clear that the source code would be small, so I led the development in the ordinary index.html. As I wrote this article, I added the typing that I tested in TypeScript playground.




All Articles