Schreiben Sie eine schnellere Python-Physik-simulator
Ich gespielt haben, um mit dem schreiben meiner eigenen Physik-engine in Python als eine übung in der Physik und der Programmierung. Ich habe nach dem tutorial befindet sich hier. Das ging auch, aber dann fand ich den Artikel "Advanced character physics" von thomas jakobsen, die mit Verlet-integration für Simulationen, die ich faszinierend fand.
Habe ich schon versucht zu schreiben, meine eigenen basic-Physik-simulator mit verlet-integration, aber es erweist sich als etwas schwieriger, als ich es zunächst erwartet. Ich war auf der Suche nach Beispiel-Programme zu Lesen, und stolperte über das man in Python geschrieben und ich fand auch dieses tutorial die Verarbeitung verwendet.
Was mich beeindruckt, über die Verarbeitung version ist, wie schnell es läuft. Das Tuch alleine hat 2400 verschiedene Punkte simuliert, und das ist nicht einschließlich der stellen.
Python-Beispiel verwendet nur 256 Teilchen für das Tuch, und es läuft mit etwa 30 frames pro Sekunde. Ich habe versucht, die Erhöhung der Anzahl der Partikel, die in 2401 (es muss Platz für das Programm zu arbeiten), es lief in etwa 3 fps.
Diese beiden arbeiten durch speichern von Instanzen der Partikel-Objekt in eine Liste, und dann die Iteration durch die Liste, indem Sie jedes Teilchen "update " position" - Methode. Als ein Beispiel, dies ist der Teil des Codes von der Verarbeitung, Skizze, berechnet für jedes Partikel die neue Position:
for (int i = 0; i < pointmasses.size(); i++) {
PointMass pointmass = (PointMass) pointmasses.get(i);
pointmass.updateInteractions();
pointmass.updatePhysics(fixedDeltaTimeSeconds);
}
EDIT: Hier ist der code aus dem python-version, die ich gelinkt früher:
"""
verletCloth01.py
Eric Pavey - 2010-07-03 - www.akeric.com
Riding on the shoulders of giants.
I wanted to learn now to do 'verlet cloth' in Python\Pygame. I first ran across
this post \ source:
http://forums.overclockers.com.au/showthread.php?t=870396
http://dl.dropbox.com/u/3240460/cloth5.py
Which pointed to some good reference, that was a dead link. After some searching,
I found it here:
http://www.gpgstudy.com/gpgiki/GDC%202001%3A%20Advanced%20Character%20Physics
Which is a 2001 SIGGRAPH paper by Thomas Jakobsen called:
"GDC 2001: Advanced Characer Physics".
This code is a Python\Pygame interpretation of that 2001 Siggraph paper. I did
borrow some code from 'domlebo's source code, it was a great starting point. But
I'd like to think I put my own flavor on it.
"""
#--------------
# Imports & Initis
import sys
from math import sqrt
# Vec2D comes from here: http://pygame.org/wiki/2DVectorClass
from vec2d import Vec2d
import pygame
from pygame.locals import *
pygame.init()
#--------------
# Constants
TITLE = "verletCloth01"
WIDTH = 600
HEIGHT = 600
FRAMERATE = 60
# How many iterations to run on our constraints per frame?
# This will 'tighten' the cloth, but slow the sim.
ITERATE = 2
GRAVITY = Vec2d(0.0,0.05)
TSTEP = 2.8
# How many pixels to position between each particle?
PSTEP = int(WIDTH*.03)
# Offset in pixels from the top left of screen to position grid:
OFFSET = int(.25*WIDTH)
#-------------
# Define helper functions, classes
class Particle(object):
"""
Stores position, previous position, and where it is in the grid.
"""
def __init__(self, screen, currentPos, gridIndex):
# Current Position : m_x
self.currentPos = Vec2d(currentPos)
# Index [x][y] of Where it lives in the grid
self.gridIndex = gridIndex
# Previous Position : m_oldx
self.oldPos = Vec2d(currentPos)
# Force accumulators : m_a
self.forces = GRAVITY
# Should the particle be locked at its current position?
self.locked = False
self.followMouse = False
self.colorUnlocked = Color('white')
self.colorLocked = Color('green')
self.screen = screen
def __str__(self):
return "Particle <%s, %s>"%(self.gridIndex[0], self.gridIndex[1])
def draw(self):
# Draw a circle at the given Particle.
screenPos = (self.currentPos[0], self.currentPos[1])
if self.locked:
pygame.draw.circle(self.screen, self.colorLocked, (int(screenPos[0]),
int(screenPos[1])), 4, 0)
else:
pygame.draw.circle(self.screen, self.colorUnlocked, (int(screenPos[0]),
int(screenPos[1])), 1, 0)
class Constraint(object):
"""
Stores 'constraint' data between two Particle objects. Stores this data
before the sim runs, to speed sim and draw operations.
"""
def __init__(self, screen, particles):
self.particles = sorted(particles)
# Calculate restlength as the initial distance between the two particles:
self.restLength = sqrt(abs(pow(self.particles[1].currentPos.x -
self.particles[0].currentPos.x, 2) +
pow(self.particles[1].currentPos.y -
self.particles[0].currentPos.y, 2)))
self.screen = screen
self.color = Color('red')
def __str__(self):
return "Constraint <%s, %s>"%(self.particles[0], self.particles[1])
def draw(self):
# Draw line between the two particles.
p1 = self.particles[0]
p2 = self.particles[1]
p1pos = (p1.currentPos[0],
p1.currentPos[1])
p2pos = (p2.currentPos[0],
p2.currentPos[1])
pygame.draw.aaline(self.screen, self.color,
(p1pos[0], p1pos[1]), (p2pos[0], p2pos[1]), 1)
class Grid(object):
"""
Stores a grid of Particle objects. Emulates a 2d container object. Particle
objects can be indexed by position:
grid = Grid()
particle = g[2][4]
"""
def __init__(self, screen, rows, columns, step, offset):
self.screen = screen
self.rows = rows
self.columns = columns
self.step = step
self.offset = offset
# Make our internal grid:
# _grid is a list of sublists.
# Each sublist is a 'column'.
# Each column holds a particle object per row:
# _grid =
# [[p00, [p10, [etc,
# p01, p11,
# etc], etc], ]]
self._grid = []
for x in range(columns):
self._grid.append([])
for y in range(rows):
currentPos = (x*self.step+self.offset, y*self.step+self.offset)
self._grid[x].append(Particle(self.screen, currentPos, (x,y)))
def getNeighbors(self, gridIndex):
"""
return a list of all neighbor particles to the particle at the given gridIndex:
gridIndex = [x,x] : The particle index we're polling
"""
possNeighbors = []
possNeighbors.append([gridIndex[0]-1, gridIndex[1]])
possNeighbors.append([gridIndex[0], gridIndex[1]-1])
possNeighbors.append([gridIndex[0]+1, gridIndex[1]])
possNeighbors.append([gridIndex[0], gridIndex[1]+1])
neigh = []
for coord in possNeighbors:
if (coord[0] < 0) | (coord[0] > self.rows-1):
pass
elif (coord[1] < 0) | (coord[1] > self.columns-1):
pass
else:
neigh.append(coord)
finalNeighbors = []
for point in neigh:
finalNeighbors.append((point[0], point[1]))
return finalNeighbors
#--------------------------
# Implement Container Type:
def __len__(self):
return len(self.rows * self.columns)
def __getitem__(self, key):
return self._grid[key]
def __setitem__(self, key, value):
self._grid[key] = value
#def __delitem__(self, key):
#del(self._grid[key])
def __iter__(self):
for x in self._grid:
for y in x:
yield y
def __contains__(self, item):
for x in self._grid:
for y in x:
if y is item:
return True
return False
class ParticleSystem(Grid):
"""
Implements the verlet particles physics on the encapsulated Grid object.
"""
def __init__(self, screen, rows=49, columns=49, step=PSTEP, offset=OFFSET):
super(ParticleSystem, self).__init__(screen, rows, columns, step, offset)
# Generate our list of Constraint objects. One is generated between
# every particle connection.
self.constraints = []
for p in self:
neighborIndices = self.getNeighbors(p.gridIndex)
for ni in neighborIndices:
# Get the neighbor Particle from the index:
n = self[ni[0]][ni[1]]
# Let's not add duplicate Constraints, which would be easy to do!
new = True
for con in self.constraints:
if n in con.particles and p in con.particles:
new = False
if new:
self.constraints.append( Constraint(self.screen, (p,n)) )
# Lock our top left and right particles by default:
self[0][0].locked = True
self[1][0].locked = True
self[-2][0].locked = True
self[-1][0].locked = True
def verlet(self):
# Verlet integration step:
for p in self:
if not p.locked:
# make a copy of our current position
temp = Vec2d(p.currentPos)
p.currentPos += p.currentPos - p.oldPos + p.forces * TSTEP**2
p.oldPos = temp
elif p.followMouse:
temp = Vec2d(p.currentPos)
p.currentPos = Vec2d(pygame.mouse.get_pos())
p.oldPos = temp
def satisfyConstraints(self):
# Keep particles together:
for c in self.constraints:
delta = c.particles[0].currentPos - c.particles[1].currentPos
deltaLength = sqrt(delta.dot(delta))
try:
# You can get a ZeroDivisionError here once, so let's catch it.
# I think it's when particles sit on top of one another due to
# being locked.
diff = (deltaLength-c.restLength)/deltaLength
if not c.particles[0].locked:
c.particles[0].currentPos -= delta*0.5*diff
if not c.particles[1].locked:
c.particles[1].currentPos += delta*0.5*diff
except ZeroDivisionError:
pass
def accumulateForces(self):
# This doesn't do much right now, other than constantly reset the
# particles 'forces' to be 'gravity'. But this is where you'd implement
# other things, like drag, wind, etc.
for p in self:
p.forces = GRAVITY
def timeStep(self):
# This executes the whole shebang:
self.accumulateForces()
self.verlet()
for i in range(ITERATE):
self.satisfyConstraints()
def draw(self):
"""
Draw constraint connections, and particle positions:
"""
for c in self.constraints:
c.draw()
#for p in self:
# p.draw()
def lockParticle(self):
"""
If the mouse LMB is pressed for the first time on a particle, the particle
will assume the mouse motion. When it is pressed again, it will lock
the particle in space.
"""
mousePos = Vec2d(pygame.mouse.get_pos())
for p in self:
dist2mouse = sqrt(abs(pow(p.currentPos.x -
mousePos.x, 2) +
pow(p.currentPos.y -
mousePos.y, 2)))
if dist2mouse < 10:
if not p.followMouse:
p.locked = True
p.followMouse = True
p.oldPos = Vec2d(p.currentPos)
else:
p.followMouse = False
def unlockParticle(self):
"""
If the RMB is pressed on a particle, if the particle is currently
locked or being moved by the mouse, it will be 'unlocked'/stop following
the mouse.
"""
mousePos = Vec2d(pygame.mouse.get_pos())
for p in self:
dist2mouse = sqrt(abs(pow(p.currentPos.x -
mousePos.x, 2) +
pow(p.currentPos.y -
mousePos.y, 2)))
if dist2mouse < 5:
p.locked = False
#------------
# Main Program
def main():
# Screen Setup
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
# Create our grid of particles:
particleSystem = ParticleSystem(screen)
backgroundCol = Color('black')
# main loop
looping = True
while looping:
clock.tick(FRAMERATE)
pygame.display.set_caption("%s -- www.AKEric.com -- LMB: move\lock - RMB: unlock - fps: %.2f"%(TITLE, clock.get_fps()) )
screen.fill(backgroundCol)
# Detect for events
for event in pygame.event.get():
if event.type == pygame.QUIT:
looping = False
elif event.type == MOUSEBUTTONDOWN:
if event.button == 1:
# See if we can make a particle follow the mouse and lock
# its position when done.
particleSystem.lockParticle()
if event.button == 3:
# Try to unlock the current particles position:
particleSystem.unlockParticle()
# Do stuff!
particleSystem.timeStep()
particleSystem.draw()
# update our display:
pygame.display.update()
#------------
# Execution from shell\icon:
if __name__ == "__main__":
print "Running Python version:", sys.version
print "Running PyGame version:", pygame.ver
print "Running %s.py"%TITLE
sys.exit(main())
Weil beide Programme arbeiten in etwa der gleichen Weise, aber die Python-version ist SO viel langsamer, es macht mich Frage mich:
- Ist dieser Leistungsunterschied Teil der Natur von Python?
- Was sollte ich anders machen, aus den oben, wenn ich will, um eine bessere Leistung aus meinem eigenen Python-Programme? E. g speichern Sie die Eigenschaften aller Teilchen im inneren ein array anstelle der Verwendung von einzelnen Objekten, etc.
EDIT: Beantwortet!!!
@Mr E verbunden ist PyCon reden in die Kommentare und @A. Rosa Antwort mit dem die verlinkten Ressourcen-all das half ENORM, in ein besseres Verständnis, wie Sie schreiben, gut, schnelle python-code. Ich bin jetzt Bookmark diese Seite für zukünftige Referenz 😀
- Ein allgemeiner Punkt. Es gibt eine schöne Pycon video über die über-Nutzung von Klassen. Der Referent hält das aufzeigen Beispiele von Klassen mit "zwei Methoden, eine davon ist
__init__
" , sagen, dass Sie besser vertreten als Methoden (ignorieren des__str__
Funktionen hier). Sie könnte leicht ersetzen Sie Ihre Teilchen mit, sagen wir, einnamedtuple
oder und haben eindraw_particle
Funktion. - Oh, ich sehe auch, dass es nicht dein code ist also vielleicht nicht relevant...
- Ich Liebe diese Vorlage!!! Ich fand zwar heraus, dass Menschen dazu neigen, nicht zu nehmen sehr gut zu wies ihm, eine Reaktion, erinnert mich an dieser.
- Hah! Ja, müssen Sie wählen Sie Ihre Momente sorgfältig..
Du musst angemeldet sein, um einen Kommentar abzugeben.
Es ist ein Guido van Rossum ist der Artikel Links in den Abschnitt Performance-Tipps der Python Wiki. In seiner Schlussfolgerung, Lesen Sie den folgenden Satz:
Der Aufsatz setzt sich mit einer Liste von Leitlinien für die loop-Optimierung. Ich empfehle beide Ressourcen, denn Sie geben konkrete und praktische Tipps zur Optimierung von Python-code.
Es ist auch eine bekannte Gruppe von benchmarks in benchmarksgame.alioth.debian.org, wo Sie finden können comparasions unter den verschiedenen Programmen und Sprachen, die in unterschiedlichen Maschinen. Wie gesehen werden kann, gibt es viele Variablen im Spiel, die unmöglich macht Stand, etwas breiter als Java ist schneller als Python. Dies wird Häufig zusammengefasst in dem Satz "Sprachen nicht über Geschwindigkeiten; Implementierungen".
In Ihren code angewendet werden können, mehr pythonic und schnellere alternativen mit built-in-Funktionen. Zum Beispiel gibt es mehrere verschachtelte Schleifen (einige von Ihnen nicht verlangen, verarbeitet die gesamte Liste), die umgeschrieben werden kann mit
imap
oder Liste Verstehens. PyPy ist auch eine weitere interessante option zur Verbesserung der Leistung. Ich bin kein Experte über Python-Optimierung, aber es gibt viele Tipps, die sehr nützlich sind (Beachten Sie, dass nicht das schreiben von Java-in Python ist einer von Ihnen!).Ressourcen und anderen Fragen auf ALSO:
Wenn Sie schreiben Python-wie Sie schreiben-Java, natürlich, es wird langsamer sein, idiomatischen java nicht gut übersetzen zu idiomatic python.
Schwer zu sagen, ohne zu sehen, Ihren code.
Hier eine unvollständige Liste der Unterschiede zwischen python und java, das manchmal die Leistung beeinträchtigen:
Verarbeitung verwendet immediate-mode canvas-Bereich, wenn Sie möchten, eine vergleichbare Leistung in Python, müssen Sie auch anwenden, immediate-mode canvas. Leinwände in den meisten GUI-framework (einschließlich Tkinter-canvas) wird beibehalten-Modus, der einfacher zu bedienen ist, aber grundsätzlich langsamer als immediate-mode. Benötigen Sie immediate-mode canvas wie die von pygame, SDL, oder Pyglet.
Python ist eine dynamische Sprache, das heißt, Instanz-member zugreifen, Modul-Mitglied Zugang und Globale variable-Zugriff wird zur Laufzeit aufgelöst. Instanz-member zugreifen, Modul-Mitglied Zugang und Globale variable-Zugriff in python ist wirklich Wörterbuch-Zugang. In java, Sie sind entschlossen, zur compile-Zeit und von seiner Natur viel schneller. Cache Häufig aufgerufene globals -, Modul-Variablen und Attribute, um eine lokale variable.
In python 2.x, range() erzeugt eine konkrete Liste, in python iteration erfolgt mit iterator
for item in list
, ist in der Regel schneller als die iteration erfolgt mittels iteration variablefor n in range(len(list))
. Sollte man fast immer Durchlaufen direkt über den iterator anstatt der Iteration mit range(len(...)).Python ' s zahlen ist unveränderlich, das bedeutet, dass jede arithmetische Berechnung ordnet ein neues Objekt. Dies ist ein Grund, warum plain python ist nicht sehr geeignet für low-level-Berechnungen; die meisten Menschen wollen, dass schreiben zu können, die low-level-Berechnungen die zimmerreserviereung, ohne das resort zu schreiben C-Erweiterung, verwendet in der Regel cython, psyco, oder numpy. Diese Regel wird nur ein problem, wenn Sie haben Millionen von Berechnungen wenn.
Diese sind nur teilweise sehr unvollständige Liste, es gibt viele andere Gründe, warum übersetzen von java zu python erzeugen würde suboptimalen code. Ohne zu sehen, Ihren code es ist unmöglich zu sagen, was Sie tun müssen, anders. Optimiert python-code ist in der Regel ganz anders aussieht als optimierter java-code.
Ich würde auch vorschlagen, zu Lesen, über andere Physik-engines. Es gibt ein paar open-source-engines, die eine Vielzahl von Methoden für die Berechnung der "Physik".
Gibt es auch ports von den meisten der Motoren:
Wenn Sie Lesen Sie die Dokumentation zu den Motoren findet man oft Aussagen, die sagen, dass Sie optimiert für Geschwindigkeit (30fps - 60fps). Aber wenn Sie denken, Sie können dies tun, während die Berechnung der "echten" Physik-Sie sind falsch. Die meisten Motoren berechnen die Physik an einem Punkt, wo ein normaler user nicht optisch unterscheiden Sie zwischen "echten" physikalischen Verhalten " und "simuliert" das physikalische Verhalten. Aber wenn Sie untersuchen die Fehler, es ist neglectable, wenn Sie wollen, um Spiele schreiben. Aber wenn Sie wollen, zu tun, Physik, alle diese Motoren sind von keinerlei nutzen für Sie.
Das ist, warum ich sagen würde, wenn Sie dabei sind, eine Reale physische simulation, Sie sind langsamer als die Motoren von design-und Sie wird niemals schneller sein als eine andere Physik-engine.
Teilchen-Physik-simulation übersetzt sich leicht in die lineare algebra-Operationen, dh. matrix-Operationen. Numpy bietet solche Operationen, die implementiert in Fortran/C/C++ unter der Haube. Gut geschriebene python/Numpy-code (unter voller Ausnutzung der Sprache & Bibliothek) ermöglicht das schreiben anständig schnellen code.