파일:Black Hole Shadow.gif

원본 파일(1,280 × 720 픽셀, 파일 크기: 3.09 MB, MIME 종류: image/gif, 반복됨, 108 프레임, 6.5 s)

파일 설명

설명
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.
날짜
출처 자작
저자 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}}

라이선스

나는 아래 작품의 저작권자로서, 이 저작물을 다음과 같은 라이선스로 배포합니다:
Creative Commons CC-Zero 이 파일은 크리에이티브 커먼즈 CC0 1.0 보편적 퍼블릭 도메인 귀속에 따라 이용할 수 있습니다.
저작물에 본 권리증서를 첨부한 자는 법률에서 허용하는 범위 내에서 저작인접권 및 관련된 모든 권리들을 포함하여 저작권법에 따라 전 세계적으로 해당 저작물에 대해 자신이 갖는 일체의 권리를 포기함으로써 저작물을 퍼블릭 도메인으로 양도하였습니다. 저작권자의 허락을 구하지 않아도 이 저작물을 상업적인 목적을 포함하여 모든 목적으로 복제, 수정·변경, 배포, 공연·실연할 수 있습니다.

설명

이 파일이 나타내는 바에 대한 한 줄 설명을 추가합니다
Animated diagram showing the event horizon, the photon sphere, and the shadow of a black hole.

dd2fee407ab4d247c6ae30521389dea7a51caf09

3,237,835 바이트

6.479999999999987

720 화소

1,280 화소

파일 역사

날짜/시간 링크를 클릭하면 해당 시간의 파일을 볼 수 있습니다.

날짜/시간섬네일크기사용자설명
현재2023년 9월 27일 (수) 12:582023년 9월 27일 (수) 12:58 판의 섬네일1,280 × 720 (3.09 MB)Hugo SpinelliUploaded own work with UploadWizard

다음 문서 1개가 이 파일을 사용하고 있습니다:

이 파일을 사용하고 있는 모든 위키의 문서 목록

다음 위키에서 이 파일을 사용하고 있습니다:

  • en.wikipedia.org에서 이 파일을 사용하고 있는 문서 목록
  • ja.wikipedia.org에서 이 파일을 사용하고 있는 문서 목록

메타데이터