ThreeBodyProblem 源代码

# 我是一些注释!!!
import numpy as np
import pyqtgraph as pg
import pyqtgraph.opengl as gl
from pyqtgraph.Qt import QtCore 
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QIcon
import csv


DATA=np.zeros((19))
[文档]def remove_i(x, i): '''计算加速度要去除自身所在位置影响 参数: - x:位置矩阵x - i:第i个质点''' shape = (x.shape[0]-1,) + x.shape[1:] y = np.empty(shape, dtype=float) y[:i] = x[:i] y[i:] = x[i+1:] return y
[文档]def a(i, x, G, m): '''计算第i个质量点在位置矩阵X,引力常数G和质量为m的条件下的加速度 参数: - i:第几个质点 - x:位置矩阵X - G:引力常数G - m:质量矩阵 返回值: - result:第i个质量点的加速度 ''' x_i = x[i] x_j = remove_i(x, i) m_j = remove_i(m, i) diff = x_j - x_i # if abs(np.min(diff))<1e-6: # raise Exception('精度失败!'+str(diff)) mag3 = np.sum(diff**2, axis=1)**1.5 result = G * np.sum(diff * (m_j / mag3)[:,np.newaxis], axis=0) return result
[文档]def timestep(x0, v0, G, m, dt): '''给定瞬时条件和时间间隔,为所有质量点计算下一瞬间位置和速度(标准离散时间运动) 参数: - x0:初始位置 - v0:初始速度 - G:引力常数 - m:质量 - dt:时间微分(微分越小结果越精确,但会降低程序的运行速度) 返回值: - x1:在一个dt后各点的末位置 - v1:在一个dt后各点的末速度 ''' N = len(x0) x1 = np.empty(x0.shape, dtype=float) v1 = np.empty(v0.shape, dtype=float) for i in range(N): a_i0 = a(i, x0, G, m) # print('a%d='%(i+1)+str(a_i0)) v1[i] = a_i0 * dt + v0[i] x1[i] = a_i0 * dt**2 + v0[i] * dt + x0[i] return x1, v1
[文档]def timestep2(x0, v0, G, m, dt): '''给定瞬时条件和时间间隔,为所有质量点计算下一瞬间位置和速度(直线匀速运动) 参数: - x0:初始位置 - v0:初始速度 - G:引力常数 - m:质量 - dt:时间微分(微分越小结果越精确,但会降低程序的运行速度) 返回值: - x1:在一个dt后各点的末位置 - v1:在一个dt后各点的末速度''' N = len(x0) x1 = np.empty(x0.shape, dtype=float) v1 = np.empty(v0.shape, dtype=float) for i in range(N): v1[i] = [0,0,0] x1[i] = x0[i]+0.1 return x1, v1
[文档]def initial_cond(N=3, D=3): '''在空间中为N个质量点产生随机条件 参数: - N:质量点个数,三体运动设为3 - D:空间维度,三维空间设为3 ''' global DATA x0 = np.random.rand(N, D)*10 v0 = np.zeros((N, D), dtype=float) m = np.ones(N, dtype=float) DATA=np.asarray( np.concatenate( ([0], x0.flatten(), v0.flatten(),) ) ) # print(DATA) return x0, v0, m
[文档]def simulate(N, D, S, G, dt): '''数值模拟函数''' x0, v0, m = initial_cond(N, D) for s in range(S): x1, v1 = timestep(x0, v0, G, m, dt) x0, v0 = x1, v1
[文档]class myTable(QTableWidget): '''自定义表格''' pro='constant table'
[文档]class MainPrograme(): def __init__(self): '''初始化函数,主要作用为声明各个组件''' app = pg.mkQApp("三体问题") self.win=QMainWindow() self.cw=QWidget() self.layout=QGridLayout() self.w = gl.GLViewWidget() self.axs=gl.GLAxisItem() # print(self.axs.childItems()) self.axs.setSize(10,10,10) self.axs.translate(-30,-30,0) self.line1=gl.GLLinePlotItem(width=1,antialias=True,color=pg.mkColor((51,1.4))) self.line2=gl.GLLinePlotItem(width=1,antialias=True,color=pg.mkColor((51,1.4))) self.line3=gl.GLLinePlotItem(width=1,antialias=True,color=pg.mkColor((51,1.4))) self.linedraw(False) self.w.addItem(self.line1) self.w.addItem(self.line2) self.w.addItem(self.line3) self.t =QtCore.QTimer() self.timing=0 self.btn_stop=QPushButton('暂停/运动') self.btn_stop.clicked.connect(self.animate_start_stop) self.btn_table=QPushButton('设置参数') self.btn_table.clicked.connect(self.parametersettings) self.c=QRadioButton('是否绘制轨迹') self.c.toggled.connect(self.linedraw) self.slider=QSlider(orientation=Qt.Orientation.Horizontal) self.label_for_slider=QLabel(text='speed='+self.slider.value().__str__()) self.Nbody_lable=QLabel(text='设置天体形状大小') self.Nbodies=QSpinBox() self.Nbodies.valueChanged.connect(self.change_size) # 坐标网格 self.g = gl.GLGridItem() self.ini_camera=QPushButton('摄像机归位') self.ini_camera.clicked.connect(self.ini) # self.poses = np.random.random(size=(2,3)) # self.poses[0] = (0,0,0) self.points_color = np.tile(np.asarray([0,255,255,255]),(3,1)) self.x0, self.v0, self.m = initial_cond(3, 3) # self.varibles self.dt=0.01 self.G=1 self.x1, self.v1 = timestep(self.x0, self.v0, self.G, self.m, self.dt) # 控制散点大小 # size = np.random.random(size=pos.shape[0])*10 self.point_size=10 self.sp2 = gl.GLScatterPlotItem(pos=self.x0, color=self.points_color, size=self.point_size)
[文档] def change_size(self): '''改变散点外观大小''' self.sp2.size=self.Nbodies.value() self.update()
[文档] def ini(self): '''初始化摄像机位置''' self.w.reset() self.w.setCameraPosition(distance=100)
[文档] def load(self): '''加载各个组件,分配空间布局并为组件绑定事件监听函数''' self.win.resize(880,600) self.win.show() self.win.setWindowTitle('三体问题数值计算及动画模拟') self.win.setWindowIcon(QIcon('icon.jpeg')) self.win.setCentralWidget(self.cw) self.layout.setSpacing(20) self.cw.setLayout(self.layout) leftLayout=QVBoxLayout() self.layout.addWidget(self.w,0,1,6,3) self.layout.addLayout(leftLayout,1,0) leftLayout.setSpacing(40) leftLayout.addWidget(self.btn_stop) leftLayout.addWidget(self.c) leftLayout.addWidget(self.Nbodies) leftLayout.addWidget(self.btn_table) leftLayout.addWidget(self.Nbody_lable) leftLayout.addWidget(self.ini_camera) speed_layout=QVBoxLayout() speed_layout.setSpacing(10) speed_layout.addWidget(self.label_for_slider,alignment=Qt.AlignmentFlag.AlignCenter) speed_layout.addWidget(self.slider) leftLayout.addLayout(speed_layout) self.g.scale(4,4,1) self.Nbody_lable.setMaximumSize(130,15) self.w.addItem(self.g) self.w.addItem(self.sp2) self.w.setCameraPosition(distance=50) self.w.addItem(self.axs) self.Nbodies.setMinimum(1) self.Nbodies.setMaximum(10) self.slider.setMaximumSize(150,40) self.slider.setTickPosition(QSlider.TickPosition.TicksBelow) self.slider.setTickInterval(100) self.slider.setMinimum(10) self.slider.setMaximum(1000) self.slider.valueChanged.connect(self.changeSpeed) self.t.timeout.connect(self.update) self.statusbar=self.win.statusBar() self.statusbar.showMessage('程序正在运行!') qact_exit=QAction(text='exit') qact_exit.setShortcut('ctrl+q') qact_exit.setStatusTip('退出') qact_exit.triggered.connect(qApp.quit) qact_open=QAction(text='open') qact_open.setShortcut('ctrl+n') qact_open.setStatusTip('打开csv文件') qact_open.triggered.connect(self.filedialog_open) qact_save=QAction(text='save') qact_save.setShortcut('ctrl+s') qact_save.setStatusTip('保存csv文件') qact_save.triggered.connect(self.filedialog_save) qact_c1=QAction(text='调整一号天体颜色') qact_c1.setStatusTip('打开调色板') qact_c1.triggered.connect(self.colordiallog) qact_c2=QAction(text='调整二号天体颜色') qact_c2.setStatusTip('打开调色板') qact_c2.triggered.connect(self.colordiallog) qact_c3=QAction(text='调整三号天体颜色') qact_c3.setStatusTip('打开调色板') qact_c3.triggered.connect(self.colordiallog) menu=self.win.menuBar() filemenu=menu.addMenu('文件') filemenu.addAction(qact_exit) filemenu.addAction(qact_open) filemenu.addAction(qact_save) look=menu.addMenu('视图') look.addAction(qact_c1) look.addAction(qact_c2) look.addAction(qact_c3) helpact=QAction(text='帮助') helpact.setStatusTip('如何使用...') helpact.triggered.connect(self.helper) authinfo=QAction(text='关于我') authinfo.setStatusTip('作者信息') authinfo.triggered.connect(self.info) more=menu.addMenu('更多') more.addAction(helpact) more.addAction(authinfo) self.x1, self.v1 = timestep(self.x0, self.v0, self.G, self.m, self.dt) pg.exec()
[文档] def info(self): '''弹出作者信息对话框''' reply = QMessageBox.information(self.win, '作者信息','计科二班闫泽轩,个人网页home.ustc.edu.cn/~yzx_ustc',QMessageBox.Yes | QMessageBox.No,QMessageBox.Yes)
[文档] def helper(self): '''弹出提示信息框''' reply = QMessageBox.information(self.win, '帮助', "ctrl+s可以保存当前状态信息成csv格式\n\ ctrl+n可以打开以csv格式保存的数据信息,详情参考我的个人网页中的样例\n\ 设置参数后将丢失调参前信息,即使没有进行修改,请及时保存!\n\ ctrl+q退出程序,不要手贱哦~", QMessageBox.Yes | QMessageBox.No,QMessageBox.Yes)
[文档] def linedraw(self,checked): '''响应单选框是否绘制轨迹的事件请求 参数: checked:单选框是否被选中''' # print('hahaha'+str(checked)) if checked: self.line1.show() self.line2.show() self.line3.show() else: self.line1.hide() self.line2.hide() self.line3.hide()
[文档] def myfmt(self,item): '''保证参数设置表格数据符合格式要求,并更新至DATA之中 参数: item:发生改变的表格元素,若数据不符合格式将被置0''' global DATA #DATA已保证初始化,仅有一维数组 sender=self.win.sender() # print(item.column()) if sender.pro=='const': if isinstance(item,QTableWidgetItem): try: var=np.float64(item.text()) item.setText(format(var,'.6f')) except ValueError: var=0 item.setText('0') finally: if item.column()==0: # 引力常数 self.G=var pass elif item.column()==1: # 时间微分 self.dt=var pass else: # 质量设置 self.m[item.column()-2]=var pass else: j=item.column() i=item.row() if isinstance(item,QTableWidgetItem): try: var=np.float64(item.text()) item.setText(format(var,'.6f')) except ValueError: var=0 item.setText('0') finally: DATA[3*j+i+1]=var self.x0=DATA[1:10].reshape(3,3) self.v0=DATA[10:19].reshape(3,3) pass # print(DATA) pass
[文档] def parametersettings(self): '''弹出参数设置表格对话框,执行此函数将清空原有DATA中的数据信息!''' dialog=QDialog() global DATA self.t.stop() dialog.setGeometry(self.win.geometry().left()+100,self.win.geometry().top()+100,400,300) dialog.setFixedSize(700,300) dialog.setWindowFlags( Qt.WindowCloseButtonHint) dialog.setWindowTitle('参数设置') dialog.setWindowModality(Qt.ApplicationModal) vt=myTable(1,5) vt.pro='const' l=[self.G,self.dt,self.m[0],self.m[1],self.m[2]] for i in range(5): itemContent=l[i].__str__() item=QTableWidgetItem(itemContent) #为每个表格内添加数据 vt.setItem(0,i,item) vt.itemChanged.connect(self.myfmt) vt.setHorizontalHeaderLabels(['引力常数','时间微分','质量1','质量2','质量3']) mt = myTable(3,6) mt.pro='varib' mt.itemChanged.connect(self.myfmt) mt.setVerticalHeaderLabels(['x','y','z']) mt.setHorizontalHeaderLabels(['x1','x2','x3','v1','v2','v3']) if(DATA.shape[0]!=1): DATA=np.asarray( np.concatenate( ([0], self.x0.flatten(), self.v0.flatten(), ) ) ) # print('重新初始化') # DATA=DATA[-1] # print(DATA) # print(DATA[1:].reshape(6,3)) dts=DATA[1:].reshape(6,3) for i in range(3): for j in range(6): itemContent=dts[j][i].__str__() #为每个表格内添加数据 mt.setItem(i,j,QTableWidgetItem(itemContent)) lay=QGridLayout(dialog) lay.addWidget(vt,0,0,5,1) lay.addWidget(mt) dialog.exec()
[文档] def changeSpeed(self,value): '''改变图像刷新速度(注意不是时间微分dt!为获得更精确的结果请在参数设置表格中手动更改dt) 参数: value:刷新速率''' self.statusbar.showMessage(str(self.t.interval())) self.label_for_slider.setText('speed='+str(value)) self.t.setInterval((int)(1000-value))
[文档] def update(self): '''更新散点位置及轨迹''' global DATA # color = np.ones((self.poses.shape[0],4), dtype=np.ubyte) # self.poses=np.random.random(size=(self.Nbodies.value(),3))*10 self.sp2.setData(pos=self.x1) self.x1, self.v1 = timestep(self.x0, self.v0, self.G, self.m, self.dt) self.timing+=self.dt DATA=np.vstack( (DATA, np.concatenate( ( [self.timing], self.x1.flatten(), self.v1.flatten(),) ) ) ) self.line1.setData(pos=DATA[...,1:4]) self.line2.setData(pos=DATA[...,4:7]) self.line3.setData(pos=DATA[...,7:10]) # print(DATA[...,1:4]) self.x0, self.v0 = self.x1, self.v1
# print('x0 ='+str(self.x0)) # print('v0 ='+str(self.v0))
[文档] def animate_start_stop(self): '''控制绘图的暂停和继续''' if self.t.isActive(): self.t.stop() else: self.t.start(1000-self.slider.value())
[文档] def colordiallog(self): '''弹出颜色选择框,为散点及其轨迹设置颜色''' sender=self.win.sender() # print(str(sender.text())) color=QColorDialog.getColor() id=1 if sender.text()=='调整一号天体颜色': id=1 self.line1.color=color elif sender.text()=='调整二号天体颜色': id=2 self.line2.color=color elif sender.text()=='调整三号天体颜色': id=3 self.line3.color=color if color.isValid(): self.points_color[id-1]=np.asarray(color.getRgbF()) self.sp2.color=self.points_color self.statusbar.showMessage(str(id)+"选择了"+color.getRgb().__str__())
[文档] def filedialog_open(self): '''弹出文件对话框,打开已有的csv格式文件,从中读取已有轨道信息!''' filename = QFileDialog.getOpenFileName(self.win, "打开", "./",filter="csv(*.csv);;")[0] if filename: global DATA with open(filename,encoding='utf-8') as f: dts=np.loadtxt(f,delimiter=',',skiprows=1,max_rows=1,usecols=[0,1,2,3,4]) # print(dts) DATA = np.loadtxt(f,delimiter = ",", skiprows = 1)#usecols=(i1,i2,i3...) # print(DATA) self.G=np.float64(dts[0]) self.dt=np.float64(dts[1]) self.m=np.float64(dts[2:]) self.x0=DATA[-1,1:10].reshape(3,3) self.v0=DATA[-1,10:19].reshape(3,3) self.update()
# print('文件数据读取完成!')
[文档] def filedialog_save(self): '''弹出保存文件对话框,将现有DATA中的数据信息,以及各质点的质量,时间微分和引力常数信息保存至指定文件''' fileName2, ok2 = QFileDialog.getSaveFileName(self.win,"文件保存","./","csv (*.csv)") with open(fileName2,'w',encoding='utf-8',newline='') as f: wr=csv.writer(f) wr.writerow(['gravity constant G','differential time dt','Mass m1','Mass m2','Mass m3']) wr.writerow([self.G,self.dt,self.m[0],self.m[1],self.m[2]]) wr.writerow(['time','X1_x','X1_y','X1_z','X2_x','X2_y','X2_z','X3_x','X3_y','X3_z','V1_x','V1_y','V1_z','V2_x','V2_y','V2_z','V3_x','V3_y','V3_z']) for i in range(0,DATA.shape[0]): wr.writerow(DATA[i])
if __name__ == '__main__': '''执行主函数,声明并加载程序''' sim=MainPrograme() sim.inteval=50 sim.load()