import sys import numpy as np import cupy as cp import pygame # Minimaler, GPU-beschleunigter 2D-Fluid-Solver mit persistenter Flüssigkeit und Gravitation. # Installation: # pip install cupy-cudaXX pygame numpy # XX = passende CUDA-Version, z.B. cupy-cuda112 für CUDA 11.2 # ============================== # EINSTELLUNGEN # ============================== N = 64 # Gittergröße (N x N) ITER = 2 # Iterationen; verringern für Geschwindigkeit DT = 0.1 # Zeitschritt DIFF = 0.0001 # Diffusionskoeffizient (klein, aber nicht null für Stabilität) VISC = 0.0001 # Viskosität (klein, aber nicht null für Stabilität) ZOOM = 8 # Pixel pro Gitterzelle in der Anzeige GRAVITY = 0.1 # Gravitationskraft (anpassbar) WIDTH, HEIGHT = N * ZOOM, N * ZOOM # ============================== # HILFSFUNKTIONEN # ============================== def set_bnd(b, x): """ Randbehandlung für die Simulation. b = 0: Dichte b = 1: u-Geschwindigkeit b = 2: v-Geschwindigkeit """ if b == 1: # u-Geschwindigkeit: links und rechts auf 0 setzen (Wände) x[0, 1:-1] = 0 x[-1, 1:-1] = 0 elif b == 2: # v-Geschwindigkeit: oben und unten auf 0 setzen (Wände) x[1:-1, 0] = 0 x[1:-1, -1] = 0 else: # Dichte: Spiegeln an den Rändern x[1:-1, 0] = x[1:-1, 1] x[1:-1, -1] = x[1:-1, -2] x[0, 1:-1] = x[1, 1:-1] x[-1, 1:-1] = x[-2, 1:-1] # Ecken setzen x[0, 0] = 0.5 * (x[1, 0] + x[0, 1]) x[0, -1] = 0.5 * (x[1, -1] + x[0, -2]) x[-1, 0] = 0.5 * (x[-2, 0] + x[-1, 1]) x[-1, -1] = 0.5 * (x[-2, -1] + x[-1, -2]) def add_source(x, s): """ Fügt eine Quelle zur Simulation hinzu. """ x += DT * s def diffuse(b, x, x0, diff): """ Diffusionsschritt der Simulation. """ a = DT * diff * (N - 2) * (N - 2) for _ in range(ITER): x[1:-1, 1:-1] = (x0[1:-1, 1:-1] + a * ( x[0:-2, 1:-1] + x[2:, 1:-1] + x[1:-1, 0:-2] + x[1:-1, 2:] )) / (1 + 4*a) set_bnd(b, x) def advect(b, d, d0, u, v): """ Advektionsschritt der Simulation. """ dt0 = DT * (N - 2) # Rückverfolgte Position berechnen X, Y = cp.meshgrid(cp.arange(N), cp.arange(N), indexing='ij') X = X[1:-1, 1:-1] - dt0 * u[1:-1, 1:-1] Y = Y[1:-1, 1:-1] - dt0 * v[1:-1, 1:-1] X = cp.clip(X, 0.5, N - 1.5) Y = cp.clip(Y, 0.5, N - 1.5) i0 = X.astype(int) i1 = i0 + 1 j0 = Y.astype(int) j1 = j0 + 1 s1 = X - i0 s0 = 1 - s1 t1 = Y - j0 t0 = 1 - t1 d_new = (s0 * (t0 * d0[i0, j0] + t1 * d0[i0, j1]) + s1 * (t0 * d0[i1, j0] + t1 * d0[i1, j1])) d[1:-1, 1:-1] = d_new set_bnd(b, d) def project(u, v, p, div): """ Projektionsschritt zur Inkompressibilität der Flüssigkeit. """ # Divergenz berechnen div[1:-1,1:-1] = -0.5 * ( (u[2:,1:-1] - u[0:-2,1:-1]) + (v[1:-1,2:] - v[1:-1,0:-2]) ) / N p.fill(0) set_bnd(0, div) set_bnd(0, p) # Poisson-Gleichung lösen for _ in range(ITER): p[1:-1,1:-1] = (div[1:-1,1:-1] + p[0:-2,1:-1] + p[2:,1:-1] + p[1:-1,0:-2] + p[1:-1,2:]) / 4 set_bnd(0, p) # Geschwindigkeiten aktualisieren u[1:-1,1:-1] -= 0.5 * (p[2:,1:-1] - p[0:-2,1:-1]) * N v[1:-1,1:-1] -= 0.5 * (p[1:-1,2:] - p[1:-1,0:-2]) * N set_bnd(1, u) set_bnd(2, v) # ============================== # FLUID-KLASSE # ============================== class Fluid: def __init__(self): self.dens = cp.zeros((N, N), dtype=cp.float32) self.dens_prev = cp.zeros((N, N), dtype=cp.float32) self.u = cp.zeros((N, N), dtype=cp.float32) self.u_prev = cp.zeros((N, N), dtype=cp.float32) self.v = cp.zeros((N, N), dtype=cp.float32) self.v_prev = cp.zeros((N, N), dtype=cp.float32) def step(self, gravity): """ Führt einen Simulationsschritt durch, einschließlich der Anwendung von Gravitation. """ # Gravitation hinzufügen (nur zur v-Komponente) self.v_prev += gravity * DT # Dichte add_source(self.dens, self.dens_prev) diffuse(0, self.dens, self.dens_prev, DIFF) advect(0, self.dens, self.dens_prev, self.u, self.v) # Geschwindigkeit add_source(self.u, self.u_prev) add_source(self.v, self.v_prev) diffuse(1, self.u, self.u_prev, VISC) diffuse(2, self.v, self.v_prev, VISC) project(self.u, self.v, self.u_prev, self.v_prev) advect(1, self.u, self.u_prev, self.u, self.v) advect(2, self.v, self.v_prev, self.u, self.v) project(self.u, self.v, self.u_prev, self.v_prev) # Quellen zurücksetzen self.dens_prev.fill(0) self.u_prev.fill(0) self.v_prev.fill(0) # ============================== # HAUPTPROGRAMM # ============================== def main(): pygame.init() screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("GPU Fluid Simulation mit Gravitation") clock = pygame.time.Clock() fluid = Fluid() # Zentrum für kontinuierliche Dichtequelle center = (N//2, N//2) running = True while running: clock.tick(60) # 60 FPS anvisiert for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Mausinteraktion: Linke Maustaste fügt Dichte und zufällige Velocity hinzu if pygame.mouse.get_pressed()[0]: mx, my = pygame.mouse.get_pos() i = int(mx // ZOOM) j = int(my // ZOOM) if 1 <= i < N-1 and 1 <= j < N-1: fluid.dens_prev[j, i] += 100 fluid.u_prev[j, i] += cp.random.uniform(-2, 2) fluid.v_prev[j, i] += cp.random.uniform(-2, 2) # Immer ein klein wenig Dichte in der Mitte fluid.dens_prev[center[1], center[0]] += 10 # Hinweis: [j, i] entspricht [y, x] # Ein Schritt Fluid-Simulation mit Gravitation fluid.step(GRAVITY) # Dichte von GPU holen für Pygame dens_host = cp.asnumpy(fluid.dens) # In Graustufen umwandeln und skalieren dens_normalized = np.clip(dens_host * 255, 0, 255).astype(np.uint8) color_img = np.stack((dens_normalized, dens_normalized, dens_normalized), axis=-1) # Skalieren des Bildes auf die Bildschirmgröße color_img_scaled = np.repeat(np.repeat(color_img, ZOOM, axis=0), ZOOM, axis=1) # Erstellen eines Pygame-Surfaces aus dem Array surface = pygame.surfarray.make_surface(color_img_scaled.swapaxes(0, 1)) # Blitten der Surface auf den Bildschirm screen.blit(surface, (0, 0)) pygame.display.flip() pygame.quit() sys.exit() if __name__ == "__main__": main()