太阳系模拟MatplotlibPython(太阳系向量您可以物体行星)「在线太阳系模型」

编程的一个用途是通过模拟来帮助我们理解真实世界。
这一技术被应用于科学、金融和许多其他定量领域。
只要控制现实世界属性的“规则”是已知的,你就可以编写一个计算机程序来探索你遵循这些规则所得到的结果。
在本文中,您将用Python模拟三维太阳系使用流行的可视化库Matplotlib在这篇文章,你将能够用Python创建你自己的3D太阳系,你可以用你想要的多少太阳和行星。
下面是一个简单的太阳系的一个例子,它有一个太阳和两个行星:你还可以打开动画地板上的二维投影,更好地展示太阳系的三维本质。
下面是同样的太阳系模拟,包括2D投影:这篇文章的大纲下面是这篇文章的概要,以便您知道接下来会发生什么:浅谈两个物体之间的引力你需要用它来模拟Python中的三维太阳系。
简介三维矢量 .太阳系和轨道天体类别的定义在里面,比如太阳和行星。
你将用一步一步的方法编写这些类,并用一个简单的太阳系来测试它们。
增加选项显示2D投影轨道物体的三维模拟。
这个2D投影有助于在3D中可视化运动。
创建一个双星系统 .在本文中,您将使用面向对象的编程和Matplotlib。
如果您希望阅读更多关于任何一个主题的内容,您可以阅读:面向对象编程使用Matplotlib的Python数据可视化基础让我们从使用Matplotlib在Python中模拟一个3D太阳系开始。
让我们谈谈地心引力太阳系中的太阳、行星和其他天体都是运动中的天体,它们相互吸引。
引力在任何两个物体之间施加。
如果这两个对象有大量M_1和M_2是距离r然后,你可以用以下公式计算它们之间的引力:F=G\frac{m_1m_2}{r^2}常数G是一个引力常数。
您将看到如何在模拟的版本中忽略这个常量,在本文中,您将使用任意单位的质量和距离,而不是kg和m。
一旦你知道了两个物体之间的引力,你就可以计算出加速度。
a每个物体都是由于这种引力而经历的,使用以下公式:F=ma使用这个加速度,你可以调整运动物体的速度。
当速度发生变化时,速度和方向都会发生变化。
用三维表示点和向量当用Python模拟一个三维太阳系时,你需要用三维空间来表示太阳系。
因此,这个3D空间中的每个点都可以用三个数字来表示,x -, y-和z-坐标。
例如,如果你想把太阳放在太阳系的中心,你可以将太阳的位置表示为(0, 0, 0) .您还需要在3D空间中表示向量。
矢量具有大小和方向。
你需要像速度、加速度和力这样的量的矢量,因为这些量都有一个方向和一个震级。
在本文中,我将不详细讨论向量代数。
相反,我将陈述您需要的任何结果。
你可以读到更多关于向量与向量代数如果你愿意的话。
为了在代码中更容易地处理向量,您可以创建一个类来处理它们。
编写这个类将作为对类和面向对象编程的快速刷新。
你可以读到用Python进行面向对象的编程如果你觉得你需要一个更彻底的解释。
虽然您也可以创建一个类来处理3D空间中的点,但这并不是必要的,在本文中我也不会创建一个类。
创建Vector类(又名复习班)如果您熟悉向量和面向对象编程,可以跳过本节,只需在定义Vector班级。
创建一个名为vectors.py中,您将定义Vector班级。
您将使用此脚本定义类并对其进行测试。
然后,可以删除最后的测试代码,只需在这个脚本中保留类定义:# vectors.pyclass Vector: def __init__(self, x=0, y=0, z=0): self.x = x self.y = y self.z = z def __repr__(self): return f"Vector({self.x}, {self.y}, {self.z})" def __str__(self): return f"{self.x}i + {self.y}j + {self.z}k"# Testing Vector Class - TO BE DELETEDtest = Vector(3, 5, 9)print(test)print(repr(test))test = Vector(2, 2)print(test)print(repr(test))test = Vector(y=5, z=3)print(test)print(repr(test))这个__init__()方法的Vector类有三个参数,表示每个轴上的值。
每个参数的默认值为0表示该轴的原点。
虽然我们不喜欢在Python中使用单个字母名称,x , y,和z是恰当的,因为它们代表了数学中常用的笛卡尔坐标系的术语。
您还定义了两个Dunder方法来将对象表示为一个字符串:__repr__()返回用于显示类名的程序员的输出。
输出__repr__()可用于重新创建对象。
__str__()返回对象的字符串表示形式的非程序员版本。
在这种情况下,它返回数学中常用的表示来表示向量,使用单位向量i , j,和k .在代码段的末尾,您可以更多地了解这两种类型的字符串表示之间的差异。
Python编码书第9章 .测试代码块的输出如下:3i + 5j + 9kVector(3, 5, 9)2i + 2j + 0kVector(2, 2, 0)0i + 5j + 3kVector(0, 5, 3)使.Vector类索引在Python项目中的这个3D太阳系中,如果Vector类是可索引的,以便您可以使用[]带有索引以提取其中一个值的符号。
使用当前形式的类,如果添加print(test[0])在您的脚本中,您将得到一个TypeError说Vector对象不可订阅。
您可以通过向类定义中添加另一个Dud方法来修复这个问题:# vectors.pyclass Vector: def __init__(self, x=0, y=0, z=0): self.x = x self.y = y self.z = z def __repr__(self): return f"Vector({self.x}, {self.y}, {self.z})" def __str__(self): return f"{self.x}i + {self.y}j + {self.z}k" def __getitem__(self, item): if item == 0: return self.x elif item == 1: return self.y elif item == 2: return self.z else: raise IndexError("There are only three elements in the vector")# Testing Vector Class - TO BE DELETEDtest = Vector(3, 5, 9)print(test[0])通过定义__getitem__(),你做了Vector可索引的类。
向量中的第一项是x的价值。
y的价值。
z
任何其他索引都会引发错误。
测试代码块的输出如下:test[0]返回向量中的第一个项,x .中定义加法和减法。
Vector班级可以定义类的对象的加法和减法。
__add__()和__sub__()DunderMethod.这些方法将使您能够使用+和-执行这些操作的符号。
如果没有这些Dud方法,则使用+和-提出TypeError .若要添加或减去两个向量,可以分别添加或减去向量的每个元素:# vectors.pyclass Vector: def __init__(self, x=0, y=0, z=0): self.x = x self.y = y self.z = z def __repr__(self): return f"Vector({self.x}, {self.y}, {self.z})" def __str__(self): return f"{self.x}i + {self.y}j + {self.z}k" def __getitem__(self, item): if item == 0: return self.x elif item == 1: return self.y elif item == 2: return self.z else: raise IndexError("There are only three elements in the vector") def __add__(self, other): return Vector( self.x + other.x, self.y + other.y, self.z + other.z, ) def __sub__(self, other): return Vector( self.x - other.x, self.y - other.y, self.z - other.z, )# Testing Vector Class - TO BE DELETEDtest = Vector(3, 5, 9) + Vector(1, -3, 2)print(test)test = Vector(3, 5, 9) - Vector(1, -3, 2)print(test)双管齐下__add__()和__sub__()返回另一个Vector对象,每个元素等于两个原始向量中相应元素的加减。
输出如下:4i + 2j + 11k2i + 8j + 7k对于乘法和除法,您也可以这样做,尽管在处理向量时,这些操作需要更多的注意。
中定义标量乘法、点积和标量除法。
Vector班级在处理向量时,不能仅仅引用“乘法”,因为有不同类型的“乘法”。
在这个项目中,你只需要标量乘法。
标量乘法是指向量与标量相乘(标量有一个数量级,但没有方向)。
但是,在本小节中,您还将定义点积两个向量。
你想用运算符,既适用于标量乘法,也适用于点积。
因此,可以定义__mul__()DunderMethod:# vectors.pyclass Vector: def __init__(self, x=0, y=0, z=0): self.x = x self.y = y self.z = z def __repr__(self): return f"Vector({self.x}, {self.y}, {self.z})" def __str__(self): return f"{self.x}i + {self.y}j + {self.z}k" def __getitem__(self, item): if item == 0: return self.x elif item == 1: return self.y elif item == 2: return self.z else: raise IndexError("There are only three elements in the vector") def __add__(self, other): return Vector( self.x + other.x, self.y + other.y, self.z + other.z, ) def __sub__(self, other): return Vector( self.x - other.x, self.y - other.y, self.z - other.z, ) def __mul__(self, other): if isinstance(other, Vector): # Vector dot product return ( self.x other.x + self.y other.y + self.z other.z ) elif isinstance(other, (int, float)): # Scalar multiplication return Vector( self.x other, self.y other, self.z other, ) else: raise TypeError("operand must be Vector, int, or float")# Testing Vector Class - TO BE DELETEDtest = Vector(3, 5, 9) Vector(1, -3, 2)print(test)test = Vector(3, 5, 9) 3print(test)使用运算符将取决于第二个操作数,即符号,是标量或向量。
如果由参数表示的第二个操作数other,是类型的Vector,计算了点积。
但是,如果other是类型的int或float,返回的结果是一个新的Vector,按比例调整。
以上代码的输出如下:69i + 15j + 27k如果您想要标量乘法,则需要标量乘法。
这个象征。
如果您试图运行该语句3Vector(3, 5, 9)相反,TypeError将被提高,因为Vector类不是用于使用的有效操作数。
带有类型的对象int .两个向量是分不开的。
但是,可以将向量除以标量。
您可以使用/运算符Vector如果定义__truediv__()DunderMethod:# vectors.pyclass Vector: def __init__(self, x=0, y=0, z=0): self.x = x self.y = y self.z = z def __repr__(self): return f"Vector({self.x}, {self.y}, {self.z})" def __str__(self): return f"{self.x}i + {self.y}j + {self.z}k" def __getitem__(self, item): if item == 0: return self.x elif item == 1: return self.y elif item == 2: return self.z else: raise IndexError("There are only three elements in the vector") def __add__(self, other): return Vector( self.x + other.x, self.y + other.y, self.z + other.z, ) def __sub__(self, other): return Vector( self.x - other.x, self.y - other.y, self.z - other.z, ) def __mul__(self, other): if isinstance(other, Vector): # Vector dot product return ( self.x other.x + self.y other.y + self.z other.z ) elif isinstance(other, (int, float)): # Scalar multiplication return Vector( self.x other, self.y other, self.z other, ) else: raise TypeError("operand must be Vector, int, or float") def __truediv__(self, other): if isinstance(other, (int, float)): return Vector( self.x / other, self.y / other, self.z / other, ) else: raise TypeError("operand must be int or float")# Testing Vector Class - TO BE DELETEDtest = Vector(3, 6, 9) / 3print(test)产出如下:1.0i + 2.0j + 3.0k求向量的大小和规范向量如果你有一个向量(x,y,z),您可以找到它的震级使用表达式(x^2+y^2+z^2)。
你也可以规范化向量。
归一化给出一个方向相同但大小为1。
您可以通过将向量的每个元素除以矢量的大小来计算归一化向量。
可以定义两个新方法来完成Vector班级:# vectors.pyimport mathclass Vector: def __init__(self, x=0, y=0, z=0): self.x = x self.y = y self.z = z def __repr__(self): return f"Vector({self.x}, {self.y}, {self.z})" def __str__(self): return f"{self.x}i + {self.y}j + {self.z}k" def __getitem__(self, item): if item == 0: return self.x elif item == 1: return self.y elif item == 2: return self.z else: raise IndexError("There are only three elements in the vector") def __add__(self, other): return Vector( self.x + other.x, self.y + other.y, self.z + other.z, ) def __sub__(self, other): return Vector( self.x - other.x, self.y - other.y, self.z - other.z, ) def __mul__(self, other): if isinstance(other, Vector): # Vector dot product return ( self.x other.x + self.y other.y + self.z other.z ) elif isinstance(other, (int, float)): # Scalar multiplication return Vector( self.x other, self.y other, self.z other, ) else: raise TypeError("operand must be Vector, int, or float") def __truediv__(self, other): if isinstance(other, (int, float)): return Vector( self.x / other, self.y / other, self.z / other, ) else: raise TypeError("operand must be int or float") def get_magnitude(self): return math.sqrt(self.x 2 + self.y 2 + self.z 2) def normalize(self): magnitude = self.get_magnitude() return Vector( self.x / magnitude, self.y / magnitude, self.z / magnitude, )# Testing Vector Class - TO BE DELETEDtest = Vector(3, 6, 9)print(test.get_magnitude())print(test.normalize())print(test.normalize().get_magnitude())测试代码提供了以下输出:11.2249721603218240.2672612419124244i + 0.5345224838248488j + 0.8017837257372732k1.0第三个输出给出了归一化向量的大小,表明它的大小是1 .根据使用的IDE或其他工具,在分割时可能会收到警告self.x , self.y,和self.z,如在__truediv__()和normalize()。
您不需要担心这个问题,但是如果您想要修复它,可以通过更改__init__()签署下列任何一项:def __init__(self, x=0.0, y=0.0, z=0.0):def __init__(self, x:float=0, y:float=0, z:float=0):这两个选项都让IDE知道参数应该是浮动的。
在第二个选项中,您使用类型暗示来实现。
您现在可以删除此脚本末尾的测试代码,以便您在vectors.py是类的定义。
用Python模拟三维太阳系现在,你可以开始研究Python中的3D太阳系了。
您将创建两个主要类:SolarSystemSolarSystemBody你将使用Matplotlib来创建和可视化太阳系。
您可以在终端中使用以下内容来安装Matplotlib:$ pip install matplotlib$ python -m pip install matplotlib这个Axes3DMatplotlib中的物体将“托管”太阳系。
如果您使用过Matplotlib,并且主要使用了2D绘图,那么您将使用(有意或不知情的)Axes对象。
Axes3D的3D等效Axes,顾名思义。

现在是开始编写和测试这些类的时候了。
您可以创建两个新文件:solar_system_3d.pysimple_solar_system.py接下来,您将开始处理SolarSystem班级。
设置SolarSystem班级您将在整个项目中使用任意单元。
这意味着,与其用米作为距离,而用公斤作为质量,你将使用没有单位的数量。
参数size用于定义包含太阳系的立方体的大小:# solar_system_3d.pyclass SolarSystem: def __init__(self, size): self.size = size self.bodies = [] def add_body(self, body): self.bodies.append(body)定义SolarSystem类的__init__()方法,其中包含参数。
size。
您还定义了bodies属性。
这个属性是一个空列表,当你稍后创建它们时,它将包含太阳系内的所有天体。
这个add_body()方法可以用来将轨道天体添加到太阳系中。
下一步是介绍Matplotlib。
属性创建图形和一组轴。
subplots()在matplotlib.pyplot :# solar_system_3d.pyimport matplotlib.pyplot as pltclass SolarSystem: def __init__(self, size): self.size = size self.bodies = [] self.fig, self.ax = plt.subplots( 1, 1, subplot_kw={"projection": "3d"}, figsize=(self.size / 50, self.size / 50), ) self.fig.tight_layout() def add_body(self, body): self.bodies.append(body)你打电话plt.subplots(),它返回一个图形和一组轴。
返回的值分配给属性。
fig和ax。
你打电话plt.subplots()有以下论点:前两个论点是1和1若要在图形中创建单个轴集,请执行以下操作。
这个subplot_kw参数有一个字典作为它的参数,它将投影设置为3d。
这意味着创建的轴是Axes3D对象。
figsize设置包含Axes3D对象。
您还可以调用该方法。
tight_layout()。
这是Figure类在Matplotlib中。
此方法减少了图形边缘的边距。
到目前为止,您可以在控制台/REPL中尝试代码:>>> import matplotlib.pyplot as plt>>> from solar_system_3d import SolarSystem>>> solar_system = SolarSystem(400)>>> plt.show() # if not using interactive mode这给出了一组空的三维轴的图形:您将使用size参数设置此多维数据集的大小。
你会回到SolarSystem稍后上课。
目前,您可以将您的注意力转向定义SolarSystemBody班级。
设置SolarSystemBody班级您可以开始创建SolarSystemBody类及其__init__()方法。
我正在截断SolarSystem下面代码中的类定义用于显示。
在此代码块和以后的代码块中,包含# ...指出您之前编写的未显示的代码:# solar_system_3d.pyimport matplotlib.pyplot as pltfrom vectors import Vector# class SolarSystem:# ... class SolarSystemBody: def __init__( self, solar_system, mass, position=(0, 0, 0), velocity=(0, 0, 0), ): self.solar_system = solar_system self.mass = mass self.position = position self.velocity = Vector(velocity) self.solar_system.add_body(self)中的参数。
__init__()方法是:solar_system使你能够将天体连接到太阳系。
这个论点应该是类型的。
SolarSystem .mass定义身体质量的整数或浮点数。
在这个项目中,你将使用任意的单位,所以你不需要使用“真实”质量的恒星和行星。
position是三维空间中定义身体位置的点。
这是一个包含x -, y-和z-点坐标。
默认值是起源。
velocity定义身体的速度。
由于运动物体的速度有大小和方向,所以它必须是一个矢量。
尽管实例化SolarSystemBody元组,则可以将元组转换为Vector将其赋值给属性时。
self.velocity .你也叫add_body()方法中定义的SolarSystem类将这个天体添加到太阳系中。
稍后,您将向__init__()方法。
中定义另一个方法。
SolarSystemBody用其当前的位置和速度移动物体:# solar_system_3d.pyimport matplotlib.pyplot as pltfrom vectors import Vector# class SolarSystem:# ... class SolarSystemBody: def __init__( self, solar_system, mass, position=(0, 0, 0), velocity=(0, 0, 0), ): self.solar_system = solar_system self.mass = mass self.position = position self.velocity = Vector(velocity) self.solar_system.add_body(self) def move(self): self.position = ( self.position[0] + self.velocity[0], self.position[1] + self.velocity[1], self.position[2] + self.velocity[2], )这个move()方法重新定义position属性的velocity属性。
我们已经讨论过你是如何用任意单位来计算距离和质量的。
你也在使用任意的时间单位。
每个‘时间单位’将是循环的一个迭代,您将使用它来运行模拟。
因此,move()将身体按一次迭代所需的数量移动,这是一个时间单位。
绘制太阳系天体你们已经创建了Matplotlib结构,它将容纳太阳系及其所有天体。
现在,您可以添加一个draw()方法SolarSystemBody若要在Matplotlib图上显示主体,请执行以下操作。
您可以通过绘制一个标记来完成这一任务。
在这样做之前,您需要在SolarSystemBody若要控制将绘制的标记的颜色和大小以表示身体,请执行以下操作:# solar_system_3d.pyimport mathimport matplotlib.pyplot as pltfrom vectors import Vector# class SolarSystem:# ... class SolarSystemBody: min_display_size = 10 display_log_base = 1.3 def __init__( self, solar_system, mass, position=(0, 0, 0), velocity=(0, 0, 0), ): self.solar_system = solar_system self.mass = mass self.position = position self.velocity = Vector(velocity) self.display_size = max( math.log(self.mass, self.display_log_base), self.min_display_size, ) self.colour = "black" self.solar_system.add_body(self) def move(self): self.position = ( self.position[0] + self.velocity[0], self.position[1] + self.velocity[1], self.position[2] + self.velocity[2], ) def draw(self): self.solar_system.ax.plot( self.position, marker="o", markersize=self.display_size, color=self.colour )类属性min_display_size和display_log_base设置参数,以确定您将在3D图上显示的标记的大小。
您设置了一个最小的大小,以便您显示的标记不太小,即使对于小的身体也是如此。
您将使用对数标度将质量转换为标记大小,并将此对数的基值设置为另一个类属性。
这个display_size属性中的实例属性。
__init__()方法在计算的标记大小和所设置的最小标记大小之间进行选择。
为了在这个项目中确定身体的显示大小,你要使用它的质量。
您还可以添加colour属性__init__(),暂时默认为黑色。
要测试这些新添加的内容,可以在控制台/REPL中尝试以下内容:>>> import matplotlib.pyplot as plt>>> from solar_system_3d import SolarSystem, SolarSystemBody>>> solar_system = SolarSystem(400)>>> plt.show() # if not using interactive mode>>> body = SolarSystemBody(solar_system, 100, velocity=(1, 1, 1))>>> body.draw()>>> body.move()>>> body.draw()第一次呼叫body.draw()在原点绘制物体,因为你使用的是太阳系天体的默认位置。
打电话给body.move()用一个“时间单位”所需的数量移动身体。
因为身体的速度是(1, 1, 1),身体将沿着三个轴中的每一个移动一个单位。
第二次呼叫body.draw()在第二个位置画太阳系天体。
请注意,当您这样做时,轴将自动重新排列。
您很快就会在主代码中处理这个问题。
动星和行星您可以返回到SolarSystem通过给太阳系及其天体添加两种新的方法,将其分类和连接起来:update_all()和draw_all() :# solar_system_3d.pyimport mathimport matplotlib.pyplot as pltfrom vectors import Vectorclass SolarSystem: def __init__(self, size): self.size = size self.bodies = [] self.fig, self.ax = plt.subplots( 1, 1, subplot_kw={"projection": "3d"}, figsize=(self.size / 50, self.size / 50), ) self.fig.tight_layout() def add_body(self, body): self.bodies.append(body) def update_all(self): for body in self.bodies: body.move() body.draw() def draw_all(self): self.ax.set_xlim((-self.size / 2, self.size / 2)) self.ax.set_ylim((-self.size / 2, self.size / 2)) self.ax.set_zlim((-self.size / 2, self.size / 2)) plt.pause(0.001) self.ax.clear()# class SolarSystemBody:# ...这个update_all()方法穿过太阳系中的每一个物体,移动并画出每一个物体。
这个draw_all()方法使用太阳系的大小设置三轴的限制,并通过pause()功能。
此方法还清除轴,为下一个绘图做好准备。
您可以开始构建一个简单的太阳系,并通过创建一个名为simple_solar_system.py :# simple_solar_system.pyfrom solar_system_3d import SolarSystem, SolarSystemBodysolar_system = SolarSystem(400)body = SolarSystemBody(solar_system, 100, velocity=(1, 1, 1))for _ in range(100): solar_system.update_all() solar_system.draw_all()运行此脚本时,您将看到一个黑体从情节的中心移动:您可以更改三维图形的透视图,这样您就可以直接沿着其中一个轴查看3D轴。
可以通过将视图的方位和仰角设置为0在……里面SolarSystem.__init__() :# solar_system_3d.pyimport mathimport matplotlib.pyplot as pltfrom vectors import Vectorclass SolarSystem: def __init__(self, size): self.size = size self.bodies = [] self.fig, self.ax = plt.subplots( 1, 1, subplot_kw={"projection": "3d"}, figsize=(self.size / 50, self.size / 50), ) self.fig.tight_layout() self.ax.view_init(0, 0) def add_body(self, body): self.bodies.append(body) def update_all(self): for body in self.bodies: body.move() body.draw() def draw_all(self): self.ax.set_xlim((-self.size / 2, self.size / 2)) self.ax.set_ylim((-self.size / 2, self.size / 2)) self.ax.set_zlim((-self.size / 2, self.size / 2)) plt.pause(0.001) self.ax.clear()# class SolarSystemBody:# ...跑动simple_solar_system.py现在给出以下观点:这个x-轴现在垂直于你的屏幕。
因为你在2D显示器上显示一个3D视图,所以你总是有一个方向与你用来显示图形的2D平面垂直。
这一限制使得很难区分物体何时沿该轴运动。
你可以通过改变身体的速度simple_solar_system.py到(1, 0, 0)并再次运行脚本。
身体似乎是静止的,因为它只是沿着轴移动,从你的屏幕出来。

帮助3D透视您可以通过根据它的不同更改标记的大小来改进三维可视化。
x-协调。
靠近您的对象看起来更大,而更远的对象看起来更小。
您可以对draw()方法中的SolarSystemBody班级:# solar_system_3d.py# ...class SolarSystemBody:# ... def draw(self): self.solar_system.ax.plot( self.position, marker="o", markersize=self.display_size + self.position[0] / 30, color=self.colour )self.position[0]表示身体的位置。
x-轴,即垂直于屏幕的轴。
因子30除以是一个任意因素,您可以使用它来控制您希望这种效果有多强。
在本教程的后面,您还将添加另一个功能,将有助于可视化的三维运动的恒星和行星。
增加重力效应你有一个太阳系,里面有可以移动的物体。
到目前为止,如果您只有一个身体,那么代码可以正常工作。
但那不是一个非常有趣的太阳系。

如果你有两个或两个以上的物体,它们就会通过相互的引力相互作用。
在这篇文章的开头,我简要回顾了你需要处理两个物体之间的引力的物理。
由于在这个项目中使用的是任意单位,所以可以忽略引力常数G简单地计算出由于两个物体之间的重力而产生的力,如:F=\frac{m_1m_1}{r^2}一旦你知道了两个物体之间的力,因为F=ma,您可以计算出每个对象必须使用的加速度:a=\frac{F}{m}一旦你知道加速度,你就可以改变物体的速度。
您可以添加两个新方法,一个在SolarSystemBody另一个在SolarSystem,计算出任何两个物体之间的力和加速度,并穿过太阳系中的所有物体,并计算它们之间的相互作用。
计算重力加速度第一种方法计算出两个物体之间的引力,计算每个物体的加速度,并改变两个物体的速度。
如果您愿意,可以将这些任务分为三种方法,但在本例中,我将将这些任务放在SolarSystemBody :# solar_system_3d.pyimport mathimport matplotlib.pyplot as pltfrom vectors import Vector# class SolarSystem:# ...class SolarSystemBody:# ... def accelerate_due_to_gravity(self, other): distance = Vector(other.position) - Vector(self.position) distance_mag = distance.get_magnitude() force_mag = self.mass other.mass / (distance_mag 2) force = distance.normalize() force_mag reverse = 1 for body in self, other: acceleration = force / body.mass body.velocity += acceleration reverse reverse = -1accelerate_due_to_gravity()对类型的对象调用。
SolarSystemBody需要另一个SolarSystemBody身体作为一种争论。
参数self和other代表两个身体相互作用。
此方法的步骤如下:用两个物体的位置来确定两个物体之间的距离。
您将其表示为向量,因为它的大小和方向都很重要。
你提取x -, y-和z-position属性使用解包装运算符。
并将这些转换为类型的对象。
Vector你之前定义的。
因为您定义了__sub__()DUD方法Vector类,您可以从另一个向量中减去一个向量,以获得它们之间的距离作为另一个向量。
还可以使用get_magnitude()方法Vector班级。
接下来,你用上面总结的方程计算出两个物体之间的力的大小。
然而,力有方向,也有震级。
因此,您需要将其表示为向量。
力的方向与连接两个物体的矢量方向相同。
首先对距离向量进行归一化,得到力向量。
这种归一化给出了一个单位向量,其方向与连接这两个物体的向量相同,但其大小为1。
然后,把单位向量乘以力的大小。
在这种情况下,您使用的是向量的标量乘法,这是在包含以下内容时定义的__mul__()在Vector班级。
对于这两个物体中的每一个,你用上面所示的方程来计算加速度。
force是一个向量。
因此,当你除以body.mass,您使用的是您定义的标量除法,包括__truediv__()在Vector班级。
acceleration返回的对象是Vector.__truediv__(),这也是Vector对象。
最后,用加速度来增加速度。
该方法计算出与一个时间单位相关的值,在本仿真中是控制仿真的循环迭代所需的时间。
二.reverse参数确保对第二个物体施加相反的加速度,因为这两个物体是相互拉伸的。
这个操作员再次调用Vector.__mul__()并导致标量乘法。
计算太阳系中所有天体之间的相互作用现在你可以计算出任何两个天体之间的相互作用,你可以计算出太阳系中所有天体之间的相互作用。
你可以把你的注意力转移到SolarSystem类的类:# solar_system_3d.pyimport mathimport matplotlib.pyplot as pltfrom vectors import Vectorclass SolarSystem:# ... def calculate_all_body_interactions(self): bodies_copy = self.bodies.copy() for idx, first in enumerate(bodies_copy): for second in bodies_copy[idx + 1:]: first.accelerate_due_to_gravity(second)class SolarSystemBody:# ... def accelerate_due_to_gravity(self, other): distance = Vector(other.position) - Vector(self.position) distance_mag = distance.get_magnitude() force_mag = self.mass other.mass / (distance_mag 2) force = distance.normalize() force_mag reverse = 1 for body in self, other: acceleration = force / body.mass body.velocity += acceleration reverse reverse = -1这个calculate_all_body_interactions()方法贯穿太阳系的所有天体。
每个天体与太阳系中的其他天体相互作用:你用的是self.bodies以满足天体在循环过程中被从太阳系中移除的可能性。
在你在本文中所写的版本中,你不会从太阳系中移除任何物体。
但是,如果您进一步扩展这个项目,您可能需要在将来这样做。
为了确保您的代码不会两次计算同一两个主体之间的交互,您只需要计算出一个实体与列表中跟随它的那些主体之间的交互。
这就是为什么你要用切片idx + 1:在第二个for循环。
最后一行调用accelerate_due_to_gravity()对于第一主体,并包括第二主体作为方法的论证。
现在,您已经准备好创建一个简单的太阳系,并测试您到目前为止编写的代码。
创造一个简单的太阳系在这个项目中,您将关注创建两种类型的天体之一:太阳和行星。
您可以为这些机构创建两个类。
新类继承自SolarSystemBody :# solar_system_3d.pyimport itertoolsimport mathimport matplotlib.pyplot as pltfrom vectors import Vector# class SolarSystem:# ...# class SolarSystemBody:# ...class Sun(SolarSystemBody): def __init__( self, solar_system, mass=10_000, position=(0, 0, 0), velocity=(0, 0, 0), ): super(Sun, self).__init__(solar_system, mass, position, velocity) self.colour = "yellow"class Planet(SolarSystemBody): colours = itertools.cycle([(1, 0, 0), (0, 1, 0), (0, 0, 1)]) def __init__( self, solar_system, mass=10, position=(0, 0, 0), velocity=(0, 0, 0), ): super(Planet, self).__init__(solar_system, mass, position, velocity) self.colour = next(Planet.colours)这个Sun类的默认质量为10,000个单位,并将颜色设置为黄色。
使用字符串'yellow',这是Matplotlib中的有效颜色。
在Planet类创建一个itertools.cycle对象有三种颜色。
在这种情况下,这三种颜色是红色、绿色和蓝色。
你可以使用你想要的任何RGB颜色,也可以使用任意数量的颜色。
在这个类中,使用带有RGB值的元组来定义颜色,而不是使用颜色名称的字符串。
这也是在Matplotlib中定义颜色的有效方法。
使用next()每当你创建一个新的行星时。
您还将默认质量设置为10个单元。
现在,你可以创建一个太阳系,其中一个太阳和两个行星在simple_solar_system.py :# simple_solar_system.pyfrom solar_system_3d import SolarSystem, Sun, Planetsolar_system = SolarSystem(400)sun = Sun(solar_system)planets = ( Planet( solar_system, position=(150, 50, 0), velocity=(0, 5, 5), ), Planet( solar_system, mass=20, position=(100, -50, 150), velocity=(5, 0, 0) ))while True: solar_system.calculate_all_body_interactions() solar_system.update_all() solar_system.draw_all()在这个脚本中,您创建了一个太阳和两个行星。
你把太阳和行星分配给变量sun和planets,但这并不是严格要求的,因为Sun和Planet对象被创建,它们被添加到solar_system你不需要直接引用它们。
你用一个while循环来运行模拟。
循环在每次迭代中执行三个操作。
运行此脚本时,将获得以下动画:它起作用了,算是吧。
你可以看到太阳锚定在这个太阳系的中心,行星受到太阳引力的影响。
除了行星在包含你电脑屏幕的平面上的运动(这些是y-和z--轴),你也可以看到行星越来越大,因为它们也在x-轴,垂直于屏幕。
然而,你可能已经注意到行星的一些奇怪的行为。
当它们被安排在太阳后面时,行星仍然被展示在太阳的前面。
这不是数学上的问题--如果你跟踪行星的位置,你会发现x-坐标显示,它们实际上是在太阳后面,正如你所预料的那样。
在其他身体后面展示身体这个问题来自Matplotlib在绘图中绘制对象的方式。
Matplotlib按绘制对象的顺序将对象按层绘制。
因为你在行星之前创造了太阳,Sun对象放在第一位solar_system.bodies并作为底层绘制。
您可以通过在行星之后创建太阳来验证这一事实,在这种情况下,您将看到行星总是出现在太阳后面。
你会希望Matplotlib按照正确的顺序绘制太阳系的天体,从最前的那些天体开始。
要实现这一点,您可以对SolarSystem.bodies的值为基础的列表。
x-协调每次刷新3D图形的时间。
下面是如何在update_all()方法SolarSystem :# solar_system_3d.pyimport itertoolsimport mathimport matplotlib.pyplot as pltfrom vectors import Vectorclass SolarSystem:# ... def update_all(self): self.bodies.sort(key=lambda item: item.position[0]) for body in self.bodies: body.move() body.draw()# ...# class SolarSystemBody:# ...# class Sun(SolarSystemBody):# ...# class Planet(SolarSystemBody):# ...使用List方法sort带着key参数来定义要用于排序列表的规则。
这个lambda函数设置此规则。
在本例中,您使用的值是position[0]表示x-协调。
因此,每次你打电话update_all()在模拟中while循环中,根据其沿x-轴心。
运行simple_solar_system.py现在的脚本如下:现在,你可以想象行星的轨道,就像它们围绕太阳运行一样。
不断变化的大小显示了它们的x-位置,当行星在太阳后面时,它们被隐藏在视线之外。

最后,你也可以移除轴线和网格,这样你在模拟中看到的就是太阳和行星。
可以通过添加对Matplotlib的调用来做到这一点。
axis()方法SolarSystem.draw_all() :# solar_system_3d.pyimport itertoolsimport mathimport matplotlib.pyplot as pltfrom vectors import Vectorclass SolarSystem:# ... def draw_all(self): self.ax.set_xlim((-self.size / 2, self.size / 2)) self.ax.set_ylim((-self.size / 2, self.size / 2)) self.ax.set_zlim((-self.size / 2, self.size / 2)) self.ax.axis(False) plt.pause(0.001) self.ax.clear()# ...# class SolarSystemBody:# ...# class Sun(SolarSystemBody):# ...# class Planet(SolarSystemBody):# ...模拟现在看起来是这样的:使用Matplotlib对Python中的一个三维太阳系进行的模拟现在已经完成。
在下一节中,您将添加一个功能,允许您查看XY-模拟底部的飞机。
这有助于可视化太阳系中物体的三维动力学。
添加一个2D投影XY-飞机在Python的三维太阳系模拟中,为了帮助可视化身体的运动,您可以在动画的“地板”上添加一个2D投影。
这个2D投影将显示物体在XY-飞机。
要实现这一点,您需要将另一个绘图添加到显示动画的相同轴上,并且只需在x-和y-坐标。
你可以锚定z-与图形底部协调,使2D投影显示在动画的地板上。
您可以首先将一个新参数添加到__init__()方法的SolarSystem班级:# solar_system_3d.pyimport itertoolsimport mathimport matplotlib.pyplot as pltfrom vectors import Vectorclass SolarSystem: def __init__(self, size, projection_2d=False): self.size = size self.projection_2d = projection_2d self.bodies = [] self.fig, self.ax = plt.subplots( 1, 1, subplot_kw={"projection": "3d"}, figsize=(self.size / 50, self.size / 50), ) self.ax.view_init(0, 0) self.fig.tight_layout()# ...# class SolarSystemBody:# ...# class Sun(SolarSystemBody):# ...# class Planet(SolarSystemBody):# ...新参数projection_2d,默认为False,将允许您在两个可视化选项之间切换。
如果projection_2d是False动画将只显示身体在3D中移动,没有轴和网格,就像你最后看到的结果一样。
让我们开始做一些改变projection_2d是True :# solar_system_3d.pyimport itertoolsimport mathimport matplotlib.pyplot as pltfrom vectors import Vectorclass SolarSystem: def __init__(self, size, projection_2d=False): self.size = size self.projection_2d = projection_2d self.bodies = [] self.fig, self.ax = plt.subplots( 1, 1, subplot_kw={"projection": "3d"}, figsize=(self.size / 50, self.size / 50), ) self.fig.tight_layout() if self.projection_2d: self.ax.view_init(10, 0) else: self.ax.view_init(0, 0) def add_body(self, body): self.bodies.append(body) def update_all(self): self.bodies.sort(key=lambda item: item.position[0]) for body in self.bodies: body.move() body.draw() def draw_all(self): self.ax.set_xlim((-self.size / 2, self.size / 2)) self.ax.set_ylim((-self.size / 2, self.size / 2)) self.ax.set_zlim((-self.size / 2, self.size / 2)) if self.projection_2d: self.ax.xaxis.set_ticklabels([]) self.ax.yaxis.set_ticklabels([]) self.ax.zaxis.set_ticklabels([]) else: self.ax.axis(False) plt.pause(0.001) self.ax.clear() def calculate_all_body_interactions(self): bodies_copy = self.bodies.copy() for idx, first in enumerate(bodies_copy): for second in bodies_copy[idx + 1:]: first.accelerate_due_to_gravity(second)class SolarSystemBody: min_display_size = 10 display_log_base = 1.3 def __init__( self, solar_system, mass, position=(0, 0, 0), velocity=(0, 0, 0), ): self.solar_system = solar_system self.mass = mass self.position = position self.velocity = Vector(velocity) self.display_size = max( math.log(self.mass, self.display_log_base), self.min_display_size, ) self.colour = "black" self.solar_system.add_body(self) def move(self): self.position = ( self.position[0] + self.velocity[0], self.position[1] + self.velocity[1], self.position[2] + self.velocity[2], ) def draw(self): self.solar_system.ax.plot( self.position, marker="o", markersize=self.display_size + self.position[0] / 30, color=self.colour ) if self.solar_system.projection_2d: self.solar_system.ax.plot( self.position[0], self.position[1], -self.solar_system.size / 2, marker="o", markersize=self.display_size / 2, color=(.5, .5, .5), ) def accelerate_due_to_gravity(self, other): distance = Vector(other.position) - Vector(self.position) distance_mag = distance.get_magnitude() force_mag = self.mass other.mass / (distance_mag 2) force = distance.normalize() force_mag reverse = 1 for body in self, other: acceleration = force / body.mass body.velocity += acceleration reverse reverse = -1class Sun(SolarSystemBody): def __init__( self, solar_system, mass=10_000, position=(0, 0, 0), velocity=(0, 0, 0), ): super(Sun, self).__init__(solar_system, mass, position, velocity) self.colour = "yellow"class Planet(SolarSystemBody): colours = itertools.cycle([(1, 0, 0), (0, 1, 0), (0, 0, 1)]) def __init__( self, solar_system, mass=10, position=(0, 0, 0), velocity=(0, 0, 0), ): super(Planet, self).__init__(solar_system, mass, position, velocity) self.colour = next(Planet.colours)您所做的更改如下:在……里面SolarSystem.__init__(),则3d视图设置为view_init(0, 0)当2D投影被关闭时,就像以前一样。
但是,当打开2D投影选项以允许底部平面可见时,高程将更改为10。
在……里面SolarSystem.draw_all(),只有当没有2D投影时,才会关闭网格和轴。
当启用2D投影时,将显示轴和网格。
但是,由于这三个轴上的数字是任意的,因此不需要用空格代替勾号。
在……里面SolarSystemBody.draw(),则添加第二个情节。
projection_2d是True。
前两个论点plot()身体是x-和y-职位。
但是,而不是使用z位置作为第三个参数,您使用的最小值是z它表示包含三个轴的立方体的“地板”。
然后,绘制一个灰色标记,大小为动画中主要标记的一半。
您还需要在simple_solar_system.py打开2D投影:# simple_solar_system.pyfrom solar_system_3d import SolarSystem, Sun, Planetsolar_system = SolarSystem(400, projection_2d=True)sun = Sun(solar_system)planets = ( Planet( solar_system, position=(150, 50, 0), velocity=(0, 5, 5), ), Planet( solar_system, mass=20, position=(100, -50, 150), velocity=(5, 0, 0) ))while True: solar_system.calculate_all_body_interactions() solar_system.update_all() solar_system.draw_all()模拟现在看起来如下:的二维投影XY-平面使它更容易跟随轨道物体的路径。
创建双星系统我们将用Python完成另一个三维太阳系的模拟。
您将使用已经定义的类来模拟双星系统。
创建一个名为binary_star_system.py创造两个太阳和两个行星:# binary_star_system.pyfrom solar_system_3d import SolarSystem, Sun, Planetsolar_system = SolarSystem(400)suns = ( Sun(solar_system, position=(40, 40, 40), velocity=(6, 0, 6)), Sun(solar_system, position=(-40, -40, 40), velocity=(-6, 0, -6)),)planets = ( Planet( solar_system, 10, position=(100, 100, 0), velocity=(0, 5.5, 5.5), ), Planet( solar_system, 20, position=(0, 0, 0), velocity=(-11, 11, 0), ),)while True: solar_system.calculate_all_body_interactions() solar_system.update_all() solar_system.draw_all()对这个双星系统的模拟如下:或者,您可以在创建SolarSystem目的:# binary_star_system.pyfrom solar_system_3d import SolarSystem, Sun, Planetsolar_system = SolarSystem(400, projection_2d=True)# ...此版本提供了以下结果:这个双星系统是不稳定的,两颗行星很快就被两个太阳抛出了这个系统。

如果您愿意,您可以扩展类定义,以检测两个天体之间的碰撞,如果行星与太阳碰撞,则将其移除。
这个项目的更简单的2D版本,其中在2D中模拟轨道行星,包括此功能。
如果您想将它添加到这个项目中,您可以查看它是如何在这个简单的项目中实现的。
本文中使用的代码的最终版本也是此GitHub回购提供 .最后一句话现在,您可以使用Matplotlib在Python中模拟一个3D太阳系。
在本文中,您已经学习了如何使用向量和Matplotlib的图形功能将对象放置在3D空间中。
您可以了解更多有关如何使用Matplotlib的内容,包括使用animations在Matplotlib中的子模块,在本章中使用Matplotlib的Python数据可视化基础Python编码书。
这就完成了两个部分的轨道行星系列。
在这个系列的第一篇文章中,您只考虑了2D场景,并使用了turtle模块创建图形动画。

在您刚刚完成的第二篇文章中,您使用Matplotlib查看了Python中的一个3D太阳系,用于动画的图形表示。
现在轮到你尝试创造简单而更复杂的太阳系了。
你能创造一个稳定的双星系统吗?我希望你喜欢用Matplotlib在Python中模拟一个3D太阳系。
现在,您已经准备好尝试创建您自己的真实世界过程模拟。
原文:Https://thepythoncodingbook.com/2021/12/11/simulating-3d-solar-system-python-matplotlib/
太阳系模拟MatplotlibPython(太阳系向量您可以物体行星)
(图片来源网络,侵删)

联系我们

在线咨询:点击这里给我发消息