Multi-Core-Processing

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
CaptainNeemo
User
Beiträge: 14
Registriert: Sonntag 6. Dezember 2020, 20:50

Hallo liebes Forum,

ich habe ein Programm entworfen, welches die Turing-Patterns simuliert. Dieses berechnet über eine gewisse Anzahl von Schritten, die Reaktions-Diffusionsgleichungen neu. Da das ganze bei Simulationen mit 200.000 Schritten eine sehr langwierige Sache ist, wollte ich nachfragen ob es möglich ist das Programm auf mehreren Cpu-Kernen laufen zu lassen und wie ich so etwas implementieren könnte. (Nutze aktuell Jupyter-Notebook)

Vielen Dank schon mal für jegliche Hilfe
liebe Grüße
CaptainNeemo
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Für Geschwindigkeit benutzt man numpy, und wenn das aus irgendeinem Grund nicht gut funktioniert, dann numba.
Mit Code könnte man Dir viel konkreter helfen.
CaptainNeemo
User
Beiträge: 14
Registriert: Sonntag 6. Dezember 2020, 20:50

@sirius3: Hier der Code

Code: Alles auswählen

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import animation
%matplotlib inline

class basement:
    """
    Das Basissystem
    """
    def start_fig(self):                                                                            #Erstellen der Figur und der Achse
        fig, ax = plt.subplots()
        return fig, ax
        
    def final_figure(self, filename, timesteps):                                                    #Kreieren der Figur und speichern dieser
        self.initialise()
        fig, ax = self.start_fig()
        for i in range(timesteps):
            self.next()
        self.create(ax)
        fig.savefig(filename, dpi = 300)
        plt.close()


    def create_gif(self, filename, timesteps=20):                                                   #Gif kreieren
        self.initialise()
        fig, ax = self.start_fig()
        def step(t):
            self.next()
            self.create(ax)
        simu = animation.FuncAnimation(fig, step, frames=np.arange(timesteps), interval=20)
        simu.save(filename=filename, dpi=300, fps=20)
        plt.close()

def start(shape):                                               #Grundumgebung schaffen
    return (
        np.random.normal(loc=0, scale=1.0, size=shape),
        np.random.normal(loc=0, scale=1.0, size=shape)
        )

def derivate(a, dx):
    return (
        - 4 * a
        + np.roll(a,1,axis=0) 
        + np.roll(a,-1,axis=0)
        + np.roll(a,+1,axis=1)
        + np.roll(a,-1,axis=1)
        ) / (dx ** 2)  

def Ra(a,b): return a - a ** 3 - b + alpha                      #Berechnen der Reaktion der beiden Stoffe 
def Rb(a,b): return (a - b) * beta

    #Paramter zum Einstellen der Png und der eigentlichen Funktionsparameter
Da, Db, alpha, beta = 1, 100, 0.01, 10
width = 200
dx = 1
dt = 0.001


class Main(basement):
    def __init__(self, Da, Db, Ra, Rb,                              #Initialisieren der Variablen
                 width=1000, height=1000,
                 dx=1, dt=0.1, steps=1, initialiser=start):
        
        self.Da = Da
        self.Db = Db
        self.Ra = Ra
        self.Rb = Rb
        self.initialiser = initialiser
        self.width = width
        self.height = height
        self.shape = (width, height)
        self.dx = dx
        self.dt = dt
        self.steps = steps
        
    def initialise(self):                                          #Initialisieren der Form
        self.t = 0
        self.a, self.b = self.initialiser(self.shape)
    	
    def next(self):                                                 #Ein weiterer Zeitschritt dt und nachladen aufrufen
        for i in range(self.steps):
            self.t += self.dt
            self.reload()

    def reload(self):                                               #Neuladen und berechnen der Diffusionsgleichungen 
        der_a = derivate(self.a, self.dx)
        d_A = self.dt * (self.Da * der_a + Ra(self.a,self.b))
        self.a += d_A

        der_b = derivate(self.b, self.dx)
        d_B = self.dt * (self.Db * der_b + Rb(self.a,self.b))
        self.b += d_B
        
    def create(self, ax):                                             #Löschen des vorangegangenen Plots, neue Zeit als Legende hinzufügen und Plot zeigen
        ax[0].clear()
        ax[0].imshow(self.a, cmap = "coolwarm")
        ax[0].set_title("A, t = {0:.1f}".format(self.t))
        ax[0].grid(b=False)

        ax[1].clear()
        ax[1].imshow(self.b, cmap = "Spectral")
        ax[1].set_title("B, t = {0:.1f}".format(self.t))
        ax[1].grid(b=False)
        
    def start_fig(self):                                               #Figur entwickeln im Basement System
        fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(12,6))
        return fig, ax

#Aufrufen der Main Funktion mit den entsprechenden Variablen und der Möglichkeit die Zeitschritte zu wählen
Main(                   
    Da, Db, Ra, Rb, 
    width=width, height=width, 
    dx=dx, dt=dt, steps=100
).final_figure("Turing-Muster2.pdf", timesteps=200)
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

@CaptainNeemo: Das ist auf den ersten Blick kein Problem, das embarrassingly parallel ist, da muss man beim Parallelisieren also zumindest mal nachdenken, wenn es überhaupt möglich ist.

Erster Schritt sollte immer sein, dass man mit Messen anfängt. Wo ist es langsam, kann ich an den Stellen was machen? Gibt es vielleicht bessere Algorithmen? Es gibt dafür `cProfile` in der Standardbibliothek, und das kann man dann mit verschiedenen Werkzeugen visualisieren, zum Beispiel `gprof2dot` oder einem der vielen Flamegraph-Tools.

Ich habe den Code nur überflogen, aber du hast da ein `np.roll` in der inneren Schleife. Das sieht so aus, als wenn es das Array komplett neu anlegen müsste. Das bedeutet also, dass du mit jedem `derivate`-Aufruf 10 Mal ein Array mit einer Million Elementen erzeugst. Da würde ich also (nach Gefühl) ansetzen.
Antworten