Cosmologie et fluctuations quantiques dans le navigateur

Fixons-nous un objectif purement pratique: mettre en œuvre un canevas sans fin avec la possibilité de le déplacer et de le mettre à l'échelle avec la souris. Un tel canevas, par exemple, peut servir de système de coordonnées en mouvement dans un éditeur graphique. La mise en œuvre de notre idée n'est pas si compliquée, mais le processus de compréhension est associé à des objets mathématiques et physiques fondamentaux, que nous considérerons au fur et à mesure de notre développement.


Résultat

Énoncé du problème


À partir de notre toile, nous voulons effectuer seulement deux fonctions: déplacer la souris et le mouvement de la souris, ainsi que zoomer lors du défilement. L'arène de nos transformations choisira un navigateur. Les armes, dans ce cas, n'ont pas à choisir.



Machine d'état


Le comportement de ces systèmes est commodément décrit par des transitions entre leurs états, c'est-à-dire machine d'état  delta:S rightarrowS delta- la fonction de transition entre états, affichant de nombreux états en soi.


Dans notre cas, le diagramme d'état ressemble à ceci:





Lors de la mise en œuvre de la fonction de transition, il est pratique de rendre l'événement dépendant. Cela deviendra apparent à l'avenir. De même, il est pratique de pouvoir souscrire à un changement d'état de la 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; }; 

Nous allons reporter l'implémentation pendant un certain temps et reprendre les transformations géométriques qui sous-tendent notre tâche.


Géométries


Si le problème du déplacement de la toile est si évident que nous ne nous attarderons pas dessus, l'étirement doit être considéré plus en détail. Tout d'abord, nous exigeons que l'étirement laisse un seul point fixe - le curseur de la souris. La condition de réversibilité doit également être satisfaite, c'est-à-dire la séquence inverse des actions de l'utilisateur doit ramener le canevas à sa position d'origine. Quelle géométrie convient à cela? Nous considérerons un groupe de transformations ponctuelles du plan en lui-même  mathbbR2 rightarrow mathbbR2, qui s'exprime généralement par l'introduction de nouvelles variables x,ydéfinis comme des fonctions de l'ancien:


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


Conformément au principe de dualité en mathématiques, de telles transformations peuvent être interprétées comme un changement dans le système de coordonnées, ainsi qu'une transformation de l'espace lui-même avec ce dernier fixé. La deuxième interprétation convient à nos besoins.


Une compréhension moderne de la géométrie est différente d'une compréhension des anciens. Selon F. Klein , - la géométrie étudie les invariants par rapport à certains groupes de transformation. Donc, dans un groupe de mouvements Dl'invariant est la distance entre deux points d((x1,y1),((x2,y2))= sqrt(x1x2)2+(y1y2)2. Il comprend la césure parallèle T(x,y)sur le vecteur (x,y)rotation R phipar rapport à l'origine à un angle  phiet reflets Mlpar rapport à une ligne l. Ces mouvements sont appelés élémentaires. La composition des deux mouvements appartient à notre groupe et se résume parfois à l'élémentaire. Ainsi, par exemple, deux réflexions miroir consécutives par rapport aux lignes droites let ndonner une rotation autour d'un certain centre sous un certain angle (vérifiez par vous-même):

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


Vous avez sûrement déjà deviné qu'un tel groupe de mouvements Dforme la géométrie euclidienne. Cependant, l'étirement ne préserve pas la distance entre deux points, mais leur relation. Par conséquent, un groupe de mouvements, bien qu'il devrait être inclus dans notre schéma, mais uniquement en tant que sous-groupe.


La géométrie qui nous convient est basée sur un groupe d'étirement Pauquel s'ajoute, outre les mouvements ci-dessus, l'homothétie Skpar coefficient k.


Eh bien, le dernier. L'élément inverse doit être présent dans le groupe. Et donc il y a un neutre e(ou célibataire) qui ne change rien. Par exemple

Sk circSk1=e


signifie d'abord étirer kfois puis 1 $ / k $ .

Maintenant, nous pouvons décrire l'étirement, en laissant le pointeur du curseur de la souris fixe, dans un langage théorique de groupe:


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


Remarque 1

Dans le cas général, les réarrangements d'actions ne sont pas commutatifs (vous pouvez d'abord enlever votre manteau puis votre chemise, mais pas l'inverse).


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



Exprimez le mouvement T(x,y)et Sket leur composition en fonction d'un vecteur dans le 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); 

Douleur 1

Il est très étrange qu'en JavaScript il n'y ait pas de surcharge d'opérateur. Il semblerait qu'avec une utilisation aussi répandue des graphiques vectoriels et raster, il est beaucoup plus pratique de travailler avec des vecteurs ou des nombres complexes sous une forme "classique". Les concepts d'action remplaceraient les opérations arithmétiques. Ainsi, par exemple, la rotation autour d'un vecteur aau coin  phiserait exprimé de manière triviale:


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


Malheureusement, JS ne suit pas le développement de la merveilleuse idée pythagoricienne «Le monde est un nombre», contrairement, au moins, à Python.



Notez que jusqu'à présent, nous avons travaillé avec un groupe de transformations continues. Cependant, l'ordinateur ne fonctionne pas avec des quantités continues, donc, à la suite de Poincaré, nous comprendrons un groupe continu comme un groupe infini d'opérations discrètes. Maintenant que nous avons compris la géométrie, nous devons nous tourner vers la relativité du mouvement.


Cosmologie de la toile. Grille modulaire


Depuis un siècle, en tant qu'humanité, l' expansion de l'univers est connue. En observant des objets éloignés, tels que des galaxies et des quasars, nous enregistrons le déplacement du spectre électromagnétique vers des ondes plus longues, ce que l'on appelle le décalage vers le rouge cosmologique. Toute mesure relie l'observateur, l'observé et les moyens de mesure par rapport auxquels nous effectuons nos mesures. Sans instruments de mesure, il est impossible d'établir des relations invariantes dans la nature, c'est-à-dire de déterminer la géométrie de l'Univers. Cependant, la géométrie perd son sens sans observabilité. Donc, dans notre tâche, il est agréable d'avoir des repères, comme les galaxies, par rapport à la lumière desquels nous pouvons déterminer la relativité du mouvement de notre toile. Une telle structure peut être un réseau périodique, qui bifurque à chaque fois que l'espace est agrandi deux fois.


Le réseau étant périodique, il est pratique d'adopter une algèbre modulaire. Ainsi, nous agirons en groupe Paussi sur le tore T2=S1 foisS1. Puisque l'écran du moniteur n'est pas un plan continu, mais un réseau entier  mathbbZ2(on néglige maintenant que c'est fini), puis l'action du groupe Pdoit être considéré sur un tore entier  mathbbZp2p- la taille du bord du carré p foisp:





Ainsi, une fois pour toutes fixant notre tore près de l'origine, nous y effectuerons tous les calculs ultérieurs. Ensuite, propagez-le à l'aide des méthodes de bibliothèque de canevas standard. Voici à quoi ressemble un mouvement d'un pixel:





Évidemment, l'opération standard de prise du module x% p ne nous convient pas, car elle traduit des valeurs négatives de l'argument en valeurs négatives, mais sur le tore entier il n'y en a pas. Écrivez votre fonction x modp:



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

Revenons maintenant à la machine d'état finale et


le définir
La classe FSM est commodément héritée d'EventEmitter, ce qui nous permettra de nous abonner.
 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); } } } } 


Ensuite, définissez un schéma de transition, créez un canevas et


tout initialiser.
 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); 


Ensuite, vous devez déterminer la fonction de rendu, définir les valeurs initiales nécessaires et vous abonner au changement d'état. Considérez la partie la plus intéressante du 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(); }); 

Premièrement, nous ne développons pas le coefficient k, mais un certain rapport nk / k. Cela est dû au fait que l'étape m de notre cartographie à un point fixe (a,b)exprimé en


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


ou, par rapport aux valeurs initiales x1,y1


xm=(x1a) prodi=1m1ki+aym=(y1b) prodi=1m1ki+b


Evidemment le produit  prodi=1m1kiil y a une fonction non linéaire de l'étape d'itération et converge très rapidement vers zéro, ou s'enfuit à l'infini avec de petits écarts initiaux.


Nous introduisons la variable g, qui est une mesure de doublement de notre toile. Évidemment, il prend une valeur constante sur un certain intervalle. Pour atteindre la linéarité kinous utilisons une substitution homogène


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


Ensuite, tous les membres de l'œuvre, à l'exception du premier et du dernier, seront réduits:


 prodi=1m1ki= prodi=1m1 frackiki1= frack21 frack3k2... frackm2km3 frackm1km2=km1


De plus, le saut de phase g réduit le taux d'expansion de telle manière que la structure fractale qui se déplie devant nous se déplace toujours de façon linéaire. Ainsi, nous obtenons une variation approximative de la loi de puissance de Hubble de l'expansion de l'Univers.


Reste à comprendre les limites de précision de notre modèle.


Fluctuations quantiques. Champ de numéro 2-adic


La compréhension du processus de mesure a conduit au concept d'un nombre réel. Le principe d'incertitude de Heisenberg pointe ses limites. Un ordinateur moderne ne fonctionne pas avec des nombres réels, mais avec des mots machine, dont la longueur est déterminée par la capacité du processeur. Les mots machine forment un champ de nombres 2-adiques  mathbbQ2et sont désignés comme  mathbbF2nn- longueur du mot. Dans ce cas, le processus de mesure est remplacé par le processus de calcul et est associé à une métrique non archimédienne:


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


Ainsi, notre modèle a une limite de calcul. Les limitations sont décrites dans la norme IEEE_754 . À partir d'un moment donné, notre taille de base dépassera la limite de précision et l'opération de prise du module commencera à générer des erreurs ressemblant à des séquences pseudo-aléatoires. Ceci est simplement vérifié en supprimant la ligne


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

La limite finale dans notre cas est calculée par la méthode semi-empirique, puisque nous travaillons avec plusieurs paramètres.


Conclusion


Ainsi, apparemment distantes à première vue, les théories sont connectées sur la toile du navigateur. Les concepts d'action, de mesure et de calcul sont étroitement liés les uns aux autres. Le problème de leur combinaison n'est toujours pas résolu.


Résultat

PS
Il était clair que le code source serait petit, j'ai donc dirigé le développement dans le fichier index.html ordinaire. En écrivant cet article, j'ai ajouté la saisie que j'ai testée dans la cour de récréation TypeScript.

Source: https://habr.com/ru/post/fr467959/


All Articles