Dosiero:Black Hole Shadow.gif

Paĝenhavo ne ekzistas en aliaj lingvoj.
El Vikipedio, la libera enciklopedio

Bildo en pli alta difino((1 280 × 720 rastrumeroj, grandeco de dosiero: 3,09 MB, MIME-tipo: image/gif), ripeta GIF, 108 ĉeloj, 6,5 s)

Jen dosiero de la Wikimedia-Komunejo. La priskribo en ties priskriba paĝo estas montrata suben.
La Komunejo estas dosieruja retejo de libere licencitaj dosieroj.

Resumo

Priskribo
English: A plane wave of light passes by a static black hole and some of the light-rays are absorbed. The ones absorbed are those that have an impact parameter less than the radius . By the reversibility of light, this means that looking at this sphere is equivalent to looking at the surface of the event horizon. This lensing effect causes the apparent size of the black hole to be bigger, by a factor of about . The photon sphere is also depicted, as the region where orbiting light-rays going in are inevitably absorbed, and orbiting rays going out can escape.
Dato
Fonto Propra verko
Aŭtoro Hugo Spinelli
Source code
InfoField
Processing.py 3 code
import math

WIDTH, HEIGHT = 1920*2//3, 1080*2//3

fps = 60
c = 400.0
GM = 50*c**2
n_balls = 20000//1
too_close_factor = 1.00001
line_width = 100.0/n_balls #1.5
steps_per_frame = 1
refresh_dt = 0.05

save_frames = True
filepath = 'frames\\'

hide_circles = True
hide_absorbed = True
absorbed_color = color(25,0,55,1000000/n_balls)
light_color = color(255,255,0,1000000/n_balls) #color(255,230,0,10)
fill_color = color(0) #color(0) or light_color
background_color = color(100)

p0 = (WIDTH/2.0, HEIGHT/2.0)
x_sep = 0.0

rs = 2*GM/c**2

def norm(v):
    return sqrt(v[0]**2 + v[1]**2)
def add(v1, v2):
    return (v1[0]+v2[0], v1[1]+v2[1])
def subtract(v1, v2):
    return (v1[0]-v2[0], v1[1]-v2[1])
def distance(v1, v2):
    return norm(subtract(v1, v2))
def inner(v1, v2):
    return v1[0]*v2[0] + v1[1]*v2[1]
def product(v, a):
    return (a*v[0], a*v[1])
def proj(v1, v2):
    return product(v2, inner(v1,v2)/(norm(v2)**2))
def unitary(v):
    return product(v, 1.0/norm(v))
def angle(v1, v2):
    return math.atan2(v1[0]*v2[1]-v1[1]*v2[0],
                  v1[0]*v2[0]+v1[1]*v2[1])
def rotate(v, theta):
    s, c = math.sin(theta), math.cos(theta)
    x, y = v[0], v[1]
    return (c*x-s*y, s*x+c*y)
    
def sign(x):
    if x<0:
        return -1
    if x>0:
        return 1
    return 0

def vround(v):
    return (int(round(v[0])), int(round(v[1])))

def wrapToPi(x):
    if x > math.pi:
        return x-2*math.pi
    if x < -math.pi:
        return x+2*math.pi
    return x

class Ball:
    def __init__(self, p, v, radius):
        self.p = p
        self.v = v
        self.phi_step = 0.001
        self.vu = self.get_vu(0.001)
        
        self.radius = max(radius, 0.0)
        
        self.path = [self.p, self.p]

        self.fill_color = fill_color
        self.line_color = light_color

        self.inactive = False
        self.absorbed = False
        
        self.ellapsed_time = 0

    def draw_circle(self):
        if hide_circles or self.inactive:
            return
        fill(self.fill_color)
        ellipse(self.p[0], self.p[1], 2*self.radius, 2*self.radius)
        
    def get_phi(self, p):
        return angle((1.0,0), subtract(p, p0))
        
    def get_vu(self, dt):
        dp = product(self.v, dt)
        p_next = add(self.p, dp)
        
        du = 1/distance(p_next, p0) - 1/distance(self.p, p0)
        dphi = self.get_phi(p_next) - self.get_phi(self.p)
        return du/dphi
        
    def update(self, dt):
        if self.inactive:
            return
        
        self.ellapsed_time += dt
        
        direction = sign(angle(subtract(self.p, p0), self.v))
        
        dphi = direction*self.phi_step
        u = 1/distance(self.p, p0)
        phi = self.get_phi(self.p)
        u_next = u + self.vu*dphi
        phi_next = phi + dphi
        p_next = (cos(phi_next)/u_next, sin(phi_next)/u_next)
        p_next = add(p_next, p0)
        
        self.v = subtract(p_next, self.p)
        dp = self.get_dp(dt)
        p_prev = self.p
        self.p = add(self.p, dp)
        self.v = product(dp, 1.0/dt)
        
        dphi = wrapToPi(self.get_phi(self.p) - self.get_phi(p_prev))
        dvu = (1.5*rs*u**2-u)*dphi
        self.vu += dvu

        if (self.ellapsed_time + dt)//refresh_dt > self.ellapsed_time//refresh_dt:
            self.path.append(self.p)
        else:
            self.path[-1] = self.p
        
    def get_dp(self, dt):
        # v only gives the local orbit direction
        r_vec = subtract(self.p, p0)
        x, y = r_vec
        r = norm(r_vec)
        phi = self.get_phi(self.p)
        
        k = self.v[1]/self.v[0]
        b = 1-rs/r
        
        #https://bit.ly/2WaCIcB
        k1 = sqrt(  -1/( 2*(b-1)*k*x*y - (b*x**2+y**2)*k**2 - b*y**2 - x**2 )  )
        
        dx = sign(self.v[0])*abs(b*c*dt*r*k1)
        dy = sign(self.v[1])*abs(k*dx)
        
        return (dx, dy)
        
    def is_too_close(self):
        if norm(subtract(self.p, p0)) < too_close_factor*(2*GM/c**2):
            return True
        
    def set_too_close(self):
        self.inactive = True
        self.absorbed = True
        self.line_color = absorbed_color

    def is_out_of_bounds(self):
        dx = 4*abs(self.v[0])*refresh_dt
        dy = 4*abs(self.v[1])*refresh_dt
        if (self.p[0]<-dx       and self.v[0]<-1) or \
           (self.p[0]>WIDTH+dx  and self.v[0]> 1) or \
           (self.p[1]<-dy       and self.v[1]<-1) or \
           (self.p[1]>HEIGHT+dy and self.v[1]> 1):
            return True
        return False
    
    def set_out_of_bounds(self):
        self.inactive = True

balls = []
M = 2.0
radius = M*HEIGHT/(2.0*n_balls)
for ky in range(n_balls/2):
    x = WIDTH + radius + x_sep
    y = radius + ky*2*radius
    balls += [
        Ball((x, HEIGHT/2.0+y), (-c, 0), radius),
        Ball((x, HEIGHT/2.0-y), (-c, 0), radius)
    ]
ordered_balls = list(range(n_balls))
for k in range(n_balls/2):
    ordered_balls[n_balls/2+k] = balls[2*k]
    ordered_balls[n_balls/2-k-1] = balls[2*k+1]
#balls.append(Ball((0, 1.51*2*GM/c**2), (-c, 0), radius))
#balls.append(Ball((0, 1.49*2*GM/c**2), (-c, 0), radius))

def draw_wave_front(inactive_balls):
    active_ratio = (n_balls-inactive_balls)/(1.0*n_balls)
    cuttoff = 0.1
    if active_ratio < cuttoff:
        return
    
    stroke(color(255,255,0,255.0*(active_ratio-cuttoff)))
    strokeWeight(1.5)
    noFill()
    
    beginShape()
    started = False
    for k in range(n_balls/2+1):
        ball = ordered_balls[n_balls/2-k]
        p = ball.p
        if ball.absorbed:
            continue
        if distance(p, p0) < rs + 0.5:
            continue
        if not started:
            started = True
            x, y = p
            curveVertex(x, y)
            curveVertex(x, y)
            continue
        if distance((x,y), p) < 10:
            continue
        x, y = p
        curveVertex(x, y)
    x, y = p
    curveVertex(x, y)
    curveVertex(x, y)
    endShape()
    
    beginShape()
    started = False
    for k in range(1,n_balls/2+1):
        ball = ordered_balls[k-n_balls/2-1]
        p = ball.p
        if ball.absorbed:
            continue
        if distance(p, p0) < rs + 0.5:
            continue
        if not started:
            started = True
            x, y = p
            curveVertex(x, y)
            curveVertex(x, y)
            continue
        if distance((x,y), p) < 10:
            continue
        x, y = p
        curveVertex(x, y)
    x, y = p
    curveVertex(x, y)
    curveVertex(x, y)
    endShape()
        
    stroke(0)
    strokeWeight(1)

def draw_solid_path():
    def draw_solid_path_for(ball1, ball2):
        if ball1.absorbed or ball2.absorbed:
            return
        
        def get_xy(ball, k2):
            if k2 < len(ball.path):
                return ball.path[k2]
            return ball.path[-1]
        
        d1 = distance(ball1.p, p0)
        d2 = distance(ball2.p, p0)
        closeness = 1.0
        cuttoff = 1.5
        if d1 < cuttoff*rs or d2 < cuttoff*rs:
            x = min(d1, d2)/rs
            closeness = sqrt((x-1.0)/(cuttoff-1.0))

        noStroke()
        for k2 in range(min(len(ball1.path), len(ball2.path)) -1):
            x1, y1 = get_xy(ball1, k2)
            x2, y2 = get_xy(ball1, k2+1)
            x3, y3 = get_xy(ball2, k2+1)
            x4, y4 = get_xy(ball2, k2)
            d = distance((x2, y2), (x3, y3))
            a = closeness*(255.0*ball1.radius)/d
            a = max(0,min(a,255))
            fill(color(255,255,0,a))
            quad(x1, y1, x2, y2, x3, y3, x4, y4)
        stroke(0)

    for k in range(n_balls/2):
        ball1 = ordered_balls[n_balls/2-k]
        ball2 = ordered_balls[n_balls/2-k-1]
        draw_solid_path_for(ball1, ball2)
    for k in range(1,n_balls/2):
        ball1 = ordered_balls[k-n_balls/2-1]
        ball2 = ordered_balls[k-n_balls/2]
        draw_solid_path_for(ball1, ball2)

img_shadow = None
img_photon_sphere = None
img_event_horizon = None
def setup():
    global img_shadow, img_photon_sphere, img_event_horizon
    size(WIDTH, HEIGHT)
    frameRate(fps)
    smooth(8)
    img_shadow = loadImage('Shadow.png')
    img_photon_sphere = loadImage('Photon Sphere.png')
    img_event_horizon = loadImage('Event Horizon.png')
    s = 0.3
    img_shadow.resize(int(round(s*img_shadow.width)), 0)
    img_photon_sphere.resize(int(round(s*img_photon_sphere.width)), 0)
    img_event_horizon.resize(int(round(s*img_event_horizon.width)), 0)

frame = 1
post_frame = 0
finished = False
def draw():
    global frame, post_frame, finished
    if finished:
        return
    background(background_color)
    
    dt = 1.0/fps

    inactive_balls = 0
    for ball in balls:
        if ball.inactive:
            inactive_balls += 1
            continue
        if ball.is_out_of_bounds():
            ball.set_out_of_bounds()
            continue
        if ball.is_too_close():
            ball.set_too_close()
            continue

    if balls[0].p[0] > WIDTH + balls[0].radius:
        print balls[0].p[0] - WIDTH
        fill(color(0, 0, 0))
        ellipse(p0[0], p0[1], 2*rs, 2*rs)
        
        for ball in balls:
            for k in range(steps_per_frame):
                ball.update(dt/steps_per_frame)
        return

    for ball in balls:
        ball.draw_circle()
        
    draw_wave_front(inactive_balls)
    draw_solid_path()
      
    fill(color(0, 0, 0))
    ellipse(p0[0], p0[1], 2*rs, 2*rs)
    
    if inactive_balls > 0.96*n_balls:
        post_frame += 1
    if True:
        a = 255  # post_frame*255.0/30
        
        M = sqrt(27)/2
        noFill()
        strokeWeight(2.0)
        stroke(color(0, 0, 100, a))
        line(p0[0],p0[1]-M*rs, p0[0]+WIDTH-p0[0], p0[1]-M*rs)
        line(p0[0],p0[1]-M*rs+2*M*rs, p0[0]+WIDTH-p0[0], p0[1]-M*rs+2*M*rs)
        ellipse(p0[0], p0[1], M*2*rs, M*2*rs)
        stroke(color(255,255,0,a))
        ellipse(p0[0], p0[1], 1.5*2*rs, 1.5*2*rs)
        strokeWeight(1.0)
        
        font = createFont('Georgia', 32)
        textFont(font)
        
        fill(color(0, 0, 100, a))
        s = 'Shadow:'
        text(s, p0[0], p0[1]-M*rs-2-22)
        tint(255, a)
        image(img_shadow, p0[0]+textWidth(s)+10, p0[1]-M*rs-2-74)
        
        fill(color(255,255,0,a))
        s = 'Photon Sphere:'
        buff = 0.1
        text(s, p0[0], p0[1]-(1.5 + buff)*rs-2-5)
        tint(255, a)
        image(img_photon_sphere, p0[0]+textWidth(s)+10, p0[1]-(1.5 + buff)*rs-2-26)
        
        fill(color(0,0,0,a))
        s = 'Event Horizon:'
        text(s, p0[0], p0[1]-rs-2)
        tint(255, a)
        image(img_event_horizon, p0[0]+textWidth(s)+10, p0[1]-rs-2-67+22)
        
        fill(color(255,255,255,a))
        stroke(color(255,255,255,a))
        text('Singularity', p0[0],p0[1]-5)
        ellipse(p0[0], p0[1], 3, 3)
        
        stroke(0)

    for ball in balls:
        for k in range(steps_per_frame):
            ball.update(dt/steps_per_frame)
    
    if save_frames and post_frame<180:
        saveFrame(filepath + str(frame).zfill(4) + '.png')
    if post_frame<180:
        print frame
    if post_frame==180:
        print 'Done!'
        finished = True
    
    frame += 1

The PNG files can be generated, e.g., from https://latex.codecogs.com/eqneditor/editor.php. The LaTeX code for each file is:
LaTeX code
% "Shadow.png":
\color[RGB]{0,0,100}\boldsymbol{R = \frac{\sqrt{27}}{2}r_s \approx 2.6r_s}

% "Photon Sphere.png":
\color{yellow}\boldsymbol{r = 1.5r_s}

% "Event Horizon.png":
\boldsymbol{r_s = \frac{2GM}{c^2}}

Permesiloj:

Mi, la posedanto de la aŭtorrajto por ĉi tiu verko, ĉi-maniere publikigas tiun laŭ la jena permesilo:
Creative Commons CC-Zero Ĉi tiu dosiero estas disponebla laŭ la Krea Komunaĵo CC0 1.0 Universala Publikaĵiga Dediĉo.
La persono kiu asociis verkon kun ĉi tiu faro dediĉis la verkon kiel publikaĵon forlasante ĉiujn siajn rajtojn al la verko mondvaste sub aŭtorrajta leĝo inkluzivante ĉiujn rilatajn aŭ najbarajn rajtojn permesitajn de leĝo. Oni povas kopii, modifi, distribui kaj ludi la verkon, eĉ por komercaj kialoj, ĉiuj sen peto de permeso.

Titoloj

Donu unulinian priskribon de la enhavo de ĉi tiu dosiero
Animated diagram showing the event horizon, the photon sphere, and the shadow of a black hole.

Eroj prezentitaj en ĉi tiu dosiero

montras

image/gif

dd2fee407ab4d247c6ae30521389dea7a51caf09

3 237 835 bajto

6,479999999999987 sekundo

720 rastrumero

1 280 rastrumero

Dosierhistorio

Alklaku iun daton kaj horon por vidi kiel la dosiero tiam aspektis.

Dato/HoroBildetoGrandecojUzantoKomento
nun03:58, 27 sep. 2023Bildeto por versio ekde 03:58, 27 sep. 20231 280 × 720 (3,09 MB)Hugo SpinelliUploaded own work with UploadWizard

Neniu paĝo ligas al ĉi tiu dosiero.

Suma uzado de la dosiero

La jenaj aliaj vikioj utiligas ĉi tiun dosieron:

Metadatumoj