├── .gitignore ├── Black hole simulation.pdf ├── README.md ├── __pycache__ └── black_hole.cpython-35.pyc ├── approch ├── milkyway D=100000.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=110.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=1161.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=12285.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=143.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=1509.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=15966.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=185.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=1961.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=20750.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=241.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=2549.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=26969.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=313.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=3313.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=35050.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=407.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=4306.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=45553.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=50.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=529.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=5596.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=59203.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=65.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=687.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=7273.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=76943.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=84.0 Rs=8.0 size=1980 offset_X=0.jpg ├── milkyway D=893.0 Rs=8.0 size=1980 offset_X=0.jpg └── milkyway D=9452.0 Rs=8.0 size=1980 offset_X=0.jpg ├── black hole moving.gif ├── black_hole.py ├── black_hole_moving2.gif ├── images ├── default.png └── milkyway.jpg ├── matrix ├── 50.0_8.0_1000_360_x.txt ├── 50.0_8.0_1000_360_y.txt ├── 50.0_8.0_2000_360_x.txt ├── 50.0_8.0_2000_360_y.txt ├── 50.0_8.0_3000_360_x.txt ├── 50.0_8.0_3000_360_y.txt ├── 50.0_8.0_360_360_x.txt └── 50.0_8.0_360_360_y.txt ├── milkyway D=50 Rs=8 size=6000.jpg ├── offsets ├── milkyway D=50 Rs=8 size=1980 offset_X=0.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1029.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1078.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1127.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1176.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1225.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1274.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1323.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1372.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1421.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=147.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1470.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1519.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1568.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1617.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1666.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1715.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1764.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1813.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1862.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=1911.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=196.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=245.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=294.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=343.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=392.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=441.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=49.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=490.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=539.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=588.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=637.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=686.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=735.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=784.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=833.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=882.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=931.jpg ├── milkyway D=50 Rs=8 size=1980 offset_X=98.jpg └── milkyway D=50 Rs=8 size=1980 offset_X=980.jpg ├── old results ├── Figure_2.png ├── Figure_6.png ├── black hole simulation - menu.py └── only_trajectories_with_euler.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/settings.json 2 | -------------------------------------------------------------------------------- /Black hole simulation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/Black hole simulation.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Black-hole-simulation-using-python 2 | 3 | Non-spinning black hole simulation based on photon geodesic equation. 4 | This program takes an equirectangular image and returns the image distorded by the black hole. 5 | Results can be saved in form of matrices to call them later. 6 | An offset system can be used to simulate a moving background by saving a series of simulations with different offset. 7 | 8 | An optional GUI can be used to control the BlackHole class. 9 | 10 | ## Animation 11 | 12 | An animation can be easily generated using the GUI. 13 | 14 | ### Example 15 | 16 | ![Black Hole spinning animation](black_hole_moving2.gif) -------------------------------------------------------------------------------- /__pycache__/black_hole.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/__pycache__/black_hole.cpython-35.pyc -------------------------------------------------------------------------------- /approch/milkyway D=100000.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=100000.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=110.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=110.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=1161.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=1161.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=12285.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=12285.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=143.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=143.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=1509.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=1509.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=15966.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=15966.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=185.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=185.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=1961.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=1961.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=20750.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=20750.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=241.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=241.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=2549.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=2549.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=26969.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=26969.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=313.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=313.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=3313.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=3313.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=35050.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=35050.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=407.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=407.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=4306.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=4306.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=45553.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=45553.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=50.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=50.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=529.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=529.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=5596.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=5596.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=59203.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=59203.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=65.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=65.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=687.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=687.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=7273.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=7273.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=76943.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=76943.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=84.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=84.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=893.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=893.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /approch/milkyway D=9452.0 Rs=8.0 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/approch/milkyway D=9452.0 Rs=8.0 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /black hole moving.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/black hole moving.gif -------------------------------------------------------------------------------- /black_hole.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Black hole simulation 4 | 5 | @author: Jonathan Peltier 6 | 7 | GitHub repository: 8 | https://github.com/Python-simulation/Black-hole-simulation-using-python/ 9 | 10 | BlackHole class solving photons trajectories closed to a static black hole. 11 | Render the perceived image deformation by a black hole. 12 | Object-oriented programming version. 13 | Numpy optimized version 30x faster. 14 | """ 15 | 16 | import sys 17 | import math # import known fonctions and constants 18 | import time # Used to check computation time 19 | import os.path # Used to search files on computer 20 | from tkinter import Tk, Frame 21 | from tkinter import Button, Label, Checkbutton, BooleanVar, StringVar, Spinbox 22 | from tkinter.filedialog import askopenfilename 23 | 24 | import matplotlib.pyplot as plt # Graphical module 25 | import matplotlib 26 | if matplotlib.get_backend() not in ("TKAgg", "Qt5Agg"): 27 | matplotlib.use("TKAgg", force=True) 28 | #from matplotlib.widgets import Slider # TODO: use it for offset GUI 29 | import numpy as np # Use for matrices and list 30 | from scipy.interpolate import interp1d # Use for interpolation 31 | from scipy.integrate import solve_ivp # Integrate ord diff eqs 32 | from scipy.constants import pi # 3.141592653589793 33 | from scipy.constants import c # Speed light vaccum = 299792458 m/s 34 | from scipy.constants import G # Newton constant = 6.67408e-11 m3/Kg/s2 35 | from scipy.constants import au # sun-earth distance m = 149597870691 m 36 | from PIL import Image # Use to open, modify and save images 37 | from PIL import ImageDraw 38 | 39 | M_sun = 1.98840987e+30 # solar mass in Kg taken from AstroPy 40 | 41 | 42 | class BlackHole: 43 | """Main class""" 44 | def __init__(self): 45 | """Main class""" 46 | self.init_var() 47 | plt.ion() 48 | 49 | try: 50 | abs_path = os.path.abspath(os.path.dirname(sys.argv[0])) 51 | folder = os.path.join(abs_path, 'images') 52 | img_name = os.path.join(folder, 'milkyway.jpg') 53 | self.open(img_name, size=self.axe_X) 54 | 55 | except FileNotFoundError: 56 | print("milkyway image not found, creating a default image") 57 | self.create_default_image(size=self.axe_X) 58 | 59 | def init_var(self): 60 | """Initialize most variables.""" 61 | # TODO: allow both M and Rs definition 62 | #M = 1.7342*10**22/M_sun # Black hole mass in solar mass (alternative, del M below) 63 | #Rs = 2*G*M_sun*M/c**2/Ds # Schwarzschild radius in Astronomical unit (ua) 64 | self.Rs = 8 # Schwarzschild radius in ua 65 | self.M = self.Rs * c**2 / 2 / G * au / M_sun # Black hole mass in solar masses (del if use solar mass) 66 | self.D = 50 # Distance from the black hole in ua 67 | self.axe_X = 360 # Image size over x 68 | self.FOV_img = 360 # The image FOV (it doesn't change the current image FOV !) 69 | 70 | self.kind = 'cubic' # Interpolation: linear for speed(less accurate), cubic for precision (slow) 71 | self.fixed_background = True 72 | self.display_trajectories = True 73 | self.display_interpolation = True 74 | self.display_blackhole = True 75 | # Note that openning matrices is slower than computing the blackhole, 76 | # but skip trajectories calculation than takes 1.5s -> better to open 77 | # matricies at low resolution but better to compute at high resolution 78 | self.use_matrix = True # Use matrices if exists # TODO: GUI option 79 | self.save_matrix = False # will save or overwrite matrices if exists 80 | 81 | self.zoom = 0 82 | self.offset_X = 0 83 | self.offset_X2 = 0 84 | self.out_graph = False 85 | #---------------------------------------------------------------------- 86 | self.img_matrix_x = None 87 | self.img_matrix_y = None 88 | self.img2 = None 89 | self.ax = None 90 | #---------------------------------------------------------------------- 91 | # if want matricies without images loaded 92 | # self.axe_Y = self.axe_X//2 93 | # self.img_res = self.axe_X/360 # =Pixels per degree along axis 94 | # self.img_res_Y = self.axe_Y/180 # =Pixels per degree along axis 95 | # self.FOV_img_Y = self.FOV_img//2 96 | # self.img_debut = None 97 | 98 | def create_default_image(self, size="default", pattern="grid"): 99 | """Create a default image if doesn't want to import one.""" 100 | if size == "default": 101 | size = self.axe_X 102 | 103 | abs_path = os.path.abspath(os.path.dirname(sys.argv[0])) 104 | folder = os.path.join(abs_path, 'images') 105 | self.img_name = os.path.join(folder, 'default.png') 106 | 107 | axe_X = 1000 # int(self.axe_X) 108 | axe_Y = 500 # self.axe_X//2 109 | 110 | if pattern == "noise": 111 | pixels = np.random.randint(0, 255, (axe_Y, axe_X, 3)) 112 | self.img_original = Image.fromarray(pixels.astype('uint8'), 'RGB') 113 | 114 | elif pattern in ("grid", "cercle", "rectangle"): 115 | self.img_original = Image.new('RGB', (axe_X, axe_Y), color=255) 116 | Drawer = ImageDraw.Draw(self.img_original) 117 | Drawer.rectangle((0, 0, axe_X/2, axe_Y/2), fill="yellow") 118 | Drawer.rectangle((0, axe_Y/2, axe_X/2, axe_Y), fill="green") 119 | Drawer.rectangle((axe_Y, 0, axe_X, axe_Y/2), fill="blue") 120 | Drawer.rectangle((axe_Y, axe_Y/2, axe_X, axe_Y), fill="red") 121 | 122 | nbr_rect = 40 123 | if pattern == "grid": 124 | for i in range(0, axe_X, axe_X//nbr_rect): 125 | Drawer.line((i, 0, i, axe_Y), fill="black", width=2) 126 | for i in range(0, axe_Y, axe_Y//(nbr_rect//2)): 127 | Drawer.line((0, i, axe_X, i), fill="black", width=2) 128 | else: 129 | for i in range(0, axe_X, axe_X//nbr_rect): 130 | 131 | if pattern == "cercle": 132 | Drawer.ellipse((i, i/2, axe_X-i, axe_Y-i/2), outline="black") 133 | elif pattern == "rectangle": 134 | Drawer.rectangle((i, i/2, axe_X-i, axe_Y-i/2), outline="black") 135 | 136 | else: 137 | raise ValueError("pattern parameter must be: grid, noise, cercle or rectangle") 138 | 139 | self.img_debut = self.img_resize(size) 140 | return self.img_debut 141 | 142 | def open(self, img_name, size="default"): 143 | """Open an equirectangular image. 144 | Can resize it with the size option. 145 | """ 146 | print("Openning %s" % img_name) 147 | self.img_original = Image.open(img_name, mode='r') 148 | self.img_name = img_name 149 | 150 | if size == "default": 151 | size = self.img_original.size[0] 152 | 153 | self.img_debut = self.img_resize(size) 154 | return self.img_debut 155 | 156 | def img_resize(self, axe_X): 157 | """Create img_debut at the desired size from the img_original.""" 158 | self.img_debut = self.img_original.convert("RGB") 159 | size_X, size_Y = self.img_debut.size 160 | size_factor = axe_X/size_X 161 | axe_X = int(axe_X) 162 | axe_Y = int(size_factor*size_Y) 163 | 164 | # even dimensions needed for image (error if not) 165 | if axe_X % 2 != 0: 166 | axe_X -= 1 167 | 168 | if axe_Y % 2 != 0: 169 | axe_Y -= 1 170 | 171 | self.img_debut = self.img_debut.resize((axe_X, axe_Y), Image.Resampling.LANCZOS) 172 | self.FOV_img_Y = self.FOV_img * axe_Y / axe_X 173 | 174 | if self.FOV_img_Y > 180: 175 | raise StopIteration("Can't have a FOV>180 in the Y-axis") 176 | 177 | print("size %sx%s pixels\n" % (axe_X, axe_Y)) 178 | self.img_res = axe_X/360 # =Pixels per degree along axis 179 | self.img_res_Y = axe_Y/180 # =Pixels per degree along axis 180 | 181 | self.axe_X, self.axe_Y = axe_X, axe_Y 182 | 183 | return self.img_debut 184 | 185 | def compute(self, Rs, D): 186 | """main method used to compute the black hole deformation and apply it 187 | on a image.""" 188 | self.Rs = Rs 189 | self.D = D 190 | self.M = (self.Rs * c**2 * au) / (2 * G * M_sun) 191 | print("M = %.1e M☉\t%.2e Kg" % (self.M, self.M*M_sun)) 192 | print("Rs = %s ua\t%.2e m" % (self.Rs, self.Rs*au)) 193 | print("D = %s ua\t%.2e m\n" % (self.D, self.D*au)) 194 | 195 | vrai_debut = time.process_time() 196 | 197 | if self.use_matrix and self.check_matrices(): 198 | img_matrix_x, img_matrix_y = self.open_matrices() 199 | plt.close('Trajectories interpolation') 200 | plt.close('Trajectories plan') 201 | 202 | else: 203 | seen_angle, deviated_angle = self.trajectories() 204 | 205 | self.interpolation = self.interpolate(seen_angle, deviated_angle) 206 | 207 | if self.display_interpolation is True: 208 | xmin = np.min(seen_angle) 209 | xmax = np.max(seen_angle) 210 | seen_angle_splin = np.linspace(xmin, xmax, 20001) 211 | deviated_angle_splin = self.interpolation(seen_angle_splin) 212 | plt.figure('Trajectories interpolation') 213 | plt.clf() 214 | plt.title("Light deviation interpolation", va='bottom') 215 | plt.xlabel('seen angle(°)') 216 | plt.ylabel('deviated angle(°)') 217 | plt.plot(seen_angle, deviated_angle, 'o') 218 | plt.plot(seen_angle_splin, deviated_angle_splin) 219 | plt.grid() 220 | #plt.savefig('interpolation.png', dpi=250, bbox_inches='tight') 221 | plt.draw() 222 | # 223 | print("last angle", seen_angle[-1]) 224 | print("trajectories time: %.1f" % (time.process_time()-vrai_debut)) 225 | 226 | img_matrix_x, img_matrix_y = self.create_matrices() 227 | 228 | if self.save_matrix is True: 229 | matrix_file_x, matrix_file_y = self.matrices_names() 230 | np.savetxt(matrix_file_x, img_matrix_x, fmt='%i') 231 | np.savetxt(matrix_file_y, img_matrix_y, fmt='%i') 232 | print("Saved matrices: \n\t%s\n\t%s" % (matrix_file_x, matrix_file_y)) 233 | 234 | self.img_matrix_x = img_matrix_x 235 | self.img_matrix_y = img_matrix_y 236 | 237 | self.img2 = self.img_pixels(self.img_debut) 238 | 239 | vrai_fin = time.process_time() 240 | print("\nglobal computing time: %.1f\n" % (vrai_fin-vrai_debut)) 241 | 242 | if self.display_blackhole: 243 | self.plot() 244 | 245 | def trajectories(self): 246 | """Compute several photons trajectories in order to interpolate the 247 | possibles trajectories and gain in execution time.""" 248 | # OPTIMIZE: take too much time due to too much solver call 249 | alpha_min = self.search_alpha_min() 250 | alpha_finder = self.FOV_img/2 251 | 252 | if self.display_trajectories is True: 253 | plt.figure('Trajectories plan') 254 | plt.clf() #clear the graph to avoir superposing data from the same set (can be deactivated if need to superpose) 255 | ax = plt.subplot(111, projection='polar') #warning if use python in ligne (!= graphical) graphs got superposed 256 | ax.set_title("light trajectories close to a black hole\n", va='bottom') 257 | ax.set_xlabel('R(UA)') 258 | plt.ylabel('phi(°)\n\n\n\n', rotation=0) 259 | ax.set_rlim((0, 4*self.D)) 260 | ax.set_rlabel_position(-90) 261 | 262 | seen_angle = np.array([]) 263 | deviated_angle = np.array([]) 264 | 265 | booli = False # avoid points from the first loop to exceed points from the second loop 266 | points = 40 # careful with this if using kind=linear 267 | 268 | for i in range(6): 269 | # print(alpha_finder) 270 | 271 | for alpha in np.linspace(alpha_finder, alpha_min, 272 | num=points, endpoint=booli): 273 | r, phi = self.solver(alpha) 274 | 275 | if r[-1] > 1.1*self.Rs: # if not capture by black hole 276 | seen_angle = np.append(seen_angle, 180-alpha) 277 | dev_angle = phi[-1] + math.asin(self.D/r[-1]*math.sin(phi[-1])) 278 | dev_angle = math.degrees(dev_angle) 279 | deviated_angle = np.append(deviated_angle, dev_angle) 280 | Ci = 'C'+str(i) 281 | 282 | if self.display_trajectories is True: 283 | ax.plot(phi, r, Ci) # plot one trajectory 284 | 285 | if self.kind == 'linear': 286 | alpha_finder = alpha_min + (alpha_finder - alpha_min)/(points/3 + 1) # start a more precise cycle from last point 287 | 288 | else: 289 | alpha_finder = alpha_min + (alpha_finder - alpha_min)/(points + 1) # start a more precise cycle from last point 290 | 291 | points = 10 # careful with this if using kind=linear 292 | 293 | if i == 4: 294 | booli = True # allow to display the last point 295 | 296 | if self.display_trajectories is True: 297 | # plt.savefig('trajectories.png', format='png', dpi=1000, bbox_inches='tight') 298 | plt.draw() 299 | 300 | return seen_angle, deviated_angle 301 | 302 | def search_alpha_min(self): 303 | """Return last angle at which the photon is kept by the black hole.""" 304 | alpha_min = 0 305 | 306 | for alpha in range(0, 180, 4): 307 | r = self.solver(alpha)[0] 308 | if r[-1] > 1.1*self.Rs: 309 | break 310 | 311 | if (alpha-4) > 0: 312 | alpha_min = alpha - 4 313 | # print("alpha_min :",alpha_min,"(-4)") 314 | i = 1 315 | 316 | while alpha_min == 0 or round(alpha_min*self.img_res) != round((alpha_min+i*10)*self.img_res): #increase precision 317 | 318 | for alpha in range(int(alpha_min/i), int(180/i), 1): 319 | alpha = alpha*i 320 | r = self.solver(alpha)[0] 321 | 322 | if r[-1] > 1.1*self.Rs: 323 | break 324 | 325 | if (alpha-i) > 0: 326 | alpha_min = alpha - i 327 | # print("alpha_min : ",alpha_min," (-",i,")",sep="") 328 | 329 | i = i/10 330 | i = 10*i 331 | alpha_min += i 332 | print("alpha_min: %s [%s, %s]" % (alpha_min, alpha_min-i, alpha_min)) 333 | 334 | return alpha_min 335 | 336 | def solver(self, alpha): 337 | """Solve the differential equation, in spherical coordinate, for a 338 | static black hole using solve_ivp. 339 | Allows to compute the photon trajectory giving its distance from the 340 | black hole and its initial angular speed.""" 341 | if alpha == 0: # skip divided by 0 error 342 | return [0], [0] # r and phi=0 343 | 344 | if alpha == 180: 345 | return [self.D], [0] # if angle= pi then, tan(pi)=0 so 1/tan=1/0 346 | 347 | # initial value for position and angular speed 348 | y0 = [1/self.D, 1/(self.D*math.tan(math.radians(alpha)))] 349 | sol = solve_ivp(fun=self._diff_eq, t_span=[0, 10*pi], y0=y0, method='Radau', events=[self._eventRs]) #, self._eventR])#,t_eval=np.linspace(0, t_max, 10000)) #dense_output=False 350 | 351 | if sol.t[-1] == 10*pi: 352 | raise StopIteration("solver error, alpha reached computation limit (loop number)") 353 | 354 | phi = np.array(sol.t) 355 | r = np.abs(1/sol.y[0, :]) # must use this because solver can't be stop before infinity because negative 356 | 357 | return r, phi 358 | 359 | def _diff_eq(self, phi, u): 360 | """Represent the differential equation : d²u(ɸ)/dɸ²=3/2*Rs*u²(ɸ)-u(ɸ) 361 | """ 362 | v0 = u[1] #correspond to u' 363 | v1 = 3/2*self.Rs*u[0]**2 - u[0] #correspond to u" 364 | return v0, v1 365 | 366 | def _eventRs(self, phi, u): 367 | """stop solver if radius < black hole radius""" 368 | with np.errstate(all='ignore'): 369 | return 1/u[0] - self.Rs 370 | _eventRs.terminal = True 371 | 372 | # def _eventR(self, phi, u): #not needed and don't work with ivp (without it we get an error message but irrelevant) 373 | # """stop solver if radius > sphere limit""" 374 | # R = 1e15 375 | # return (1/u[0]-math.sqrt(R**2-self.D**2*math.sin(phi)**2)+self.D*math.cos(phi)) 376 | # _eventR.terminal = True 377 | 378 | def interpolate(self, x_pivot, f_pivot): 379 | """Create interpolation data to reduce computation time.""" 380 | interpolation = interp1d(x_pivot, f_pivot, 381 | kind=self.kind, bounds_error=False) 382 | return interpolation 383 | 384 | def matrices_names(self, folder=None): 385 | """Return the matrices names.""" 386 | if folder is None: 387 | abs_path = os.path.abspath(os.path.dirname(sys.argv[0])) 388 | folder = os.path.join(abs_path, 'matrix') 389 | 390 | matrix_name_x = "%s_%s_%s_%s_x.txt" % ( 391 | self.D, self.Rs, self.axe_X, self.FOV_img) 392 | matrix_file_x = os.path.join(folder, matrix_name_x) 393 | 394 | matrix_name_y = "%s_%s_%s_%s_y.txt" % ( 395 | self.D, self.Rs, self.axe_X, self.FOV_img) 396 | matrix_file_y = os.path.join(folder, matrix_name_y) 397 | 398 | return matrix_file_x, matrix_file_y 399 | 400 | def check_matrices(self, folder=None): 401 | """Check if matricess exists.""" 402 | if folder is None: 403 | abs_path = os.path.abspath(os.path.dirname(sys.argv[0])) 404 | folder = os.path.join(abs_path, 'matrix') 405 | 406 | matrix_file_x, matrix_file_y = self.matrices_names(folder=folder) 407 | 408 | x_file = listdirectory(folder, matrix_file_x) 409 | y_file = listdirectory(folder, matrix_file_y) 410 | 411 | matrices_exist = x_file is True and y_file is True 412 | return matrices_exist 413 | 414 | def create_matrices(self): 415 | """Call find_position function and create matrices with pixels 416 | positions informations. 417 | Create two matrices with corresponding (x, y) -> (x2, y2). 418 | """ 419 | debut = time.process_time() 420 | 421 | x = np.arange(0, self.axe_X) 422 | y = np.arange(0, self.axe_Y) 423 | xv, yv = np.meshgrid(x, y) 424 | 425 | print("\nmatrix creation estimation time: %.1fs" % (1e-6*self.axe_X*self.axe_Y)) 426 | 427 | img_matrix_x, img_matrix_y = self.find_position(xv, yv) 428 | 429 | fin = time.process_time() 430 | print("matrix created in time: %.1f s" % (fin-debut)) 431 | return img_matrix_x, img_matrix_y 432 | 433 | def open_matrices(self): 434 | """Open the matricies corresponding to the chosen Rs, D, axe_X and FOV 435 | parameters.""" 436 | print("\nmatrix opening estimation: %.1f" % ( 437 | 1.65e-6*self.axe_X*self.axe_Y)) 438 | matrix_opening_debut = time.process_time() 439 | 440 | matrix_file_x, matrix_file_y = self.matrices_names() 441 | img_matrix_x = np.loadtxt(matrix_file_x, dtype=int) 442 | img_matrix_y = np.loadtxt(matrix_file_y, dtype=int) 443 | matrix_opening_fin = time.process_time() 444 | print("matrix opening time:", round(matrix_opening_fin-matrix_opening_debut, 1)) 445 | return img_matrix_x, img_matrix_y 446 | 447 | def find_position(self, xv, yv): 448 | """Takes seen pixel position and search deviated pixel position.""" 449 | # Convert position in spheric coord 450 | phi = xv*self.FOV_img/360/self.img_res 451 | theta = yv*self.FOV_img_Y/180/self.img_res_Y 452 | phi2 = phi+(360-self.FOV_img)/2 453 | theta2 = theta+(180-self.FOV_img_Y)/2 454 | 455 | u, v, w = spheric2cart(np.radians(theta2), np.radians(phi2)) # give cartesian coord of pixel 456 | 457 | # ignore errors due to /0 -> inf, -inf 458 | # divide (w/v) and invalid arctan2() 459 | with np.errstate(all='ignore'): # OPTIMIZE: see comment about pi = -pi and don't matter if -0 or 0 -> just replace by pi 460 | beta = -np.arctan(w/v) 461 | # beta2 = -np.arctan2(w, v) 462 | 463 | # v2 = np.dot(rotation_matrix(beta), [u, v, w]) # take 3*3 created matrix and aplly to vector 464 | matrix = rotation_matrix(beta) 465 | u2 = matrix[0, 0]*u 466 | v2 = matrix[1, 1]*v+matrix[1, 2]*w 467 | w2 = matrix[2, 1]*v+matrix[2, 2]*w 468 | _, seen_angle = cart2spheric(u2, v2, w2) # return phi in equator "projection" 469 | 470 | seen_angle = np.degrees(seen_angle) 471 | seen_angle = np.mod(seen_angle, 360) # define phi [0, 360] 472 | 473 | # seen_angle[seen_angle > 360] -= 360 474 | deviated_angle = np.zeros(seen_angle.shape) 475 | deviated_angle[seen_angle < 180] = self.interpolation(seen_angle[seen_angle < 180]) 476 | deviated_angle[seen_angle >= 180] = 360 - self.interpolation(360-seen_angle[seen_angle >= 180]) 477 | # np.flip(deviated_angle, 1) " mais probleme overlap entre left et right 478 | 479 | theta = pi/2# *np.ones(deviated_angle.shape) 480 | phi = np.radians(deviated_angle) 481 | u3, v3, w3 = spheric2cart(theta, phi) #get cart coord of deviated pixel 482 | 483 | matrix = rotation_matrix(-beta) 484 | u4 = matrix[0, 0]*u3 485 | v4 = matrix[1, 1]*v3+matrix[1, 2]*w3 486 | w4 = matrix[2, 1]*v3+matrix[2, 2]*w3 487 | 488 | theta, phi = cart2spheric(u4, v4, w4) #give spheric coord of deviated pixel 489 | 490 | theta, phi = np.degrees(theta), np.degrees(phi) 491 | 492 | phi -= (360-self.FOV_img)/2 493 | theta -= (180-self.FOV_img_Y)/2 494 | 495 | with np.errstate(all='ignore'): # OPTIMIZE 496 | phi = np.mod(phi, 360) # define phi [0, 360] 497 | theta = np.mod(theta, 180) # define phi [0, 360] 498 | 499 | phi[phi == 360] = 0 500 | xv2 = phi*360/self.FOV_img*self.img_res 501 | yv2 = theta*180/self.FOV_img_Y*self.img_res_Y #give deviated angle pixel position 502 | 503 | xv2[np.isnan(xv2)] = -1 504 | yv2[np.isnan(yv2)] = -1 505 | 506 | xv2 = np.array(xv2, dtype=int) 507 | yv2 = np.array(yv2, dtype=int) 508 | 509 | return xv2, yv2 510 | 511 | def gif(self, nbr_offset=1): 512 | """Apply seveal offset and save each images to be reconstructed 513 | externaly to make agif animation of a moving black hole.""" 514 | file_name, extension = return_folder_file_extension(self.img_name)[1:] 515 | 516 | offset_X_temp = 0 # locals, relative to img2 given, not absolute 517 | offset_X_tot = 0 518 | time_estimate = 2.2e-8*self.axe_X*self.axe_Y*(nbr_offset+1) 519 | print("\ntotal offsets estimation time: %.1f" % (time_estimate)) 520 | 521 | if nbr_offset == 1: # avoid two offsets for a single image 522 | nbr_offset = 0 523 | 524 | # +1 for final offset to set back image to initial offset 525 | for a in range(nbr_offset+1): 526 | if a < nbr_offset: 527 | print("\n%s/%s\toffset: %s" % (a+1, nbr_offset, offset_X_tot)) 528 | 529 | self.img_debut = img_offset_X(self.img_debut, offset_X_temp) 530 | 531 | img2 = self.img_pixels(self.img_debut) 532 | 533 | if self.fixed_background != True and self.fixed_background != False: 534 | 535 | if self.fixed_background.get() is True: # needed for GUI 536 | img2 = img_offset_X(img2, -offset_X_tot) # if want a fixed background and moving black hole 537 | 538 | if self.fixed_background is True: 539 | img2 = img_offset_X(img2, -offset_X_tot) # if want a fixed background and moving black hole 540 | 541 | if nbr_offset != 1 and a < nbr_offset: #if need to save real offset, put offset_x in global and offset_x+offset_x2+offset_x_tot in save name 542 | image_name_save = "%s_D=%s_Rs=%s_size=%s_offset=%i%s" % (file_name, self.D, self.Rs, self.axe_X, offset_X_tot+self.offset_X+self.offset_X2, extension) 543 | img2.save(image_name_save) 544 | print("Save: "+image_name_save) 545 | 546 | if a < nbr_offset: 547 | offset_X_temp = int(self.axe_X/nbr_offset) #at the end to have offset=0 for the first iteration 548 | offset_X_tot += offset_X_temp 549 | 550 | elif nbr_offset > 1: 551 | print("\nOffset reset to %i" % (offset_X_tot % self.axe_X)) 552 | 553 | self.img2 = img2 554 | 555 | def plot(self): 556 | """Plot the black hole and connect functions to the canvas.""" 557 | self.fig = plt.figure('Black hole') 558 | self.fig.clf() #clear the graph to avoir superposing data from the same set (can be deactivated if need to superpose) 559 | self.ax = plt.subplot() 560 | 561 | if self.img2 is not None: 562 | self.ax.imshow(self.img2) 563 | else: 564 | print("No black hole deformation in the memory, displayed the original image instead.") 565 | self.ax.imshow(self.img_debut) 566 | 567 | self.ax.set_title("scrool to zoom in or out \nright click to add an offset in the background \nleft click to refresh image \n close the option windows to stop the program") 568 | self.fig.canvas.mpl_connect('scroll_event', self.onscroll) 569 | self.fig.canvas.mpl_connect('button_press_event', self.onclick) 570 | self.fig.canvas.mpl_connect('axes_leave_event', self.disconnect) 571 | self.fig.canvas.mpl_connect('axes_enter_event', self.connect) 572 | 573 | self.draw() 574 | 575 | def img_pixels(self, img_debut): 576 | """Takes deviated pixels color and assign them to seen pixels by using 577 | the matrices img_matrix_x and img_matrix_y.""" 578 | pixels = np.array(img_debut) 579 | pixels2 = np.array(img_debut) 580 | 581 | xv, yv = self.img_matrix_x, self.img_matrix_y 582 | 583 | yv[yv >= self.axe_Y] = -2 # locate pixels outside of the image 584 | xv[xv >= self.axe_X] = -2 585 | 586 | pixels2 = pixels[yv, xv] # apply the black hole deformation 587 | 588 | pixels2[xv == -1] = [0, 0, 0] # color the black hole in black 589 | pixels2[yv == -2] = [255, 192, 203] # color pixels outside 590 | pixels2[xv == -2] = [255, 192, 203] 591 | 592 | img2 = Image.fromarray(pixels2.astype('uint8'), 'RGB') 593 | return img2 594 | 595 | def img_save(self): 596 | """Save the image img2 with the parameters values.""" 597 | file_name, extension = return_folder_file_extension(self.img_name)[1:] 598 | image_name_save = "%s_D=%s_Rs=%s_size=%s_offset=%i%s" % (file_name, self.D, self.Rs, self.axe_X, self.offset_X+self.offset_X2, extension) 599 | 600 | if self.img2 is not None: 601 | self.img2.save(image_name_save) 602 | print("Saved "+image_name_save) 603 | else: 604 | print("No image to save") 605 | 606 | def onscroll(self, event): 607 | """Zoom in or out the canvas when using the scrool wheel.""" 608 | if self.out_graph is False: 609 | self.zoom += 10*event.step 610 | 611 | if self.zoom >= self.axe_X/2/self.FOV_img*self.FOV_img_Y: 612 | self.zoom = self.axe_X/2/self.FOV_img*self.FOV_img_Y 613 | 614 | if self.zoom <= 0: 615 | self.zoom = 0 616 | 617 | self.draw() 618 | 619 | def draw(self): 620 | """Draw the black hole on the canvas by setting the axes need to match 621 | the zoom setting.""" 622 | # TODO: take graph axe value but careful with changing size when computing 623 | # need to change the equation because based on full scale and not arbitrary position 624 | left_side = self.zoom*self.FOV_img/self.FOV_img_Y 625 | right_side = self.axe_X - self.zoom*self.FOV_img/self.FOV_img_Y 626 | up_side = self.zoom 627 | down_side = self.axe_Y - self.zoom 628 | 629 | if right_side == self.axe_X: 630 | right_side -= 1 631 | if down_side == self.axe_Y: 632 | down_side -= 1 633 | 634 | self.ax.set_xlim((left_side, right_side)) 635 | self.ax.set_ylim((down_side, up_side)) 636 | # print((self.left_side, self.right_side), (self.down_side, self.up_side)) 637 | self.fig.canvas.draw() 638 | plt.draw() 639 | plt.pause(0.001) 640 | 641 | def onclick(self, event): 642 | """Use to apply an offset when right clicking. Will be replace by a 643 | Slider from matplotlib.""" 644 | # OPTIMIZE: create bar to offset and del this function 645 | if self.out_graph is False: 646 | 647 | if (event.button == 3 and event.xdata >= 0 648 | and event.xdata <= self.axe_X): 649 | self.offset_X += self.offset_X2 650 | self.offset_X2 = int(self.axe_X/2 - event.xdata - self.offset_X) 651 | self.img_debut = img_offset_X(self.img_debut, self.offset_X2) 652 | self.img2 = self.img_pixels(self.img_debut) 653 | self.ax.imshow(self.img2) 654 | self.fig.canvas.draw() 655 | 656 | def disconnect(self, event): 657 | """Used to known when the mouse is outside the black hole canvas.""" 658 | self.out_graph = True 659 | 660 | 661 | def connect(self, event): 662 | """Used to known when the mouse is inside the black hole canvas.""" 663 | self.out_graph = False 664 | 665 | 666 | class BlackHoleGUI: 667 | """GUI controling a blackhole instance.""" 668 | def __init__(self, blackhole=None): 669 | """GUI controling a blackhole instance.""" 670 | if blackhole is None: 671 | self.blackhole = BlackHole() 672 | else: 673 | self.blackhole = blackhole 674 | 675 | self.create_interface() 676 | 677 | def create_interface(self): 678 | """Create the interface.""" 679 | root = Tk() 680 | frame = Frame(root) 681 | root.title("Black hole options") 682 | frame.pack() 683 | 684 | open_file_button = Button(frame, text="Open image", width=14, command=self.open_file_name) 685 | open_file_button.grid(row=0, column=0) 686 | 687 | L1 = Label(frame, text="radius") 688 | L1.grid(row=1, column=0) 689 | var = StringVar(root) 690 | var.set(self.blackhole.Rs) 691 | self.radius = Spinbox(frame, from_=1e-100, to=1e100, textvariable=var, bd=2, width=7) 692 | self.radius.grid(row=1, column=1) 693 | 694 | L2 = Label(frame, text="distance") 695 | L2.grid(row=2, column=0) 696 | var = StringVar(root) 697 | var.set(self.blackhole.D) 698 | self.distance = Spinbox(frame, from_=1e-100, to=1e100, textvariable=var, bd=2, width=7) 699 | self.distance.grid(row=2, column=1) 700 | 701 | compute_button = Button(frame, text="Compute", width=14, command=self.compute) 702 | compute_button.grid(row=1, column=2) 703 | 704 | self.message = Label(frame, text="", width=20) 705 | self.message.grid(row=1, column=3) 706 | self.message5 = Label(frame, text="", width=20) 707 | self.message5.grid(row=2, column=3) 708 | 709 | L3 = Label(frame, text="Image size") 710 | L3.grid(row=3, column=0) 711 | var = StringVar(root) 712 | var.set(self.blackhole.axe_X) 713 | self.size = Spinbox(frame, from_=1, to=1e100, textvariable=var, bd=2, width=7) 714 | self.size.grid(row=3, column=1) 715 | 716 | self.message2 = Label(frame, text="", width=20) 717 | self.message2.grid(row=3, column=3) 718 | 719 | save_button = Button(frame, text="Save image", width=14, command=self.img_save) 720 | save_button.grid(row=4, column=2) 721 | 722 | self.message4 = Label(frame, text="") 723 | self.message4.grid(row=4, column=3) 724 | 725 | message6 = Label(frame, text="Fix background") 726 | message6.grid(row=5, column=0) 727 | 728 | fixed_background = BooleanVar() 729 | C1 = Checkbutton(frame, text="", variable=fixed_background, 730 | onvalue=True, offvalue=False) 731 | C1.grid(row=5, column=1) 732 | 733 | L4 = Label(frame, text="images") 734 | L4.grid(row=6, column=0) 735 | 736 | var = StringVar(root) 737 | var.set(10) 738 | self.number = Spinbox(frame, from_=1, to=1e100, textvariable=var, bd=2, width=7) 739 | self.number.grid(row=6, column=1) 740 | 741 | save_gif_button = Button(frame, text="Save animation", width=14, command=self.save_gif) 742 | save_gif_button.grid(row=6, column=2) 743 | 744 | self.message3 = Label(frame, text="") 745 | self.message3.grid(row=6, column=3) 746 | 747 | root.mainloop() 748 | 749 | def compute(self): 750 | """Call the compute method of the blackhole instance.""" 751 | if not self.test_GUI(): 752 | return None 753 | self.reset_msg() 754 | 755 | try: 756 | if float(self.distance.get()) <= 0 or float(self.radius.get()) <= 0: 757 | self.message["text"] = "Can't be 0 or negative" 758 | return None 759 | elif (float(self.distance.get()) == self.blackhole.D 760 | and float(self.radius.get()) == self.blackhole.Rs 761 | and int(self.size.get()) == self.blackhole.axe_X 762 | and self.blackhole.img_matrix_x is not None): 763 | self.message["text"] = "same values as before" 764 | return None 765 | elif float(self.distance.get()) < float(self.radius.get()): 766 | self.message["text"] = "Inside black hole !" 767 | return None 768 | else: 769 | self.blackhole.D = float(self.distance.get()) 770 | self.blackhole.Rs = float(self.radius.get()) 771 | 772 | except ValueError: 773 | self.message["text"] = "radius, distance" 774 | self.message5["text"] = "& image size are floats" 775 | return None 776 | 777 | if self.blackhole.axe_X != int(self.size.get()): 778 | try: 779 | self.increase_resolution() 780 | except ValueError as ex: 781 | print(ex) 782 | return None 783 | 784 | self.message["text"] = "Computing" 785 | 786 | self.blackhole.compute(self.blackhole.Rs, self.blackhole.D) 787 | self.message["text"] = "Done !" 788 | 789 | def increase_resolution(self): 790 | """Increase the image size and correct offset and zoom to match the new 791 | size.""" 792 | if not self.test_GUI(): 793 | return None 794 | self.reset_msg() 795 | 796 | if int(self.size.get()) <= 0: 797 | self.message2["text"] = "Can't be 0 or negative" 798 | raise ValueError("Can't be 0 or negative") 799 | elif int(self.size.get()) == self.blackhole.axe_X: 800 | self.message2["text"] = "same size as before" 801 | else: 802 | # self.message2["text"] = "Computing" 803 | new_size_image = int(self.size.get()) 804 | self.blackhole.offset_X += self.blackhole.offset_X2 805 | self.blackhole.offset_X2 = 0 806 | res_fact = new_size_image/self.blackhole.axe_X 807 | 808 | self.blackhole.axe_X = new_size_image 809 | 810 | self.blackhole.offset_X *= res_fact 811 | self.blackhole.zoom *= res_fact 812 | 813 | try: 814 | self.blackhole.img_resize(self.blackhole.axe_X) 815 | self.blackhole.img_debut = img_offset_X( 816 | self.blackhole.img_debut, self.blackhole.offset_X) 817 | except ValueError as ex: 818 | print("error when resizing image") 819 | raise ValueError(ex) 820 | 821 | def img_save(self): 822 | """Save the image img2 with the parameters values. GUI version.""" 823 | if not self.test_GUI(): 824 | return None 825 | self.reset_msg() 826 | 827 | file_name, extension = return_folder_file_extension(self.blackhole.img_name)[1:] 828 | image_name_save = "%s_D=%s_Rs=%s_size=%s_offset=%i%s" % (file_name, self.blackhole.D, self.blackhole.Rs, self.blackhole.axe_X, self.blackhole.offset_X+self.blackhole.offset_X2, extension) 829 | 830 | if self.blackhole.img2 is not None: 831 | self.blackhole.img2.save(image_name_save) 832 | print("Saved "+image_name_save) 833 | self.message4["text"] = "Saved "+image_name_save 834 | else: 835 | self.message4["text"] = "No image to save" 836 | 837 | def save_gif(self): 838 | """Call the gif method if the blackhole instance.""" 839 | if not self.test_GUI(): 840 | return None 841 | self.reset_msg() 842 | 843 | file_name, extension = return_folder_file_extension(self.blackhole.img_name)[1:] 844 | 845 | if self.blackhole.img2 is not None: 846 | self.message3["text"] = "Computing" 847 | else: 848 | self.message3["text"] = "No image to save" 849 | return None 850 | try: 851 | if int(self.number.get()) <= 0: 852 | print("Can't be 0 or negative") 853 | self.message3["text"] = "Can't be 0 or negative" 854 | else: 855 | self.blackhole.gif(int(self.number.get())) 856 | image_name_save = "%s_D=%s_Rs=%s_size=%s_offset=%s%s" % (file_name, self.blackhole.D, self.blackhole.Rs, self.blackhole.axe_X, "*", extension) 857 | self.message3["text"] = "Saved "+image_name_save 858 | except Exception: 859 | print("need integer") 860 | self.message3["text"] = "need integer" 861 | 862 | def open_file_name(self): 863 | """Open a new image and aply the previous parameters. 864 | Compute a new black hole if parameters were changed. 865 | Source: https://gist.github.com/Yagisanatode/0d1baad4e3a871587ab1""" 866 | if not self.test_GUI(): 867 | return None 868 | self.reset_msg() 869 | 870 | img_name = askopenfilename( 871 | # initialdir="", 872 | filetypes=[("Image File", ".png .jpg")], 873 | title="Image file") 874 | 875 | if img_name: 876 | size = int(self.size.get()) 877 | self.blackhole.open(img_name, size=size) 878 | if self.blackhole.img_matrix_x is not None: 879 | if self.blackhole.axe_X != self.blackhole.img_matrix_x.shape[1]: 880 | self.blackhole.compute(self.blackhole.Rs, self.blackhole.D) 881 | 882 | self.blackhole.offset_X += self.blackhole.offset_X2 883 | self.blackhole.offset_X2 = 0 884 | self.blackhole.img_debut = img_offset_X( 885 | self.blackhole.img_debut, self.blackhole.offset_X) 886 | self.blackhole.img2 = self.blackhole.img_pixels(self.blackhole.img_debut) 887 | 888 | self.blackhole.plot() 889 | 890 | def reset_msg(self): 891 | """Reset all GUI messages.""" 892 | if not self.test_GUI(): 893 | return None 894 | self.message5["text"] = "" 895 | self.message4["text"] = "" 896 | self.message3["text"] = "" 897 | self.message2["text"] = "" 898 | self.message["text"] = "" 899 | 900 | def test_GUI(self): 901 | """Return True if the GUI is active and False otherwise.""" 902 | try: 903 | temp = self.message["text"] 904 | self.message["text"] = "" 905 | self.message["text"] = temp 906 | return True 907 | except Exception: 908 | print("The GUI is not openned, function ignored") 909 | return False 910 | 911 | 912 | def listdirectory(path, matrix_file): 913 | """Allow to search if files exist in folders. 914 | Source: https://python.developpez.com/faq/?page=Fichier#isFile""" 915 | for root, dirs, files in os.walk(path): 916 | for i in files: 917 | fichier = os.path.join(root, i) 918 | if fichier == matrix_file: 919 | return True 920 | return False 921 | 922 | 923 | def spheric2cart(theta, phi): 924 | """Convert spherical coordinates to cartesian.""" 925 | x = np.sin(theta) * np.cos(phi) 926 | y = np.sin(theta) * np.sin(phi) 927 | z = np.cos(theta) 928 | return x, y, z 929 | 930 | 931 | def cart2spheric(x, y, z): 932 | """Convert cartesian coordinates to spherical.""" 933 | # doesn't compute r because chosen egal to 1 934 | with np.errstate(all='ignore'): 935 | theta = np.arccos(z) 936 | phi = np.arctan2(y, x) 937 | 938 | return theta, phi 939 | 940 | 941 | def rotation_matrix(beta): 942 | """Return the rotation matrix associated with counterclockwise rotation 943 | about the x axis by beta degree. 944 | Source: https://stackoverflow.com/questions/6802577/rotation-of-3d-vector 945 | """ 946 | beta = np.array(beta) 947 | aa_bb, ab2neg = np.cos(beta), np.sin(beta) 948 | zero, one = np.zeros(beta.shape), np.ones(beta.shape) 949 | 950 | return np.array([[one, zero, zero], 951 | [zero, aa_bb, -ab2neg], 952 | [zero, ab2neg, aa_bb]]) 953 | 954 | 955 | def img_offset_X(img, offset_X): 956 | """Return the image with an offset in the X-axis. 957 | Allow to rotate around the blackhole.""" 958 | offset_X = int(offset_X) 959 | (axe_X, axe_Y) = img.size 960 | 961 | while offset_X >= axe_X: 962 | offset_X -= axe_X 963 | 964 | if offset_X == 0: 965 | return img 966 | 967 | if offset_X < 0: 968 | offset_X = -offset_X 969 | img_right = img.crop((0, 0, axe_X-offset_X, axe_Y)) 970 | img_left = img.crop((axe_X-offset_X, 0, axe_X, axe_Y)) 971 | img.paste(img_right, (offset_X, 0)) 972 | 973 | else: 974 | img_right = img.crop((0, 0, offset_X, axe_Y)) 975 | img_left = img.crop((offset_X, 0, axe_X, axe_Y)) 976 | img.paste(img_right, (axe_X-offset_X, 0)) 977 | 978 | img.paste(img_left, (0, 0)) 979 | 980 | return img 981 | 982 | 983 | def return_folder_file_extension(img_name): 984 | """Return the foler, file and extension of a file.""" 985 | *folder, file = img_name.replace("\\", "/").split("/") 986 | 987 | if len(folder) != 0: 988 | folder = folder[0] 989 | 990 | file, extension = file.split(".") 991 | 992 | return folder, file, "."+extension 993 | 994 | 995 | def example(): 996 | """Compute and save example.""" 997 | blackhole = BlackHole() 998 | img_name = os.path.join('images', 'milkyway.jpg') 999 | blackhole.open(img_name, size=1000) 1000 | blackhole.compute(Rs=8, D=50) 1001 | blackhole.img2.save('example.jpg') 1002 | 1003 | 1004 | def approching_blackhole(): 1005 | """Approching a black hole example.""" 1006 | blackhole = BlackHole() 1007 | Rs = 8.0 1008 | D_list = np.round(10**np.linspace(np.log10(50), np.log10(100000), 30)) 1009 | blackhole.open(blackhole.img_name, size=2000) 1010 | 1011 | for D in D_list: 1012 | blackhole.compute(Rs, D) 1013 | blackhole.img_save() 1014 | 1015 | 1016 | if __name__ == "__main__": 1017 | # blackholeGUI = BlackHoleGUI() 1018 | 1019 | blackhole = BlackHole() 1020 | blackhole.compute(8, 50) 1021 | blackholeGUI = BlackHoleGUI(blackhole) 1022 | 1023 | # img_name = os.path.join('images', 'milkyway.jpg') 1024 | # blackhole.open(img_name, size=360) 1025 | # blackhole.img_resize(360) 1026 | # blackhole.compute(Rs=8, D=50) 1027 | -------------------------------------------------------------------------------- /black_hole_moving2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/black_hole_moving2.gif -------------------------------------------------------------------------------- /images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/images/default.png -------------------------------------------------------------------------------- /images/milkyway.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/images/milkyway.jpg -------------------------------------------------------------------------------- /milkyway D=50 Rs=8 size=6000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/milkyway D=50 Rs=8 size=6000.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=0.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1029.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1029.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1078.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1078.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1127.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1127.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1176.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1176.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1225.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1225.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1274.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1274.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1323.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1323.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1372.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1372.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1421.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1421.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=147.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=147.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1470.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1470.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1519.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1519.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1568.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1568.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1617.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1617.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1666.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1666.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1715.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1715.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1764.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1764.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1813.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1813.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1862.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1862.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=1911.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=1911.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=196.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=196.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=245.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=245.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=294.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=294.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=343.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=343.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=392.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=392.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=441.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=441.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=49.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=490.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=490.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=539.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=539.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=588.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=588.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=637.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=637.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=686.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=686.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=735.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=735.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=784.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=784.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=833.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=833.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=882.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=882.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=931.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=931.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=98.jpg -------------------------------------------------------------------------------- /offsets/milkyway D=50 Rs=8 size=1980 offset_X=980.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/offsets/milkyway D=50 Rs=8 size=1980 offset_X=980.jpg -------------------------------------------------------------------------------- /old results/Figure_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/old results/Figure_2.png -------------------------------------------------------------------------------- /old results/Figure_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Python-simulation/Black-hole-simulation-using-python/0a207a9d1766ad7c2ac214e11ad06ce0be62b54e/old results/Figure_6.png -------------------------------------------------------------------------------- /old results/black hole simulation - menu.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import math #import known fonction and constants 3 | import time # Used to check computation time 4 | import os.path # Used to search files on computer 5 | 6 | import matplotlib.pyplot as plt # Graphical module 7 | import numpy as np # Use for matrices and list 8 | from scipy.interpolate import interp1d # Use for interpolation 9 | from scipy.integrate import solve_ivp # Integrate ordinary differential equations 10 | from scipy.constants import pi 11 | from scipy.constants import c # Speed light vaccum (m/s) 12 | from scipy.constants import G # Newton constant m3/Kg/s2 13 | from PIL import Image # Use to open, modify and save images 14 | from tkinter import Button, Tk, Frame, Entry, Label, Checkbutton, BooleanVar, StringVar, Spinbox 15 | from tkinter.filedialog import askopenfilename 16 | from numba import jit, prange # njit 17 | # could be nice to use #@njit(parallel=True) 18 | 19 | """ 20 | Programs simulating photons trajectory closed to a static black hole. 21 | Render the perceive image deformation by the black hole. 22 | The code is ugly because it has been done when I first started python. 23 | Must change it completly to use classes and suppress the awful global variables 24 | """ 25 | # ============================================================================= 26 | """Variables & constants initialisation (don't change)""" 27 | t_max = 10*pi 28 | Ms = 1.9891 * 10**30 # solar mass in Kg 29 | Ds = 149597870700 # sun-earth distance in m 30 | #------------------------------------------------------------------------------ 31 | """Only values that can be changed (without knowing the program)""" 32 | #M = 1.7342*10**22/Ms # Black hole mass in solar mass (alternative, del M below) 33 | #Rs = 2*G*Ms*M/c**2/Ds # Schwarzschild radius in Astronomical unit (ua) 34 | Rs = 8 # Schwarzschild radius in ua 35 | M = Rs * c**2 / 2 / G * Ds / Ms # Black hole mass in solar masses (del if use solar mass) 36 | D = 50 # Distance from the black hole in ua 37 | final_size_img = 360 # Over axe_X 38 | use_matrix = True # Use matrices if exists 39 | save_matrix = True # False: if don't want to save countless matrices, True: will save or overwrite matrices if exists 40 | kind = 'linear' # Interpolation: linear for speed(less accurate), cubic for precision (slow) 41 | FOV_img = 360 # The image FOV (it doesn't change the current image FOV !) 42 | FOV = FOV_img # Can be <= FOV_img to reduce compute area, 43 | #FOV = 100 # Must be >FOV_img if FOV_img small(otherwise creat a non-compute cercle) 44 | offset_X_tot = 0 # If first offset !=0 (allow to start at middle and keep going with a diferent offset_X) 45 | offset_X = 0 # Initialize offset and can be use to choose offset instead of dependence on nbr_offset 46 | nbr_offset = 1 # Number of image needed with a constant offset between (must be changed to specify a precise angle) 47 | fixed_background = True 48 | display_trajectories = True 49 | display_interpolation = True 50 | #------------------------------------------------------------------------------ 51 | print ("M ", "%.1e"%M, "M☉", "\t%.2e"%(M*Ms), "Kg") 52 | print("Rs", Rs, "ua", "\t%.2e"%(Rs*Ds), "m") 53 | print("D ", D, "ua", "\t%.2e"%(D*Ds), "m") 54 | 55 | 56 | def return_folder_file_extension(image_name): 57 | *folder, file = image_name.replace("\\", "/").split("/") 58 | 59 | if len(folder) != 0: 60 | folder = folder[0] 61 | 62 | file, extension = file.split(".") 63 | 64 | return folder, file, "."+extension 65 | 66 | def fun(phi, u): 67 | """Represent the differential equation : d²u(ɸ)/dɸ²=3/2*Rs*u²(ɸ)-u(ɸ)""" 68 | v0 = u[1] #correspond to u' 69 | v1 = 3/2*Rs*u[0]**2 - u[0] #correspond to u" 70 | return v0, v1 71 | 72 | 73 | #def eventR(phi, u): #not needed and don't work with ivp (without it we get an error message but irrelevant) 74 | # """stop solver if radius > sphere limit""" 75 | # R = 1000 76 | # return (1/u[0]-math.sqrt(R**2-D**2*math.sin(phi)**2)+D*math.cos(phi)) 77 | #eventR.terminal = True 78 | 79 | 80 | def eventRs(phi, u): 81 | """stop solver if radius < black hole radius""" 82 | return 1/u[0] - Rs 83 | eventRs.terminal = True 84 | 85 | 86 | def solver(D, alpha): 87 | """Solve the differetial equation, in spherical coordinate, for a static 88 | black hole using solve_ivp. 89 | Allows to compute the photon trajectory giving its distance from the 90 | black hole and its initial angular speed""" 91 | if alpha == 0: #skip divided by 0 error 92 | return [0], [0] #r and phi=0 93 | 94 | if alpha == 180: 95 | return [D], [0] # if angle= pi then, tan(pi)=0 so 1/tan=1/0 96 | 97 | y0 = [1/D, 1/(D*math.tan(math.radians(alpha)))] #initial value for position and angular speed 98 | sol = solve_ivp(fun=fun, t_span=[0, t_max], y0=y0, method='Radau', events=[eventRs])#,eventR])#,t_eval=np.linspace(0, t_max, 10000)) #dense_output=False 99 | 100 | if sol.t[-1] == t_max: 101 | raise StopIteration ("solver error, alpha reached computing limit (loop number)") 102 | 103 | phi = sol.t 104 | r = np.abs(1/sol.y[0,:]) #must use this because solver can't be stop before infinity because negative 105 | 106 | return r, phi 107 | 108 | 109 | def search_alpha_min(D, img_res, Rs): 110 | """Return last angle at which the photon is kept by the black hole""" 111 | # debut = time.process_time() 112 | alpha_min = 0 113 | 114 | for alpha in range(0, 180, 4): 115 | r, phi = solver(D, alpha) 116 | if r[-1] > 1.1*Rs: 117 | break 118 | 119 | if alpha-4 > 0: 120 | alpha_min = alpha-4 121 | # print("alpha_min :",alpha_min,"(-4)") 122 | i = 1 123 | 124 | while alpha_min == 0 or round(alpha_min*img_res) != round((alpha_min+i*10)*img_res): #increase precision 125 | 126 | for alpha in range(int(alpha_min/i), int(180/i), 1): 127 | alpha = alpha*i 128 | r, phi = solver(D, alpha) 129 | 130 | if r[-1] > 1.1*Rs: 131 | break 132 | 133 | if alpha-i > 0: 134 | alpha_min = alpha-i 135 | # print("alpha_min : ",alpha_min," (-",i,")",sep="") 136 | 137 | i = i/10 138 | i = 10*i 139 | alpha_min += i 140 | print("alpha_min: ",alpha_min," [",alpha_min-i,";",alpha_min,"]",sep="") 141 | # fin = time.process_time() 142 | # print("min angle time",fin-debut) 143 | 144 | return alpha_min 145 | 146 | 147 | def trajectories(D, alpha_finder, img_res, Rs): 148 | """Compute several photons trajectories in order to interpolate the 149 | possibles trajectories and gain in execution time""" 150 | # debut = time.process_time() 151 | 152 | alpha_min = search_alpha_min(D, img_res, Rs) 153 | 154 | if display_trajectories is True: 155 | plt.figure(num='trajectories') 156 | plt.clf() #clear the graph to avoir superposing data from the same set (can be deactivated if need to superpose) 157 | ax = plt.subplot(111, projection='polar') #warning if use python in ligne (!= graphical) graphs got superposed 158 | plt.ylabel('phi(°)\n\n\n\n', rotation=0) 159 | ax.set_xlabel('R(UA)') 160 | ax.set_title("light trajectories close to a black hole\n", va='bottom') 161 | ax.set_rlim((0, 4*D)) 162 | ax.set_rlabel_position(-90) 163 | 164 | seen_angle = [] 165 | deviated_angle = [] 166 | # debut = time.process_time() 167 | booli = False #avoid points from the first loop to exceed points from the second loop 168 | points = 40 169 | 170 | for i in range(6): 171 | # print(alpha_finder) 172 | 173 | for alpha in np.linspace(alpha_finder, alpha_min, num=points, endpoint=booli): 174 | r, phi = solver(D, alpha) 175 | 176 | if r[-1]>Rs*1.1: #if not capture by black hole 177 | seen_angle.append(180-alpha) #put 180 in the center 178 | deviated_angle.append(math.degrees((phi[-1]+math.asin(D/r[-1]*math.sin(phi[-1]))))) 179 | Ci = 'C'+str(i) 180 | 181 | if display_trajectories is True: 182 | ax.plot(phi, r, Ci) #plot one trajectory 183 | 184 | if kind == 'linear': 185 | alpha_finder = alpha_min + (alpha_finder - alpha_min)/(points/3 + 1) # start a more precise cycle from last point 186 | 187 | else: 188 | alpha_finder = alpha_min + (alpha_finder - alpha_min)/(points + 1) # start a more precise cycle from last point 189 | 190 | points = 10 191 | 192 | if i == 4: 193 | booli = True #allow to display the last point 194 | # fin = time.process_time() 195 | #-------------------------------------------------------------------------- 196 | # print("angles time",fin-debut) 197 | print("") 198 | # fin = time.process_time() 199 | # print("Trajectories time:",round(fin-debut, 1)) 200 | 201 | if display_trajectories is True: 202 | # plt.savefig('trajectories.png', format='png', dpi=1000, bbox_inches='tight') 203 | # plt.savefig('trajectories.eps', format='eps', dpi=1000, bbox_inches='tight') 204 | plt.draw() 205 | # plt.close() #must be fixed if use spyder graph 206 | return seen_angle, deviated_angle 207 | 208 | 209 | npts=20001 # Should not be here -> see how to remove it 210 | def interpolate(x_pivot, f_pivot, npts=20001): 211 | """Display the interpolation (allows to reduce computation time when used) 212 | """ 213 | interpolation = interp1d(x_pivot, f_pivot, kind=kind, bounds_error=True) 214 | xmin = min(x_pivot) 215 | xmax = max(x_pivot) 216 | 217 | seen_angle_splin = np.linspace(xmin, xmax, npts) 218 | deviated_angle_splin = interpolation(seen_angle_splin) 219 | 220 | return seen_angle_splin, deviated_angle_splin 221 | 222 | 223 | def img_offset_X(img_debut, offset_X): 224 | """Return the image with an offset in the X-axis. Allow to creat illusion of black hole movement""" 225 | if FOV != 360 and nbr_offset != 1 : 226 | raise StopIteration ("Can't compute offset for FOV != 360°") 227 | 228 | axe_X = img_debut.size[0] 229 | axe_Y = img_debut.size[1] 230 | 231 | while offset_X >= axe_X: 232 | offset_X -= axe_X 233 | 234 | if offset_X == 0: 235 | return img_debut 236 | 237 | if offset_X < 0: 238 | offset_X = -offset_X 239 | img_right = img_debut.crop((0, 0, axe_X-offset_X, axe_Y)) 240 | img_left = img_debut.crop((axe_X-offset_X, 0, axe_X, axe_Y)) 241 | img_debut.paste(img_right, (offset_X, 0)) 242 | 243 | else: 244 | img_right = img_debut.crop((0, 0, offset_X, axe_Y)) 245 | img_left = img_debut.crop((offset_X, 0, axe_X, axe_Y)) 246 | img_debut.paste(img_right, (axe_X-offset_X, 0)) 247 | 248 | img_debut.paste(img_left, (0, 0)) 249 | 250 | return img_debut 251 | 252 | 253 | def listdirectory2(path, matrix_file): 254 | """from https://python.developpez.com/faq/?page=Fichier#isFile. 255 | Allow to search if files exist in folders""" 256 | for root, dirs, files in os.walk(path): 257 | for i in files: 258 | fichier = os.path.join(root, i) 259 | if fichier == matrix_file: 260 | return True 261 | return False 262 | 263 | 264 | @jit(nopython=True) 265 | def spheric2cart(theta, phi): 266 | x = math.sin(theta) * math.cos(phi) 267 | y = math.sin(theta) * math.sin(phi) 268 | z = math.cos(theta) 269 | return x, y, z 270 | 271 | 272 | @jit(nopython=True) 273 | def cart2spheric(x, y, z): 274 | #doesn't compute r because chosen egal to 1 275 | theta = math.acos(z) 276 | phi = math.atan2(y, x) 277 | while phi < 0: #define phi [0, 360] 278 | phi += pi + pi 279 | while theta < 0: # define theta [0, 180] 280 | theta += pi 281 | if phi == (pi + pi): 282 | phi = 0 283 | return theta, phi 284 | 285 | 286 | def rotation_matrix(beta): 287 | """from https://stackoverflow.com/questions/6802577/rotation-of-3d-vector 288 | Return the rotation matrix associated with counterclockwise rotation about 289 | the x axis by beta degree.""" 290 | a = math.cos(beta / 2.0) 291 | b = -math.sin(beta / 2.0) 292 | aa, bb = a*a, b*b 293 | ab = a*b 294 | return np.array([[aa + bb, 0, 0], 295 | [0, aa - bb, 2*ab], 296 | [0, -2*ab, aa - bb]]) 297 | 298 | 299 | def find_position(x, y): 300 | """take seen pixel position and search deviated pixel position.""" 301 | global axe_X, axe_Y, img_res, img_res_Y, deviated_angle_splin, last_angle 302 | 303 | if y == 0: # skip theta=0 304 | return x, y 305 | 306 | phi, theta = x*FOV_img/360/img_res, y*FOV_img_Y/180/img_res_Y #convert position in spheric coord 307 | phi, theta = phi+(360-FOV_img)/2, theta+(180-FOV_img_Y)/2 308 | # print(phi, theta) 309 | u, v, w = spheric2cart(math.radians(theta), math.radians(phi)) #give cartesian coord of pixel 310 | 311 | if theta == 90: # Needed to avoid error with atan() 312 | beta = 0 313 | 314 | elif phi == 180 or phi == 0: 315 | beta = pi/2 316 | 317 | else: 318 | beta = -math.atan(w/v) #see complex number argument to find angle between plan 0xv and equator (same value for all x -> projection on y, z) 319 | 320 | v2 = np.dot(rotation_matrix(beta), [u, v, w]) #take 3*3 created matrix and aplly to vector 321 | _, seen_angle = cart2spheric(v2[0], v2[1], v2[2]) #return phi in equator "projection" 322 | seen_angle = math.degrees(seen_angle) 323 | 324 | if seen_angle > 360: #assure angle is in [0, 360] 325 | seen_angle -= 360 326 | 327 | if seen_angle > 180: #only right because spherical problem (not right with Kerr) 328 | seen_angle = 360-seen_angle 329 | 330 | try: 331 | deviated_angle = 360-deviated_angle_splin[int(seen_angle*(npts-1)/last_angle)] #search deviated angle base on seen angle 332 | except: 333 | return -1, -1 #inside photosphere (black hole) 334 | else: 335 | 336 | try: 337 | deviated_angle = deviated_angle_splin[int(seen_angle*(npts-1)/last_angle)]#search deviated angle base on seen angle 338 | except: 339 | return -1,-1 #inside photosphere (black hole) 340 | 341 | u, v, w = spheric2cart(pi/2, math.radians(deviated_angle)) #get cart coord of deviated pixel 342 | v2 = np.dot(rotation_matrix(-beta), [u, v, w]) #rotate back to the original plan 343 | theta, phi = cart2spheric(v2[0], v2[1], v2[2]) #give spheric coord of deviated pixel 344 | theta, phi = math.degrees(theta), math.degrees(phi) 345 | phi, theta = phi-(360-FOV_img)/2, theta-(180-FOV_img_Y)/2 346 | x2, y2 = phi*360/FOV_img*img_res, theta*180/FOV_img_Y*img_res_Y #give deviated angle pixel position 347 | 348 | return x2, y2 # return float but matrices will implicitly floor them 349 | 350 | 351 | def matrices_creation(): 352 | """Call find_position function and creat matrices with pixels positions informations.""" 353 | global axe_X, axe_Y, left_side, right_side, up_side, down_side, img_matrice_x, img_matrice_y, FOV, FOV_img, last_angle 354 | 355 | img_matrice_x = np.array([[-1]*axe_X]*axe_Y) 356 | img_matrice_y = np.array([[-1]*axe_X]*axe_Y) 357 | debut = time.process_time() 358 | 359 | if FOV < FOV_img: 360 | print("\nmatrix creation estimation time:",round(2.98*10**(-5)*(FOV/FOV_img)*(axe_X*axe_Y-(2*(180-last_angle)/360*axe_X)**2),1)) #modif les FOV en x et y car diff si on a fov_x=360 fov_y=180 361 | 362 | else: 363 | print("\nmatrix creation estimation time:",round(2.98*10**(-5)*(axe_X*axe_Y-(2*(180-last_angle)/360*axe_X)**2),1)) #modif les FOV en x et y car diff si on a fov_x=360 fov_y=180 364 | 365 | for x in prange(left_side, right_side): 366 | # for x in range(0, axe_X): #colomns scan (phi) mettre autre axe_x pour commencer que debut vrai image (enleve partie noir) 367 | 368 | if x == round(axe_X/4): 369 | print("25%") 370 | elif x == round(axe_X/2): 371 | print("50%") 372 | elif x== round(axe_X*3/4): 373 | print("75%") 374 | # for y in range(1500, axe_Y-1500): take back old program to resize image 375 | # y=np.linspace(0, axe_Y-1, axe_Y) # doesn't work but need to be test if reduce time 376 | 377 | for y in prange(up_side, down_side): 378 | # for y in range(0, axe_Y): #lines scan (theta) 379 | x2, y2 = find_position(x, y) #search deviated angle pixel position 380 | img_matrice_x[y, x] = x2 #create matrices to use data at any time 381 | img_matrice_y[y, x] = y2 382 | 383 | fin = time.process_time() 384 | print("matrix created in time:",round(fin-debut, 1), "s") 385 | 386 | return img_matrice_x, img_matrice_y 387 | 388 | 389 | def img_pixels(img_debut, img2): 390 | """Use matrices, take deviated pixels color and assign them to seen pixels.""" 391 | global left_side, right_side, up_side, down_side, img_matrice_x, img_matrice_y #, image_name 392 | 393 | pixels = img_debut.load() 394 | pixels2 = img2.load() #could have put direcly image creation here, but wanted to recover data if crash or computation stop 395 | 396 | # TODO: I'm sure it can be done with numpy matrix in one line 397 | for x in prange(left_side, right_side): #faster than 0 to axe_X if zoom on high quality image but not compute if unzoom 398 | 399 | for y in prange(up_side, down_side): #lines scan (theta) 400 | # for x in range(0, axe_X): #colomns scan (phi) 401 | # for y in range(0, axe_Y): #lines scan (theta) 402 | x2 = int(img_matrice_x[y, x]) #extract data from matrix 403 | y2 = int(img_matrice_y[y, x]) 404 | 405 | if x2 != -1: 406 | try: 407 | R1, G1, B1 = pixels[x2, y2] # Get deviated pixel color 408 | pixels2[x, y] = (R1, G1, B1) # Colorize seen pixel with deviated pixel color 409 | except: 410 | pixels2[x, y] = (255, 0, 0) # Optional (colorize pixels out of picture) 411 | "" # if don't want to colorize, comment the line above 412 | else: 413 | # pixels2[x, y] = (0, 0, 0) # Optional (colorize the black hole instead of alpha) 414 | "" # if don't want to colorize, comment the line above 415 | 416 | # folder, file_name, extension = return_folder_file_extension(image_name) 417 | # img2.save(file_name+" D="+str(D)+" Rs="+str(Rs)+" size="+str(final_size_img)+".png") #use it instead if use high contrasted image 418 | img2 = img2.convert("RGB") # Warning, if disable -> now with manipulable graph, must add a del command to del previous image inside black hole 419 | return img2 420 | 421 | 422 | def resize_img(img_original, final_size_img): 423 | global left_side, right_side, up_side, down_side, img_res, img_res_Y, axe_X, axe_Y, FOV_img_Y, FOV_img, offset_X 424 | 425 | img_original = img_original.convert("RGB") #if needed RGBA, change all "pixels" ligne by RGBA instead of RGB 426 | axe_X = img_original.size[0] 427 | axe_Y = img_original.size[1] 428 | size_factor = final_size_img/axe_X 429 | axe_X = int(size_factor*axe_X) 430 | axe_Y = int(size_factor*axe_Y) 431 | 432 | if axe_X % 2 != 0: #even dimensions needed for image (don't rember why but error if not) 433 | axe_X -= 1 434 | 435 | if axe_Y % 2 != 0: 436 | axe_Y -= 1 437 | 438 | final_size_img = axe_X 439 | img_original = img_original.resize((axe_X, axe_Y),Image.ANTIALIAS) #can be avoid if put axe_X before resize with conditions 440 | FOV_img_Y = FOV_img*axe_Y/axe_X 441 | 442 | img_original = img_offset_X(img_original, int(offset_X)) 443 | 444 | if FOV_img_Y>180: 445 | raise StopIteration ("Can't have a FOV>360 in the Y-axis") 446 | 447 | print('\nsize ',axe_X,"*",axe_Y," pixels",sep='') 448 | img_res = axe_X/360 #=Pixels per degree along axis 449 | img_res_Y = axe_Y/180 #=Pixels per degree along axis 450 | left_side = 0 451 | right_side = axe_X #must be integer 452 | up_side = 0 453 | down_side = axe_Y 454 | # left_side = int(axe_X/2.5) 455 | # right_side = axe_X-int(axe_X/2.5) #must be integer 456 | # up_side = int(axe_Y/2.5) 457 | # down_side = axe_Y-int(axe_Y/2.5) 458 | return img_original 459 | 460 | 461 | def check_matrices(): 462 | global img_matrice_x, img_matrice_y, axe_X, axe_Y 463 | 464 | abs_path = os.path.abspath(os.path.dirname(sys.argv[0])) 465 | dir_path = os.path.join(abs_path, 'matrix') 466 | 467 | matrix_name_x = str(D)+'_'+str(Rs)+'_'+str(final_size_img)+'_'+str(FOV_img)+'_x.txt' 468 | matrix_file_x = os.path.join(dir_path, matrix_name_x) 469 | x_file = listdirectory2(dir_path, matrix_file_x) 470 | matrix_name_y = str(D)+'_'+str(Rs)+'_'+str(final_size_img)+'_'+str(FOV_img)+'_y.txt' 471 | matrix_file_y = os.path.join(dir_path, matrix_name_y) 472 | y_file = listdirectory2(dir_path, matrix_file_y) 473 | 474 | if use_matrix is True and x_file is True and y_file is True: 475 | print("\nmatrix opening estimation:", round(1.65*10**(-6)*axe_X*axe_Y, 1)) 476 | matrix_opening_debut = time.process_time() 477 | img_matrice_x = np.array([-1]*axe_X) 478 | img_matrice_y = np.array([-1]*axe_Y) 479 | img_matrice_x = np.loadtxt(matrix_file_x) #to be rigorous, add final_size_img_Y, FOV_img, FOV_img_Y, kind.. 480 | img_matrice_y = np.loadtxt(matrix_file_y) 481 | matrix_opening_fin = time.process_time() 482 | print("matrix opening time:",round(matrix_opening_fin-matrix_opening_debut, 1)) 483 | 484 | else: 485 | img_matrice_x, img_matrice_y = matrices_creation() #create a matrix with corresponding (x, y) -> (x2, y2) 486 | 487 | if save_matrix is True: 488 | np.savetxt(matrix_file_x, img_matrice_x, fmt='%i') 489 | np.savetxt(matrix_file_y, img_matrice_y, fmt='%i') 490 | 491 | 492 | def gif(nbr_offset): 493 | global img_debut, img2, offset_X_tot, offset_X, offset_X2, image_name 494 | folder, file_name, extension = return_folder_file_extension(image_name) 495 | 496 | offset_X_temp = 0 #locals, relative to img2 given, not absolute 497 | offset_X_tot = 0 498 | print("\ntotal offsets estimation time:",round(1.6*10**-6*axe_X*axe_Y*(nbr_offset+1),1)) 499 | 500 | for a in prange(nbr_offset+1): 501 | 502 | if a < nbr_offset: 503 | print("\n",a+1,"/",nbr_offset,"\toffset_X: ",offset_X_tot, sep='') 504 | 505 | img_debut = img_offset_X(img_debut, offset_X_temp) 506 | img2 = Image.new('RGBA', (axe_X, axe_Y)) #creat a transparent image (outside of the function to recover info if must stop loop) 507 | debut = time.process_time() 508 | img2 = img_pixels(img_debut, img2) 509 | 510 | if fixed_background != True and fixed_background != False: 511 | 512 | if fixed_background.get() is True: 513 | img2 = img_offset_X(img2,-offset_X_tot) # if want a fixed background and moving black hole 514 | 515 | if fixed_background is True: 516 | img2 = img_offset_X(img2,-offset_X_tot) # if want a fixed background and moving black hole 517 | 518 | if nbr_offset != 1 and a= axe_X/2/FOV_img*FOV_img_Y: 606 | zoom = axe_X/2/FOV_img*FOV_img_Y 607 | if zoom <= 0: 608 | zoom = 0 609 | left_side = int(zoom*FOV_img/FOV_img_Y) 610 | right_side = int(axe_X-zoom*FOV_img/FOV_img_Y) #must be integer 611 | up_side = int(zoom) 612 | down_side = int(axe_Y-zoom) 613 | ax.set_xlim((left_side, right_side-1)) 614 | ax.set_ylim((down_side-1, up_side)) 615 | fig.canvas.draw() 616 | 617 | 618 | def onclick(event): # function that listens to click event 619 | global fig, ax, img, img2, offset_X, out_graph, offset_X2, axe_X, img_debut, left_side, right_side, down_side, right_side 620 | if out_graph is False: 621 | if event.button == 1: 622 | ax.set_xlim((left_side, right_side-1)) 623 | ax.set_ylim((down_side-1, up_side)) 624 | if event.button == 3 and event.xdata >= 0 and event.xdata <= axe_X: 625 | offset_X += offset_X2 626 | offset_X2 = axe_X/2-event.xdata-offset_X 627 | img_debut = img_offset_X(img_debut, int(offset_X2)) 628 | img2 = Image.new( 'RGBA', (axe_X, axe_Y)) 629 | img2 = img_pixels(img_debut, img2) 630 | img = ax.imshow(img2) 631 | fig.canvas.draw() 632 | if event.button == 2: 633 | print("do you think this should do someting ?") 634 | 635 | 636 | def disconnect(event): # function that listens to click event 637 | global cid, out_graph 638 | out_graph = True 639 | 640 | 641 | def connect(event): # function that listens to click event 642 | global cid, out_graph 643 | out_graph = False 644 | 645 | 646 | def compute(): 647 | global D, Rs, img, img2, final_size_img 648 | message5["text"] = "" 649 | message4["text"] = "" 650 | message2["text"] = "" 651 | message["text"] = "" 652 | # print("temp", final_size_img, size.get()) 653 | # print(Rs, radius.get()) 654 | # print(D, distance.get()) 655 | # final_size_img = int(size.get()) 656 | # Rs = float(radius.get()) 657 | # D = float(distance.get()) 658 | try: 659 | if float(distance.get()) <= 0 or float(radius.get()) <= 0: 660 | message["text"] = "Can't be 0 or negative" 661 | elif float(distance.get()) == D and float(radius.get()) == Rs and int(size.get()) == final_size_img: 662 | message["text"] = "same values as before" 663 | elif float(distance.get()) < float(radius.get()): 664 | message["text"] = "Inside black hole !" 665 | else: 666 | D = float(distance.get()) 667 | Rs = float(radius.get()) 668 | try: 669 | if final_size_img != int(size.get()): 670 | increase_resolution() 671 | except ValueError as ex: 672 | print(ex) 673 | return 674 | 675 | message["text"] = "Computing" 676 | # final_size_img = int(size.get()) 677 | M = Rs*c**2/2/G*Ds/Ms #Black hole mass in solar masses (del if use solar mass) 678 | print ("M ","%.1e"%M,"M☉","\t%.2e"%(M*Ms),"Kg") 679 | print("Rs",Rs,"ua","\t%.2e"%(Rs*Ds),"m") 680 | print("D ",D,"ua","\t%.2e"%(D*Ds),"m") 681 | black_hole() 682 | img = ax.imshow(img2) 683 | ax.set_xlim((left_side, right_side-1)) 684 | ax.set_ylim((down_side-1, up_side)) 685 | fig.canvas.draw() 686 | message["text"] = "Done !" 687 | except ValueError: 688 | message["text"] = "Radius, distance" 689 | message5["text"] = "& image size are floats" 690 | 691 | 692 | def increase_resolution(): 693 | global offset_X, offset_X2, img, left_side, right_side, up_side, down_side, fig, ax, zoom, final_size_img, img2, img_debut 694 | # if 'img' not in globals(): #don't work, want to check if graph is close to creat a new one 695 | # print("graphique fermé") 696 | # try: 697 | if float(size.get()) <= 0: 698 | message2["text"] = "Can't be 0 or negative" 699 | raise ValueError ("Can't be 0 or negative") 700 | elif float(size.get()) == final_size_img: 701 | message2["text"] = "same size as before" 702 | else: 703 | # message2["text"] = "Computing" 704 | new_size_image = int(size.get()) 705 | offset_X += offset_X2 706 | offset_X2 = 0 707 | res_fact = new_size_image/final_size_img 708 | 709 | final_size_img = new_size_image 710 | if int(left_side*res_fact) != int(right_side*res_fact-1): 711 | ax.set_xlim((int(left_side*res_fact), int(right_side*res_fact-1))) 712 | if int(down_side*res_fact-1) != int(up_side*res_fact): 713 | ax.set_ylim((int(down_side*res_fact-1), int(up_side*res_fact))) 714 | offset_X *= res_fact 715 | zoom *= res_fact 716 | try: 717 | img_debut = resize_img(img_original, final_size_img) 718 | except ValueError as ex: 719 | print("error when resizing image") 720 | raise ValueError (ex) 721 | 722 | 723 | def save_file(): # function that listens to click event 724 | global img2, offset_X, offset_X2, image_name 725 | folder, file_name, extension = return_folder_file_extension(image_name) 726 | image_name_save = file_name+" D="+str(D)+" Rs="+str(Rs)+" size="+str(final_size_img)+" offset_X="+str(int(offset_X+offset_X2))+extension 727 | img2.save(image_name_save) 728 | print("Save: "+image_name_save) 729 | message4["text"] = "Save: "+image_name_save 730 | 731 | 732 | def save_gif(): # function that listens to click event 733 | global img2, offset_X, offset_X2, image_name 734 | folder, file_name, extension = return_folder_file_extension(image_name) 735 | print("Computing") 736 | message3["text"] = "Computing" 737 | 738 | try: 739 | if int(number.get()) <= 0: 740 | print("Can't be 0 or negative") 741 | message3["text"] = "Can't be 0 or negative" 742 | else: 743 | gif(int(number.get())) 744 | message3["text"] = "Save: "+file_name+" D="+str(D)+" Rs="+str(Rs)+" size="+str(final_size_img)+" offset_X=*"+extension 745 | except: 746 | print("need integer") 747 | message3["text"] = "need integer" 748 | 749 | 750 | def open_file_name(): 751 | """Source: https://gist.github.com/Yagisanatode/0d1baad4e3a871587ab1 752 | Not adapted for functions but classes""" 753 | global image_name, extension, img_original, img_debut, img, img2, ax, fig 754 | 755 | nom_image_temp = askopenfilename( 756 | # initialdir="", 757 | filetypes =[("Image File", ".png .jpg")], 758 | title = "Image file") 759 | 760 | if nom_image_temp: 761 | image_name = nom_image_temp 762 | print("Openning:", image_name) 763 | [img_original, img_debut] = open_image(image_name) 764 | # [img_original, img_debut] = open_image(image_name) 765 | img2=Image.new( 'RGBA', (axe_X, axe_Y)) #creat a transparent image (outside of the function to recover info if must stop loop) 766 | img2=img_pixels(img_debut, img2) 767 | img=ax.imshow(img2) 768 | fig.canvas.draw() 769 | print("Done !") 770 | message2["text"] = "Done !" 771 | # else: 772 | # print("cancel") 773 | 774 | 775 | root = Tk() 776 | frame = Frame(root) 777 | root.title("Black hole options") 778 | frame.pack() 779 | #bottomframe = Frame(root) #si bas diff du haut (inbrique plusieurs widgets) 780 | #bottomframe.pack( side = "bottom" ) 781 | 782 | open_file_button = Button(frame, text="Open image", width=14, command=open_file_name) 783 | open_file_button.grid(row=0, column=0) 784 | 785 | L1 = Label(frame, text="Radius") 786 | L1.grid(row=1, column=0) 787 | var = StringVar(root) 788 | var.set(Rs) 789 | radius = Spinbox(frame, from_=1e-100, to=1e100, textvariable=var, bd=2, width=7) 790 | radius.grid(row=1, column=1) 791 | 792 | L2 = Label(frame, text="Distance") 793 | L2.grid(row=2, column=0) 794 | var = StringVar(root) 795 | var.set(D) 796 | distance = Spinbox(frame, from_=1e-100, to=1e100, textvariable=var, bd=2, width=7) 797 | distance.grid(row=2, column=1) 798 | 799 | compute_button = Button(frame, text="Compute", width=14, command=compute) 800 | compute_button.grid(row=1, column=2) 801 | 802 | message = Label(frame, text="", width=20) #allow to display message when activate [text] 803 | message.grid(row=1, column=3) 804 | message5 = Label(frame, text="", width=20) #allow to display message when activate [text] 805 | message5.grid(row=2, column=3) 806 | 807 | L3 = Label(frame, text="Image size") 808 | L3.grid(row=3, column=0) 809 | var = StringVar(root) 810 | var.set(final_size_img) 811 | size = Spinbox(frame, from_=1, to=1e100, textvariable=var, bd=2, width=7) 812 | size.grid(row=3, column=1) 813 | 814 | message2 = Label(frame, text="", width=20) #allow to display message when activate [text] 815 | message2.grid(row=3, column=3) 816 | 817 | save_button = Button(frame, text="Save image", width=14, command=save_file) 818 | save_button.grid(row=4, column=2) 819 | 820 | message4 = Label(frame, text="") #allow to display message when activate [text] 821 | message4.grid(row=4, column=3) 822 | 823 | message6 = Label(frame, text="Fix background") #allow to display message when activate [text] 824 | message6.grid(row=5, column=0) 825 | 826 | fixed_background = BooleanVar() 827 | C1 = Checkbutton(frame, text = "", variable = fixed_background, \ 828 | onvalue = True, offvalue = False) 829 | C1.grid(row=5, column=1) 830 | 831 | L4 = Label(frame, text="images") 832 | L4.grid(row=6, column=0) 833 | 834 | var = StringVar(root) 835 | var.set(10) 836 | number = Spinbox(frame, from_=1, to=1e100, textvariable=var, bd=2, width=7) 837 | number.grid(row=6, column=1) 838 | 839 | save_gif_button = Button(frame, text="Save animation", width=14, command=save_gif) 840 | save_gif_button.grid(row=6, column=2) 841 | 842 | message3 = Label(frame, text="") #allow to display message when activate [text] 843 | message3.grid(row=6, column=3) 844 | 845 | 846 | # ============================================================================= 847 | # Beginning of the programs 848 | # ============================================================================= 849 | image_name = os.path.join('images', 'default.png') 850 | [img_original, img_debut] = open_image(image_name) 851 | 852 | black_hole() 853 | 854 | plot_black_hole() 855 | 856 | root.mainloop() 857 | # raise SystemExit #not needed and too brutal 858 | 859 | # """ temp""" 860 | 861 | # # increase_resolution() 862 | 863 | # M = Rs*c**2/2/G*Ds/Ms #Black hole mass in solar masses (del if use solar mass) 864 | 865 | # Rs=8.0 866 | # D_list = np.round(10**np.linspace(np.log10(50), np.log10(100000), 30)) 867 | 868 | # for D in D_list: 869 | # black_hole() 870 | 871 | # image_name = "/home/jonathan/temp_perso/milkyway.jpg" 872 | # folder, file_name, extension = return_folder_file_extension(image_name) 873 | # image_name_save = file_name+" D="+str(D)+" Rs="+str(Rs)+" size="+str(final_size_img)+" offset_X="+str(int(offset_X+offset_X2))+extension 874 | # img2.save(image_name_save) 875 | # print("Save: "+image_name_save) 876 | 877 | # # """mesure speed""" 878 | # # speed = [50.0, 51.5, 53.0, 55.0, 60.0, 70.0, 80.0, 90.0, 100.0, 125.0, 150.0, 879 | # # 175.0, 200.0, 250.0, 300.0, 400.0, 500.0, 600.0, 700.0, 800.0, 900.0, 880 | # # 1000.0, 1500.0, 2000.0, 3000.0, 3500.0, 5000.0, 7000.0, 10000.0, 881 | # # 20000.0, 50000.0, 100000.0 882 | # # ] 883 | # # plt.plot(speed) 884 | 885 | 886 | 887 | # # plt.plot(speed) -------------------------------------------------------------------------------- /old results/only_trajectories_with_euler.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt #module d'affichage graphique 2 | import math #module de calcul 3 | 4 | """Initialisation des variables & constantes""" 5 | #---------------------------- 6 | #ajouter input equation de u2 en symbolique ? 7 | c=1 #vitesse de la lumière dans le vide 8 | G=1 #Constante de Newton 9 | M=1 #Masse du trou noir 10 | distance_max=1 #Multiple de D permettant d'arrêter la simulation avant la divergence 11 | dphi = 10**(-4) #Intervalle de phi<<1 (10^-4) pour éviter les divergences 12 | ITERATION=int(3*math.pi/dphi) #Nombre de points à calculer (supérieur à 2pi car certains orbites peuvent aller au delà d'un tour complet) 13 | Rs = 2*G*M/c**2 #Rayon de Schwarzschild 14 | 15 | phi_initial=0 16 | 17 | """Integration d'Euler""" 18 | #---------------------------- 19 | def euler(dist,angle): 20 | angle=angle*math.pi/180 #passage en radian pour la fonction tan 21 | if angle==0: #permet d'eviter l'erreur division par zero 22 | angle=0.01 23 | 24 | u=[1/dist]*ITERATION #Variable associée à la position initial (u est multiplier par iteration pour remplire toutes les cases de la liste et permettre un calcul plus rapide par la suite) 25 | u1=1/(dist*math.tan(angle)) #Variable associée à la direction initiale 26 | 27 | ITERATION_REEL=0 28 | for i in range(ITERATION-1):#on réalise une itération de moins car le calcul nous donne la valeur i+1 29 | ITERATION_REEL+=1 30 | u2=3/2*Rs*u[i]**2-u[i] #calcul de d²u/dɸ² approximée à partir des équations des géodésiques et des conditions initiales (u=1/r) 31 | u1=u1+u2*dphi #calcul de du/dɸ approximée 32 | u[i+1]=u[i]+u1*dphi #calcul de u(ɸ) approximée 33 | if 1/u[i+1]<=Rs or 1/u[i+1]>distance_max*dist: #condition qui stop le calcul si la particule rentre dans le trou noir 34 | break #ou si la distance au trou noir est trop grande(divergence) 35 | 36 | #Création de phi et r à partir des valeurs de u renseignées (il faut réduire la taille des listes car la liste u n'est jamais remplit à cause des conditions d'arrêt) 37 | phi=[phi_initial]*ITERATION_REEL 38 | r=[dist]*ITERATION_REEL 39 | for i in range(ITERATION_REEL-1): 40 | phi[i+1]=phi[i]+dphi 41 | r[i+1]=1/u[i] 42 | 43 | return phi,r #permet de récupérer les listes de r et phi pour analyse tout en oubliant les autres variables utilisées dans la fonction euler 44 | 45 | #permet de créer une liste comprennant la taille du trou noir en fonction de phi (constant pour Schwarzschild) 46 | iter=101 #comme le trou noir est indépendant des variables des trajectoires des particules, on peut choisir un interval différent et réduire le temps de calcul 47 | black_hole=[Rs]*iter 48 | orbite_stable=[3*Rs]*iter 49 | phi_entier=[0]*iter 50 | for i in range(iter-1): 51 | phi_entier[i+1]=phi_entier[i]+2*math.pi/(iter-1) 52 | 53 | """Affichage""" 54 | #------------------------ 55 | #afficher les trajectoires parallèles entre elles et perpendiculaire au trou noir 56 | plt.figure(num='light deviation') #plot differents figure according to a specific name 57 | ax = plt.subplot(111, projection='polar') #subplot permet de mettre plusieurs graph en 1 58 | plt.xlabel('phi(°)') #le processus d'affichage est détaillé dans la fonction affichage 59 | ax.set_ylabel('R(UA)\n\n\n', rotation=0) 60 | ax.set_title("Deviation of light close to a black hole\n", va='bottom') 61 | ax.set_rlabel_position(-90) 62 | ax.plot(phi_entier,black_hole, label='black hole') 63 | ax.plot(phi_entier,orbite_stable, label='dernier orbite stable') 64 | D=500 65 | for dD in range(0,200,1): 66 | dD=dD/10 67 | phi_initial=math.atan(dD/D) #décalage de phi pour être sur une ligne perpendiculaire au trou noir 68 | phi,r=euler(math.sqrt(D**2+dD**2),math.atan(dD/D)*180/math.pi) #même décalage pour l'angle d'incidence 69 | ax.plot(phi,r) 70 | ax.legend(loc='best') 71 | plt.show() 72 | phi_initial=0 73 | 74 | #afficher les trajectoires parallèles entre elles 75 | plt.figure(num='light deviation 2') #plot differents figure according to a specific name 76 | ax = plt.subplot(111, projection='polar') #subplot permet de mettre plusieurs graph en 1 77 | plt.xlabel('phi(°)') #le processus d'affichage est détaillé dans la fonction affichage 78 | ax.set_ylabel('R(UA)\n\n\n', rotation=0) 79 | ax.set_title("Deviation of light close to a black hole\n", va='bottom') 80 | 81 | ax.set_rlabel_position(-90) 82 | ax.plot(phi_entier,black_hole, label='black hole') 83 | ax.plot(phi_entier,orbite_stable, label='dernier orbite stable') 84 | for D in range(10,50,5): 85 | phi,r=euler(D,20) 86 | ax.plot(phi,r) 87 | ax.legend(loc='best') 88 | 89 | #afficher les trajectoires pour une position donnée 90 | plt.figure(num='light deviation 3') #plot differents figure according to a specific name 91 | ax = plt.subplot(111, projection='polar') 92 | ax.set_rlabel_position(-90) 93 | ax.plot(phi_entier,black_hole, label='black hole') 94 | ax.plot(phi_entier,orbite_stable, label='dernier orbite stable') 95 | for alpha in range(0,80,10): 96 | phi,r=euler(10,alpha) 97 | ax.plot(phi,r) 98 | plt.show() 99 | 100 | #afficher qu'une seule trajectoire 101 | #---------------------------- 102 | def affichage(func,x,y): #permet de prendre comme argument n'importe quelle fonction ayant elle même deux arguments 103 | #plot differents figure according to a specific name 104 | phi,r=func(x,y) #appel la fonction au choix (ici euler) et associe les variables phi et r aux valeurs de sorties 105 | ax.plot(phi,r,label='trajectoire de la particule') #label donne un nom à la courbe 106 | ax.legend(loc='best') #permet de positionner la légende au mieux 107 | 108 | #D=float(input('distance au trou noir :')) #on pourrait insérer D et alpha dans affichage et mettre explicitement euler à la place de func mais, 109 | #alpha=float(input('angle de départ :')) #en les sortant, on peut réaliser plus facilement des boucles ou autre opérations utilisant la fonction affichage 110 | #affichage(euler,D,alpha) #permet d'appeler la fonction euler avec les valeurs choisis et d'afficher la trajectoire associée 111 | plt.figure(num='light deviation 4') 112 | ax = plt.subplot(111, projection='polar') #permet de projeter les coordonnées en polaire 113 | ax.set_title("Deviation of light close to a black hole\n", va='bottom') #donne un titre au graphique 114 | ax.set_rlabel_position(-90) # modifie l'angle d'affichage de l'axe radial 115 | plt.xlabel('phi(°)') #donne un nom aux axes 116 | ax.set_ylabel('R(UA)\n\n\n', rotation=0) 117 | ax.plot(phi_entier,black_hole, label='black hole') #permet de visualiser l'horizon du trou noir 118 | ax.plot(phi_entier,orbite_stable, label='dernier orbite stable') #permet de visualiser la zone dans laquelle les orbites ne sont plus stables 119 | 120 | affichage(euler,20,15) 121 | affichage(euler,3,90) 122 | 123 | plt.show() #affiche le ou les subplot en même temps 124 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.14 2 | matplotlib>=3.1 3 | scipy>=1.1 4 | pillow>=5.1 5 | --------------------------------------------------------------------------------