提交 2ff34fdc 编写于 作者: M march3

三体运行模拟器

上级 c54137c1
......@@ -55,40 +55,83 @@ class Body(metaclass=ABCMeta):
self.distance_scale = distance_scale
# 初始化后,加速度为0,只有多个天体的引力才会影响到加速度
# m/s²
# km/s²
self.__acceleration = np.array([0, 0, 0], dtype='float32')
self.__record_history()
@property
def has_rings(self):
"""
是否为带光环的天体(土星为 True)
:return:
"""
return False
@property
def is_fixed_star(self):
"""
是否为恒星(太阳为 True)
:return:
"""
return False
@property
def position(self):
"""
获取天体的位置(单位:km)
:return:
"""
return self.__position
@position.setter
def position(self, value):
"""
设置天体的位置(单位:km)
:param value:
:return:
"""
self.__position = value
self.__record_history()
@property
def acceleration(self):
"""
获取天体的加速度(单位:km/s²)
:return:
"""
return self.__acceleration
@acceleration.setter
def acceleration(self, value):
"""
设置天体的加速度(单位:km/s²)
:param value:
:return:
"""
self.__acceleration = value
self.__record_history()
@property
def velocity(self):
"""
获取天体的速度(单位:km/s)
:return:
"""
return self.__velocity
@velocity.setter
def velocity(self, value):
"""
设置天体的速度(单位:km/s)
:param value:
:return:
"""
self.__velocity = value
self.__record_history()
def __append_history(self, his_list, data):
"""
追加每个位置时刻的历史数据
:param his_list:
:param data:
:return:
......@@ -100,7 +143,7 @@ class Body(metaclass=ABCMeta):
def __record_history(self):
"""
记录历史
记录每个位置时刻的历史数据
:return:
"""
# 如果历史记录数超过了保留数量,则截断,只保留 __his_reserved_num 数量的历史
......@@ -139,7 +182,7 @@ class Body(metaclass=ABCMeta):
@property
def mass(self):
"""
天体质量 (kg)
天体质量 (单位:kg)
:return:
"""
return self.__mass
......@@ -147,7 +190,7 @@ class Body(metaclass=ABCMeta):
@property
def density(self):
"""
平均密度 (kg/m³)
平均密度 (单位:kg/m³)
:return:
"""
return self.__density
......@@ -155,7 +198,7 @@ class Body(metaclass=ABCMeta):
@property
def volume(self):
"""
天体的体积
天体的体积(单位:km³)
"""
# v = m/ρ
# 体积(m³) = 质量(kg) / 密度(kg/m³)
......@@ -166,7 +209,7 @@ class Body(metaclass=ABCMeta):
@property
def raduis(self):
"""
天体的半径
天体的半径(单位:km)
:return:
"""
# V = ⁴⁄₃πr³ -> r = pow((3V)/(4π),1/3)
......@@ -175,7 +218,7 @@ class Body(metaclass=ABCMeta):
@property
def diameter(self):
"""
天体的直径
天体的直径(单位:km)
:return:
"""
return self.raduis * 2
......@@ -186,33 +229,46 @@ class Body(metaclass=ABCMeta):
self.position[0], self.position[1], self.position[2], self.velocity)
def position_au(self):
"""
获取天体的位置(单位:天文单位 A.U.)
:return:
"""
pos = self.position
pos_au = pos / AU
return pos_au
def change_velocity(self, dv):
self.velocity += dv
def move(self, dt):
self.position += self.velocity * dt
# def change_velocity(self, dv):
# self.velocity += dv
#
# def move(self, dt):
# self.position += self.velocity * dt
def reset(self):
"""
重新设置初始速度和初始位置
:return:
"""
self.position = self.init_position
self.velocity = self.init_velocity
def kinetic_energy(self):
"""
计算动能(千焦耳)
表示动能,单位为焦耳j,m为质量,单位为千克,v为速度,单位为米/秒。
ek=(1/2).m.v^2
m(kg) v(m/s) -> j
m(kg) v(km/s) -> kj
"""
v = self.velocity
return 0.5 * self.mass * (v[0] ** 2 + v[1] ** 2 + v[2] ** 2)
# def kinetic_energy(self):
# """
# 计算动能(千焦耳)
# 表示动能,单位为焦耳j,m为质量,单位为千克,v为速度,单位为米/秒。
# ek=(1/2).m.v^2
# m(kg) v(m/s) -> j
# m(kg) v(km/s) -> kj
# """
# v = self.velocity
# return 0.5 * self.mass * (v[0] ** 2 + v[1] ** 2 + v[2] ** 2)
@staticmethod
def build_bodies_from_json(json_file):
"""
JSON文件转为天体对象
:param json_file:
:return:
"""
bodies = []
with open(json_file, "r") as read_content:
json_data = json.load(read_content)
......@@ -228,6 +284,6 @@ if __name__ == '__main__':
# build_bodies_from_json('../data/sun.json')
bodies = Body.build_bodies_from_json('../data/sun_earth.json')
# 太阳半径 / 地球半径
print(bodies[0].raduis / bodies[1].raduis)
print("太阳半径 / 地球半径 =", bodies[0].raduis / bodies[1].raduis)
for body in bodies:
print(body.kinetic_energy())
print(body)
......@@ -38,6 +38,14 @@ class Saturn(Body):
}
super().__init__(**params)
@property
def has_rings(self):
"""
土星带光环的天体
:return:
"""
return True
if __name__ == '__main__':
saturn = Saturn()
......
......@@ -34,6 +34,14 @@ class Sun(Body):
}
super().__init__(**params)
@property
def is_fixed_star(self):
"""
太阳为恒星
:return:
"""
return True
if __name__ == '__main__':
print(Sun())
# -*- coding:utf-8 -*-
# title :
# description :
# author :Python超人
# date :2023-01-22
# notes :
# python_version :3.8
# ==============================================================================
from bodies import Sun, Earth
from common.consts import SECONDS_PER_WEEK, SECONDS_PER_DAY
from scenes.func import mayavi_run, mpl_run
from bodies.body import Body, AU
if __name__ == '__main__':
"""
太阳、地球模拟运行
"""
# 构建两个天体对象(太阳、地球)
bodies = [
# 太阳的质量为 1.9891×10³⁰ kg
# 初始位置 x=0, y=0, z=0
# 初始速度 x=0, y=0, z=0
# 纹理图片路径为 texture/douyin.jpg
# 放大倍数为 120 倍
# 距离保持不变
Sun(name="太阳", mass=1.9891e30,
init_position=[0, 0, 0],
init_velocity=[0, 0, 0],
texture="douyin.jpg", size_scale=1.2e2, distance_scale=1.0),
# 地球的质量为 5.97237✕10²⁴ kg
# 初始位置 x=1.12天文单位, y=0, z=0
# 初始速度 x=0, y=29.7929.79 km/s, z=0
# 纹理图片路径为 texture/pythoncr.jpg
# 放大倍数为 5000 倍
# 距离保持不变
Earth(name="地球", mass=5.97237e24,
init_position=[1.12 * AU, 0, 0],
init_velocity=[0, 29.79, 0],
texture="pythoncr.jpg", size_scale=5e3, distance_scale=1.0),
]
# 使用 mayavi 查看的运行效果
mayavi_run(bodies, SECONDS_PER_DAY, view_azimuth=135)
# 使用 matplotlib 查看运行效果
# mpl_run(bodies, SECONDS_PER_WEEK)
......@@ -16,7 +16,7 @@ def mayavi_run(bodies, dt=SECONDS_PER_WEEK,
"""
用 mayavi 查看运行效果
:param bodies: 天体
:param dt: 按时间差进行演变,值越小越精确,但演变速度会慢。
:param dt: 单位:秒,按时间差进行演变,值越小越精确,但演变速度会慢。
:param view_azimuth: 观测方位角,可选,float类型(以度为单位,0-360),用x轴投影到x-y平面上的球体上的位置矢量所对的角度。
:param view_distance: 观测距离,可选,float类型 or 'auto',一个正浮点数,表示距放置相机的焦点的距离。
:param view_focalpoint: 观测焦点,可选,类型为一个由3个浮点数组成的数组 or 'auto',,代表观测相机的焦点
......@@ -49,3 +49,29 @@ def mayavi_run(bodies, dt=SECONDS_PER_WEEK,
mlab.view(azimuth=view_azimuth, distance=view_distance, focalpoint=view_focalpoint)
# mlab.view(azimuth=-45, elevation=45, distance=100e8 * 2 * 2 * 4 * 4, focalpoint=[5e10, 5e10, 5e9])
mlab.show()
def mpl_run(bodies, dt=SECONDS_PER_WEEK):
"""
:param bodies: 天体
:param dt: 单位:秒,按时间差进行演变,值越小越精确,但演变速度会慢。
:return:
"""
from simulators.mpl_simulator import MplSimulator
body_sys = System(bodies)
simulator = MplSimulator(body_sys)
simulator.run(dt)
if __name__ == '__main__':
from bodies import Sun, Earth
"""
太阳、地球
"""
bodies = [
Sun(size_scale=1.2e2), # 太阳放大 120 倍
Earth(size_scale=4e3, distance_scale=1), # 地球放大 4000 倍,距离保持不变
]
mpl_run(bodies, SECONDS_PER_WEEK)
# -*- coding:utf-8 -*-
# title :
# description :
# author :Python超人
# date :2023-01-22
# notes :
# python_version :3.8
# ==============================================================================
from bodies import Sun, Earth
from common.consts import SECONDS_PER_WEEK
from scenes.func import mayavi_run
if __name__ == '__main__':
"""
太阳、地球
"""
bodies = [
Sun(size_scale=1.2e2, texture='douyin.jpg'), # 太阳放大 120 倍
Earth(size_scale=4e3, texture='pythoncr.jpg', distance_scale=1), # 地球放大 4000 倍,距离保持不变
]
mayavi_run(bodies, SECONDS_PER_WEEK, view_azimuth=-45)
......@@ -9,12 +9,15 @@
from mayavi import mlab
from simulators.simulator import Simulator
from common.system import System
from simulators.viewers.mayavi_viewer import MayaviViewer
from simulators.viewers.mayavi_view import MayaviView
class MayaviSimulator(Simulator):
"""
Mayavi天体运行模拟器
"""
def __init__(self, bodies_sys: System):
super().__init__(bodies_sys, MayaviViewer)
super().__init__(bodies_sys, MayaviView)
@mlab.animate(delay=100, ui=True)
def run(self, dt):
......
# -*- coding:utf-8 -*-
# title :
# description :
# author :Python超人
# date :2023-01-22
# notes :
# python_version :3.8
# ==============================================================================
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from simulators.simulator import Simulator
from common.system import System
from simulators.viewers.mpl_view import MplView
import numpy as np
import time
plt.rcParams['font.sans-serif'] = ['SimHei'] # 步骤一(替换默认sans-serif字体)
plt.rcParams['axes.unicode_minus'] = False # 步骤二(解决坐标轴负数的负号显示问题)
X_MIN, X_MAX = -4e+8, 4e+8 # the x range of the bounding box (m)
Y_MIN, Y_MAX = -4e+8, 4e+8 # the y range of the bounding box (m)
Z_MIN, Z_MAX = -4e+8, 4e+8 # the z range of the bounding box (m)
class MplSimulator(Simulator):
"""
matplotlib天体运行模拟器
"""
def __init__(self, bodies_sys: System):
super().__init__(bodies_sys, MplView)
def run(self, dt):
"""
:param dt:
:return:
"""
MAX_FRAME = 20
views_frames = []
for i in range(MAX_FRAME):
self.evolve(dt)
body_views = self.body_views
self.show_figure(body_views)
time.sleep(0.5)
views_frames.append(body_views.copy())
# TODO: views_frames 用于 gif 动画
# fig = plt.figure()
#
# def update(num):
# body_viewers = viewers_frames[num]
# print(body_viewers)
#
# return self.show_figure(plt, body_viewers)
#
# ani = animation.FuncAnimation(fig=fig, func=update, frames=np.arange(0, MAX_FRAME), interval=1)
# ani.save('bodies_run.gif')
# plt.show()
def show_figure(self, bodies):
fig = plt.figure(figsize=(16, 12))
ax = plt.axes(projection="3d")
ax.set_xlim(X_MIN, X_MAX)
ax.set_ylim(Y_MIN, Y_MAX)
ax.set_zlim(Z_MIN, Z_MAX)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
for idx, body in enumerate(bodies):
if body.is_fixed_star:
color = 'red'
else:
color = 'blue'
# size = 800 if str(body.name).lower().startswith("sun") else 500
size = body.raduis / 80000
pos = body.position
ax.text(pos[0], pos[1], pos[2] + 1e8, body.name, color=color, fontsize=20)
ax.scatter(pos[0], pos[1], pos[2], color=color, s=size)
# for _his_pos in body.his_position():
# ax.scatter3D(_his_pos[0], _his_pos[1], _his_pos[2], color=color, alpha=0.5)
# ax.scatter(his_pos[0], his_pos[1], his_pos[2], color=colors[idx], s=10)
his_pos = body.his_position
if len(his_pos) > 1:
_his_pos = list(zip(*his_pos))
ax.plot3D(_his_pos[0], _his_pos[1], _his_pos[2], color=color, alpha=0.8)
plt.title("天体运行")
# display the plot
plt.show()
# return ax
if __name__ == '__main__':
from scenes.func import mpl_run
from bodies import Sun, Earth
from common.consts import SECONDS_PER_WEEK
"""
太阳、地球
"""
bodies = [
Sun(size_scale=1.2e2), # 太阳放大 120 倍
Earth(size_scale=4e3, distance_scale=1), # 地球放大 4000 倍,距离保持不变
]
mpl_run(bodies, SECONDS_PER_WEEK)
......@@ -12,7 +12,7 @@ from common.system import System
class Simulator(metaclass=ABCMeta):
"""
天体运行模拟器
"""
def __init__(self, bodies_sys: System, viewer_type: type):
......@@ -21,30 +21,41 @@ class Simulator(metaclass=ABCMeta):
:param bodies_sys: 天体系统
:param viewer_type: BodyViewer类型
"""
self.body_viewers = []
self.body_views = []
self.bodies_sys = bodies_sys
self.init_viewers(viewer_type)
self.init_views(viewer_type)
def init_viewers(self, viewer_type: type):
def init_views(self, viewer_type: type):
"""
:param viewer_type: BodyViewer类型
:return:
"""
for body in self.bodies_sys.bodies:
viewer = viewer_type(body)
self.body_viewers.append(viewer)
view = viewer_type(body)
self.body_views.append(view)
def evolve(self, dt: int):
"""
按时间差进行演变,值越小越精确,但演变速度会慢。
单位:秒,按时间差进行演变,值越小越精确,但演变速度会慢。
:param dt: 时间差(秒)
:return:
"""
self.bodies_sys.evolve(dt)
for idx, viewer in enumerate(self.body_viewers):
viewer.position = self.bodies_sys.bodies[idx].position * self.bodies_sys.bodies[idx].distance_scale
viewer.update()
for idx, view in enumerate(self.body_views):
body = self.bodies_sys.bodies[idx]
view.position = body.position * body.distance_scale
view.name = body.name
view.mass = body.mass
view.acceleration = body.acceleration
view.velocity = body.velocity
# viewer.volume = body.volume
view.raduis = body.raduis * body.size_scale
view.his_position = body.his_position()
view.is_fixed_star = body.is_fixed_star
view.has_rings = body.has_rings
view.update()
@abstractmethod
def run(self, dt: int):
......
......@@ -13,7 +13,11 @@ import numpy as np
import os
class BodyViewer(metaclass=ABCMeta):
class BodyView(metaclass=ABCMeta):
"""
天体视图(天体效果展示用)
"""
def __init__(self, body: Body):
self.body = body
self.sphere = None
......@@ -24,13 +28,22 @@ class BodyViewer(metaclass=ABCMeta):
if self.texture is None:
self.color = tuple(np.array(body.color) / 255)
else:
self.color = self.__texture_to_color(self.texture)
self.sphere = self.build()
self.position = None
self.color = self.__get_texture_main_color(self.texture)
self.sphere = self.appear()
self.position = [None, None, None]
self.name = None
self.mass = None
self.raduis = None
self.velocity = None
def __repr__(self):
return '<%s> m=%.3e(kg), r=%.3e(km), p=[%.3e,%.3e,%.3e](km), v=%s(km/s)' % \
(self.name, self.mass, self.raduis,
self.position[0], self.position[1], self.position[2], self.velocity)
def __find_texture(self, texture):
"""
在多路径下寻找纹理图片
尝试在多个路径下寻找纹理图片
:param texture: 纹理图片
:return: 纹理图片的路径
"""
......@@ -42,9 +55,9 @@ class BodyViewer(metaclass=ABCMeta):
return None
def __texture_to_color(self, texture):
def __get_texture_main_color(self, texture):
"""
根据纹理图片获取颜色
获取纹理图片的主要颜色
:param texture:
:return:
"""
......@@ -55,8 +68,23 @@ class BodyViewer(metaclass=ABCMeta):
@abstractmethod
def update(self):
"""
更新天体信息和数据,比如:更新天体的位置
:return:
"""
pass
def disappear(self):
"""
天体消失的操作,比如:销毁天体视图对象
:return:
"""
pass
@abstractmethod
def build(self):
def appear(self):
"""
天体显示的操作,比如:构建天体视图对象
:return:
"""
pass
......@@ -12,11 +12,14 @@ import os
import matplotlib.pyplot as plt
from common.func import get_dominant_colors
from simulators.viewers.body_viewer import BodyViewer
from simulators.viewers.body_view import BodyView
import numpy as np
class MayaviViewer(BodyViewer):
class MayaviView(BodyView):
"""
Mayavi天体视图(天体效果展示用)
"""
def update(self):
"""
......@@ -30,32 +33,32 @@ class MayaviViewer(BodyViewer):
self.sphere.mlab_source.set(x=self.position[0], y=self.position[1], z=self.position[2])
return x_offset[0], y_offset[0], z_offset[0]
def __find_texture(self, texture):
"""
在多路径下寻找纹理图片
:param texture: 纹理图片
:return: 纹理图片的路径
"""
paths = ['./textures', '../textures']
for path in paths:
p = path + "/" + texture
if os.path.exists(p):
return p
return None
def __texture_to_color(self, texture):
"""
根据纹理图片获取颜色
:param texture:
:return:
"""
colors = get_dominant_colors(texture)
first_color = colors[0]
# print(self.name, first_color)
return tuple(np.array(first_color) / 255)
# def __find_texture(self, texture):
# """
# 在多路径下寻找纹理图片
# :param texture: 纹理图片
# :return: 纹理图片的路径
# """
# paths = ['./textures', '../textures']
# for path in paths:
# p = path + "/" + texture
# if os.path.exists(p):
# return p
#
# return None
#
# def __texture_to_color(self, texture):
# """
# 根据纹理图片获取颜色
# :param texture:
# :return:
# """
# colors = get_dominant_colors(texture)
# first_color = colors[0]
# # print(self.name, first_color)
# return tuple(np.array(first_color) / 255)
def build(self):
def appear(self):
"""
构建球体对象
:return:
......
......@@ -6,4 +6,24 @@
# notes :
# python_version :3.8
# ==============================================================================
import os
import matplotlib.pyplot as plt
from common.func import get_dominant_colors
from simulators.viewers.body_view import BodyView
import numpy as np
class MplView(BodyView):
"""
matplotlib天体视图(天体效果展示用)
"""
def update(self):
pass
def appear(self):
pass
def disappear(self):
pass
\ No newline at end of file
textures/douyin.jpg

72.7 KB | W: | H:

textures/douyin.jpg

28.4 KB | W: | H:

textures/douyin.jpg
textures/douyin.jpg
textures/douyin.jpg
textures/douyin.jpg
  • 2-up
  • Swipe
  • Onion skin
textures/pythoncr.jpg

141.9 KB | W: | H:

textures/pythoncr.jpg

52.8 KB | W: | H:

textures/pythoncr.jpg
textures/pythoncr.jpg
textures/pythoncr.jpg
textures/pythoncr.jpg
  • 2-up
  • Swipe
  • Onion skin
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册