Perfetto, precisione infinita, fisica del gioco in Python (parte 1)
Con assoluta precisione, programma "Newton's Cradle", "tennis ball & basketball drop" e altro ancora
Questo è il primo di quattro articoli che mostrano come programmare un perfetto motore fisico in Python. È un piccolo passo nella mia grande ambizione di trasformare tutta la fisica, la matematica e persino la filosofia in programmazione. Attraverso questo progetto, scopriremo sorprese, aumenteremo la nostra comprensione e (spero) ci divertiremo. Tutto il codice è disponibile su GitHub .
Limiteremo il motore in molti modi, ad esempio alle collisioni newtoniane tra cerchi e linee. Tuttavia, non limiteremo la precisione del motore. Rappresenterà tutti i tempi, le posizioni e le velocità con espressioni esatte come 8*sqrt(3)/3
. In altre parole, evita tutte le approssimazioni numeriche.
I risultati saranno simulazioni perfette, ad esempio, del giocattolo della culla di Newton. (Per riprodurre/mettere in pausa il video, premi il pulsante nell'angolo in basso a sinistra. Il suono è utile.)
A cosa serve un motore fisico con una precisione assoluta? Tra le altre cose, può migliorare la nostra comprensione del mondo fisico. In questo articolo (Parte 1), vedremo queste nuove scoperte:
- Limite di velocità della pallina da tennis — In una famosa dimostrazione di fisica, fai cadere una pallina da tennis con un pallone da basket. La pallina da tennis rimbalza molto più velocemente di quanto cade. Quanto più veloce? Vedremo che la velocità "su" non è mai superiore a tre volte la velocità "giù". Ciò vale anche se rendiamo il basket infinitamente massiccio.
- Buone vibrazioni : immagina una pallina da tennis in movimento intrappolata tra un pallone da basket e un muro. Nel breve tempo che intercorre tra due fotogrammi del video, la pallina da tennis può rimbalzare 80 volte. Un motore che utilizza approssimazioni potrebbe perdere alcuni di questi rimbalzi. Il nostro motore conta tutti.
- Billiards Broken - Anche con una precisione infinita, una rottura del biliardo non può essere praticamente annullata. Vedremo che anche quando rimuoviamo l'"effetto farfalla" e l'incertezza quantistica, il mondo è ancora casuale e non deterministico.
Costruire il motore
Supponiamo di avere già funzioni Python per:
- Dati due oggetti qualsiasi (possibilmente in movimento) nel mondo, restituisci l'intervallo di tempo esatto fino a quando non si toccheranno. La risposta potrebbe essere "mai".
- Dati due oggetti in collisione, in che modo esattamente la collisione cambierà la loro velocità e direzione?
Elaboriamo un approccio osservando tre mondi della fisica, a partire da Newton's Cradle.
Culla di Newton
L'idea generale per il nostro motore è trovare l'ora esatta fino alla prossima collisione nel mondo. Vai avanti esattamente a quel momento. Regola la velocità degli oggetti in collisione. Ripetere.
Per Newton's Cradle, abbiamo prima creato cerchi e muri.
from perfect_physics import World, Circle, Wall
circle_list = [Circle(x=1, y=0, r=1, vx=1, vy=0, m=1)]
for i in range(1, 6):
circle_list.append(Circle(x=i * 2 + 4, y=0, r=1, vx=0, vy=0, m=1))
wall_list = [Wall(x0=0, y0=0, x1=0, y1=1), Wall(x0=20, y0=0, x1=20, y1=1)]
world = World(circle_list, wall_list, xlim=(-1, 21), ylim=(-2, 2))
world.show()
- Tra ogni coppia di oggetti, trova l'intervallo di tempo esatto in cui si scontrano (se presente). Ignora altre coppie e collisioni. — In questo caso, il primo cerchio (in movimento) si scontrerà con il secondo cerchio all'intervallo di tempo 3. Ignorando quella collisione, si scontrerà anche con il terzo cerchio all'intervallo di tempo 5, ecc. Si scontrerà con il muro più lontano all'istante campata 18.
- Trova l'intervallo di tempo delle prime collisioni e fai avanzare l'orologio mondiale esattamente di quel tanto. In questo caso, 3.
- Regola le velocità di tutti gli oggetti coinvolti in queste collisioni. Come previsto con la culla di Newton, il primo cerchio diventa stazionario e il secondo inizia a muoversi.
- Ripeti per tutto il tempo che desideri. Nell'esempio, la seconda e la terza pallina si scontreranno successivamente nell'intervallo di tempo 0. Questo video mostra l'azione, evento per evento. (Un "evento" sta spostando il tempo in avanti fino a una collisione o regolando le velocità in base alla collisione)
In generale, per ogni fotogramma:
- Trova il valore dell'orologio di simulazione per quel fotogramma.
- Trova l'evento di collisione che precede quel valore di clock.
- Partendo dal mondo alla collisione precedente, sposta i cerchi sul valore di clock del fotogramma. (In base alla progettazione, i cerchi non entreranno in collisione con nulla durante questa mossa.)
Cerchio e triangolo
Sostengo che i tempi e le posizioni sono tutti esatti, ma mi credi? Ecco alcune prove tramite un cerchio inscritto da un triangolo. Innanzitutto, ecco il video che mostra solo gli eventi. Puoi vedere l'orologio impostato su espressioni come 8*sqrt(3)/3
.
Ed ecco il normale video per il cerchio in un triangolo:
In questo caso, la simulazione rientra in uno schema. Senza una precisione infinita, il modello avrebbe potuto essere perso.
Palla da tennis e lancio di pallacanestro
Diamo un'occhiata a un altro mondo fisico per vedere un altro vantaggio di precisione infinita. Questo mondo include una pallina da tennis e un pallone da basket che si muovono verso un muro a velocità 1. La massa del pallone da basket è 1000 volte quella della pallina da tennis. (I nostri mondi non hanno gravità.)
from perfect_physics import World, Circle, Wall
big_radius = 10
world_width = 40
folder = root / f"part1/two_size{big_radius}"
big = Circle(x=world_width // 2, y=0, r=big_radius, vx=1, vy=0, m=big_radius**3)
little = Circle(x=big.x - big_radius - 1, y=0, r=1, vx=1, vy=0, m=1)
circle_list = [big, little]
wall_list = [Wall(x0=0, y0=0, x1=0, y1=1), Wall(x0=world_width, y0=0, x1=world_width, y1=1)]
world = World(circle_list, wall_list, xlim=(-1, world_width + 1), ylim=(-big_radius - 1, big_radius + 1))
world.run_in_place(2, show=True)
print([circle.vx for circle in world.circle_list])
Come previsto (se l'hai già provato nel mondo fisico), la pallina da tennis accelera. Mi ha sorpreso, tuttavia, andando solo circa tre volte più veloce.
Andiamo un attimo avanti e usiamo gli strumenti che svilupperemo nella Parte 3. Questo codice Python mostra l'effetto sulle palline da tennis di un pallone da basket con massa infinita. La pallina da tennis entra con velocità 1 ed esce esattamente con velocità 3.
from sympy import limit, simplify, oo
from perfect_physics import load
cc_velocity_solution = load("data/cc_velocity_solution.sympy")
a_vx, a_vy, b_vx, b_vy = cc_velocity_solution.subs([("a_x", 10), ("a_y", 0), ("a_r", 10), ("a_vx", -1), ("a_vy", 0),
("b_x", -1), ("b_y", 0), ("b_r", 1), ("b_vx", 1), ("b_vy", 0), ("b_m", 1)])
print(simplify(b_vx))
limit(b_vx, "a_m", oo)
# prints (1 - 3*a_m)/(a_m + 1)
# returns -3
Il motore mi ha sorpreso ancora di più quando l'ho lasciato girare più a lungo. Guarda cosa succede al tempo del video 20 secondi (tempo di simulazione 200):
Il basket schiaccia la pallina da tennis. Ciò fa rimbalzare avanti e indietro la pallina da tennis più di 80 volte in un singolo fotogramma video. La pallina da tennis raggiunge una velocità superiore a 31. Sulla traccia audio del video, la vibrazione della pallina da tennis produce un tono superiore a 2000 Hz (battiti/secondo).
Se invece del calcolo esatto, campionassimo solo una volta per fotogramma, perderemmo (o calcoliamo male) questa azione rapida.
Riassumendo Parte 1
Funziona! Abbiamo un motore fisico perfetto. Abbiamo trasformato un po' di fisica in programmazione.
Abbiamo ipotizzato di aver iniziato con due funzioni Python che ci dicessero - per ogni coppia di oggetti - due cose 1) Il tempo fino alla loro prossima collisione (se presente) e 2) L'effetto di quella collisione sulle loro velocità. Abbiamo visto come trasformare queste due funzioni in un perfetto motore fisico ripetutamente: andando avanti nel tempo fino alla collisione successiva e regolando le velocità di tutti gli oggetti coinvolti nella collisione.
Abbiamo creato video cercando la collisione che ha preceduto un fotogramma video e spostando il tempo in avanti (senza preoccuparci delle collisioni) fino a quel fotogramma.
La culla di Newton, il primo mondo, si è comportata come previsto. Il secondo mondo, un cerchio inscritto da un triangolo, usando espressioni come 8*sqrt(3)/3
per il tempo, ecc., trovò uno schema. La caduta della pallina da tennis e del basket ha scoperto un limite sorprendente per me sulla velocità della pallina da tennis. Inoltre, produceva rimbalzi più veloci di quanto potessero calcolare metodi meno perfetti.
L'articolo successivo, Parte 2 , simula una partita a biliardo con risultati filosofici sorprendenti per me. Successivamente, nella Parte 3, vedremo come fare in modo che il computer crei queste due funzioni iniziali. Infine, nella Parte 4, accelereremo un po' il motore (ma non abbastanza) e discuteremo i limiti.
Se hai idee per simulazioni che vorresti che eseguissi, inviamele. Possono diventare la base di una parte 5.
Puoi scaricare questo codice da CarlKCarlK/perfect-physics (github.com) . Fammi sapere se c'è interesse e creerò un programma di installazione migliore.
Segui Carl M. Kadie - Medium per le notifiche delle parti successive. Tutte le parti saranno libere di leggere. Infine, su YouTube, ho vecchie simulazioni fisiche (approssimative) e alcuni video sulla natura che cercano di essere divertenti .