A&A Title Image Startseite | Programme: Wellenoptik | Algorithmen, Lektionen, Analysen | Unsere Vorläufer | Kontakt, Datenschutz

VdS-Journal 92: Schwerpunktthema, Simulation von Sternhaufen

Das Programm simuliert die dynamische Entwicklung eines Sternhaufens und ermittelt zyklisch dessen Kenngrößen


# nbodyCluster (ohne Doppelsternbehandlung): Analyse der Entwicklung von Sternhaufen
from math import *
from random import *
from plot import *
from sys import *

G = 1     # Gravitationskonstante ist 1 gesetzt (einheitenfreies rechnen)
N = 100   # Anzahl der Körper
Q = 1     # Anfangsradius R0
M = 1     # Gesamtmasse
o = 1000  # Anzahl Ausgabewerte gesamt.  
J = 0.03  # maximaler Ruck, J<0.03
g = 1     # Genauigkeitssteuerung an/aus
S = 30    # t* bis Simulationsende
l = 3.0   # Limit für Entweicher. 3 ist in Ordnung
L = l
xNext = 3 # der drittnächste Stern für die Dichteberechnung

E=100    # Abbruch bei xx % Entweichern
rP = 0.05 / (N ** 0.333) # Plummer-Radius, spart Rechenzeit und erschwert das Entstehen von Doppelsternen
d = 0.1 * sqrt(1/N)      # deltaT vor der ersten Steuerung. (#8-22)
grafik=1                 # nur Text oder auch Grafik? 0/1

r = [[0.0 for i in range(3)] for j in range(N)]
v = [[0.0 for i in range(3)] for j in range(N)]
w = [[0.0 for i in range(3)] for j in range(N)] # die Hilfsgeschwindigkeit v05
a = [[0 for i in range(3)] for j in range(N)]
b = [[0.0 for i in range(3)] for j in range(N)]
m = [0.0 for i in range(N)]
R = [0.0 for i in range(N)]



def sq(x):
    return x * x


def baryzentrischR(N): 
    x = 0
    y = 0
    z = 0
    LL = 0
    for i in range(N): # Schwerpunkt berechnen ...
        x+=m[i] * r[i][0]
        y+=m[i] * r[i][1]
        z+=m[i] * r[i][2]
        LL+=m[i]
    for i in range(N): # ...  korrigieren
        r[i][0]-=x / LL
        r[i][1]-=y / LL
        r[i][2]-=z / LL

def baryzentrischV(N): 
    x = 0
    y = 0
    z = 0
    for i in range(N): # mittl.  Geschwindigkeit ber...
        x+=v[i][0]
        y+=v[i][1]
        z+=v[i][2]
    for i in range(N): # ...  korrigieren
        v[i][0]-=x / N
        v[i][1]-=y / N
        v[i][2]-=z / N



def wPot(N): 
    P = 0 
    for i in range(N):
        for j in range(i + 1,N):
            R = 0
            for k in range(3):
                R = R + sq(r[i][k] - r[j][k])
            R = sqrt(R)
            P = P + m[i] * m[j] / R
    P = G * P
    return (P)

def wKin(N): 
    K = 0
    for i in range(N):
        V = sq(v[i][0]) + sq(v[i][1]) + sq(v[i][2])
        K+=m[i] / 2 * V
    return K

def beschleunigung(): 
    global d
    global D
    global c
    for p in range(N):    # alle Beschleunigungen löschen / kopieren
        for k in range(3):
            b[p][k] = a[p][k]
            a[p][k] = 0
    B = 0
    for p in range(N): # alle Körper
        for q in range(p + 1, N): # gegen alle anderen Körper
            R = sqrt(sq(r[p][0] - r[q][0]) + sq(r[p][1] - r[q][1]) + sq(r[p][2] - r[q][2]))
            # E = R * R * R # E ist die umgekehrte 3, also R3
            E = R * (R * R + rP * rP) # mit Plummer-Radius
            for k in range(3):
                A = G * (r[q][k] - r[p][k]) / E
                a[p][k]+=m[q] * A
                a[q][k]-=m[p] * A
        # beide Beschleunigungen existieren jetzt, größten Ruck ber.
        j = sqrt(sq(a[p][0] - b[p][0]) + sq(a[p][1] - b[p][1]) + sq(a[p][2] - b[p][2]))
        A = sqrt(sq(a[p][0]) + sq(a[p][1]) + sq(a[p][2]))
        if (j / A > B): # größten Ruck in B aufheben
            B = j / A
    # neuen Zeitschritt ausrechnen
    D = d
    if B > 0:
        D = J / B * d # Vorschlag für den neuen Zeitschritt

# nach Hermite-Verfahren
def  startwerte():
    # seed(31) # seed / random nur für Tests
    A = 6.0 / 5.0 # für Radius
    for i in range(N):
        R = A * ( random()**0.33333333)
        cos_theta = 2.0 * random() - 1.0
        sin_theta = sqrt(1 - (cos_theta*cos_theta))
        phi = 2 * pi * random()
        r[i][0] = R * sin_theta * cos(phi)
        r[i][1] = R * sin_theta * sin(phi)
        r[i][2] = R * cos_theta
    A = 5.0 / 6.0 # für Geschwindigkeit
    for i in range(N):
        V = A * (random()**0.333333333)
        cos_theta = 2.0 * random() - 1
        sin_theta = sqrt(1 - (cos_theta*cos_theta))
        phi = 2 * pi * random()
        v[i][0] = V * sin_theta * cos(phi)
        v[i][1] = V * sin_theta * sin(phi)
        v[i][2] = V * cos_theta
    for i in range(N):
        m[i] = 1.0 / N
    baryzentrischR(N)
    # virialisieren
    K = wKin(N)
    P = wPot(N)
    c = sqrt(2 * K / P)
    for i in range(N):
        for k in range(3):
            v[i][k]/=c # /=c: virialisiert 
            v[i][k] = 0 # =0:kalter Kollaps. Auskommentieren für virialisierten Start
    baryzentrischV(N)
    beschleunigung() # die erste Beschleunigung gleich hier
    return c


def leapfrog1(): 
    # eine Hilfsgeschwindigkeit in Intervallmitte wird benötigt
    for i in range(N):
        for k in range(3):
            w[i][k] = v[i][k] + a[i][k] * d / 2  # v05 = v + a * dt / 2
    # der neue Ort nimmt die mittlere Geschwindigkeit 
    # Damit ist es ein quadratisches Verfahren
    for i in range(N):
        for k in range(3):
            r[i][k] = r[i][k] + w[i][k] * d   # s = s + v05 * dt
    # die Geschwindigkeit muss korrigiert werden
    beschleunigung()    
    for i in range(N):
        for k in range(3):
            v[i][k] = w[i][k] + a[i][k] * d / 2  # v=v05+a*dt/2

def leapfrog():
    global t
    global d
    while t <= T:
        leapfrog1()
        t = t + d
        if (g > 0.1): # neuer Zeitschritt nur, falls das Genauigkeitsflag gesetzt ist
            d = D


def abstand(i, j):
    return sqrt(sq(r[i][0] - r[j][0]) + sq(r[i][1] - r[j][1]) + sq(r[i][2] - r[j][2]))


def volumen(r):
    return 1.333 * pi * r * r * r


def rC(N):
    Next=[0.0 for i in range(xNext)]
    rho = [[0.0 for i in range(2)] for j in range(N)]
    for i in range(N): # Dichte um jeden Stern    
        for j in range (xNext):
            Next[j] = 7e77
        for j in range (N): # gegen jeden anderen Stern
            dist = abstand(i, j)
            for k in range (xNext):
                if (i != j) and (dist < Next[k]):
                    for l in range(xNext - 1, k, -1):
                        Next[l] = Next[l - 1] # ein Element Platz schaffen
                    Next[k] = dist
                    break
        rho[i][0] = R[i]
        rho[i][1] = (xNext - 1) / volumen(Next[xNext-1])
    # zentrale Dichte: die inneren 2%. das ist zweckmäßig bis N~500. Darüber: verkleinern.
    anzC = int(0.02 * N + 0.5)
    if (anzC < 2):
       anzC = 2
    rhoC = 0.0
    for i in range(anzC):
        rhoC += rho[i][1]
    rhoC /= anzC
    # Radius der viertel zentralen Dichte zurückmelden. Wird durch Doppelsterne verfälscht, hier nicht behoben.
    radC=0
    anzC=0
    for i in range(N):
        Rho = rho[i][1]
        if (Rho < 0.25 * rhoC):
            radC = rho[i][0]
            anzC = i + 1
            break
    return radC, anzC, rhoC

# Doppelsterne: näher als 0.03, hängt nicht von N ab!
def DS(N):
    dslimit = 0.03 
    ds = 0
    for i in range(N): # Dichte jeden Stern prüfen
        for j in range(i+1,N): # gegen jeden anderen Stern
            dist = abstand(i, j)
            if (dist < dslimit):
               ds=ds+1
    return ds/2 # jeder Stern wurde 2x berücksichtigt

def rH(n, t):
    return R[int((N - n) / 2)] # nur valide für identische Massen aller Körper. Die Entweicher abziehen.

def ausgabe(): 
    # Grafik
    global L
    n=0 # Anzahl Entweicher
    for i in range(N):
        if (grafik>0): Plot(r[i][0] / Q, r[i][1] / Q, 4, "black")
        R[i] = sqrt(sq(r[i][0]) + sq(r[i][1]) + sq(r[i][2]))
        if (R[i]>L): # L: Limit für Entweicher
           n=n+1
    R.sort()
    radC, anzC, rhoC=rC(N)
    radH=rH(n, t)
    L = l * radH # Grenze für Entweicher aktualisieren  
    K = wKin(N)
    P = wPot(N)
    ds=DS(N)
    #     1  2     3  4    5   6    7     8   9     
    #     t* N     rH wKin wPot rC  nC   dbl  pC
    print(t, N-n, radH, K, P, radC, anzC, ds, rhoC)
    abbruch=0
    if (n>(E/100.0)*N-0.5):
        abbruch=1
    return abbruch # Abbruch durch Entweicher


# Hauptprogramm
c = startwerte() 

# Koordinatensystem einrichten
#                       x0 x1 xScale y0 y1 yScale
if (grafik>0): initKoor(-3 ,3 ,0.5, -2.5,2.5,0.5) 
s = S / o # Zeitschritt für Mehrfach-Leapfrog
t = 0
clrgraf=0
while t < S:
    T = t + s # Tend für leapfrog
    if (T > S): T = S
    clrgraf=clrgraf+s
    if (clrgraf>1):
        if (grafik>0): initKoor(-3 ,3 ,0.5, -2.5,2.5,0.5) 
        clrgraf=0
    abbruch=ausgabe()
    if (abbruch>0):
        t=S+1
    leapfrog()
    if (grafik>0): update() 
if (grafik>0): name = input("Enter!") # wg. Aufruf von Konsole und grep/plot

'''
Variablenliste
a Hilfsfeld                  A  Beschleunigungen
b Hilfsfeld                  B  größter Ruck
c Virialisierungsfaktor      C  %
d deltaT                     D Vorschlag für deltaT
e anzDS                         E R3
f                            F
g Flag Genauigkeitsstrg.     G  Gravkonstante=1
h Anz. Relaxationsztn bis End
i Schleife
j Ruck                       J erlaubte Grenze für Ruck
k Schleife                   K  wKin
l limit Entweicher           L korrigiertes Limit für Entweicher
m Masse                      M  Gesamtmasse
n %                          N  Anzahl Körper
o Anzahl Ausgaben
p Schleife                   P  wPot
q Schleife                   Q  R0
r Ortsvektoren               R  Abstand vom Origo
s Teilzeit(Ausgabe)          S  Endzeit
t simulierte Zeit            T  Endzeit für die LF-Funktion
u 
v Geschwindigkeit(Feld)      V  v²
w %                          W  Relaxationszeit
x
y
z % Zufallszahl
'''