A. 如何用python设计一个能实现添加、修改、删除、显示、退出等功能的小程序
可以使用 Python 中的字典(dictionary)来实现一个简单的增删改查程序。具体实现步骤如下:
创建一个空字典用于存储数据。
通过循环菜单的方式,让用户可以选择添加、修改、删除、显示、退出等功能。
根据用户的选择执行相应的操作,例如肆闷搏添加数据就让用户输入键值对,修改数据就让用户选择要修改的键和新值,删除数据就让用户选择要删除的键,显示数据就输出整个字典,退出就结束程序。
下面是一个简单的代码示例:
data = {}裂祥 # 创建空字典用于存储数据
while True:
print('请选择操作:')
print('1. 添加数据')
print('2. 修改数据')
print('3. 删除数据')
print('4. 显示数据')
print('5. 退出程序')
choice = input('请输入选项:')
if choice == '1':
key = input('请输入键:')
value = input('请输入值:')
data[key] = value
print('添加成功!')
elif choice == '2':
key = input('请输入要修改的键:')
if key in data:
value = input('请输入新值:')
data[key] = value
print('修改成功!')
else:
print('该键不存在!')
elif choice == '3':
key = input('请输入要删除的键:')
if key in data:
del data[key]
print('删除成功!')
else:
print('该键不存在!')
elif choice == '4':
print(data)
elif choice == '5':
print('谢谢使用,再见!')
break
else:
print('输入错误,请重新选择。')
这个程序简单易罩枣懂,可以根据自己的需要进行修改和扩展。
B. 如何用python实现Markowitz投资组合优化
多股票策略回测时常常遇到问题。
仓位如何分配?
你以为基金经理都是一拍脑袋就等分仓位了吗?
或者玩点玄乎的斐波拉契数列?
OMG,谁说的黄金比例,让我看到你的脑袋(不削才怪)!!
其实,这个问题,好多好多年前马科维茨(Markowitz)我喜爱的小马哥就给出答案——投资组合理论。
根据这个理论,我们可以对多资产的组合配置进行三方面的优化。
1.找到有效前沿。在既定的收益率下使组合的方差最小。
2.找到sharpe最优的组合(收益-风险均衡点)
3.找到风险最小的组合
跟着我,一步两步,轻松实现。
该理论基于用均值和方差来表述组合的优劣的前提。将选取几只股票,用蒙特卡洛模拟初步探究组合的有效前沿。
通过最大Sharpe和最小方差两种优化来找到最优的资产组合配置权重参数。
最后,刻画出可能的分布,两种最优以及组合的有效前沿。
注:
文中的数据API来自量化平台聚宽,在此表示感谢。
原文见【组合管理】——投资组合理论(有效前沿)(包含正态检验部分)
0.导入需要的包
import pandas as pd
import numpy as np
import statsmodels.api as sm #统计运算
import scipy.stats as scs #科学计算
import matplotlib.pyplot as plt #绘图
1.选取几只感兴趣的股票
000413 东旭光电,000063 中兴通讯,002007 华兰生物,000001 平安银行,000002 万科A
并比较一下数据(2015-01-01至2015-12-31)
In[1]:
stock_set = ['000413.XSHE','000063.XSHE','002007.XSHE','000001.XSHE','000002.XSHE']
noa = len(stock_set)
df = get_price(stock_set, start_date = '2015-01-01', end_date ='2015-12-31', 'daily', ['close'])
data = df['close']
#规范化后时序数据
(data/data.ix[0]*100).plot(figsize = (8,5))
Out[1]:
2.计算不同证券的均值、协方差
每年252个交易日,用每日收益得到年化收益。计算投资资产的协方差是构建资产组合过程的核心部分。运用pandas内置方法生产协方差矩阵。
In [2]:
returns = np.log(data / data.shift(1))
returns.mean()*252
Out[2]:
000413.XSHE 0.184516
000063.XSHE 0.176790
002007.XSHE 0.309077
000001.XSHE -0.102059
000002.XSHE 0.547441
In [3]:
returns.cov()*252
Out[3]:
3.给不同资产随机分配初始权重
由于A股不允许建立空头头寸,所有的权重系数均在0-1之间
In [4]:
weights = np.random.random(noa)
weights /= np.sum(weights)
weights
Out[4]:
array([ 0.37505798, 0.21652754, 0.31590981, 0.06087709, 0.03162758])
4.计算预期组合年化收益、组合方差和组合标准差
In [5]:
np.sum(returns.mean()*weights)*252
Out[5]:
0.21622558669017816
In [6]:
np.dot(weights.T, np.dot(returns.cov()*252,weights))
Out[6]:
0.23595133640121463
In [7]:
np.sqrt(np.dot(weights.T, np.dot(returns.cov()* 252,weights)))
Out[7]:
0.4857482232609962
5.用蒙特卡洛模拟产生大量随机组合
进行到此,我们最想知道的是给定的一个股票池(证券组合)如何找到风险和收益平衡的位置。
下面通过一次蒙特卡洛模拟,产生大量随机的权重向量,并记录随机组合的预期收益和方差。
In [8]:
port_returns = []
port_variance = []
for p in range(4000):
weights = np.random.random(noa)
weights /=np.sum(weights)
port_returns.append(np.sum(returns.mean()*252*weights))
port_variance.append(np.sqrt(np.dot(weights.T, np.dot(returns.cov()*252, weights))))
port_returns = np.array(port_returns)
port_variance = np.array(port_variance)
#无风险利率设定为4%
risk_free = 0.04
plt.figure(figsize = (8,4))
plt.scatter(port_variance, port_returns, c=(port_returns-risk_free)/port_variance, marker = 'o')
plt.grid(True)
plt.xlabel('excepted volatility')
plt.ylabel('expected return')
plt.colorbar(label = 'Sharpe ratio')
Out[8]:
6.投资组合优化1——sharpe最大
建立statistics函数来记录重要的投资组合统计数据(收益,方差和夏普比)
通过对约束最优问题的求解,得到最优解。其中约束是权重总和为1。
In [9]:
def statistics(weights):
weights = np.array(weights)
port_returns = np.sum(returns.mean()*weights)*252
port_variance = np.sqrt(np.dot(weights.T, np.dot(returns.cov()*252,weights)))
return np.array([port_returns, port_variance, port_returns/port_variance])
#最优化投资组合的推导是一个约束最优化问题
import scipy.optimize as sco
#最小化夏普指数的负值
def min_sharpe(weights):
return -statistics(weights)[2]
#约束是所有参数(权重)的总和为1。这可以用minimize函数的约定表达如下
cons = ({'type':'eq', 'fun':lambda x: np.sum(x)-1})
#我们还将参数值(权重)限制在0和1之间。这些值以多个元组组成的一个元组形式提供给最小化函数
bnds = tuple((0,1) for x in range(noa))
#优化函数调用中忽略的唯一输入是起始参数列表(对权重的初始猜测)。我们简单的使用平均分布。
opts = sco.minimize(min_sharpe, noa*[1./noa,], method = 'SLSQP', bounds = bnds, constraints = cons)
opts
Out[9]:
status: 0
success: True
njev: 4
nfev: 28
fun: -1.1623048291871221
x: array([ -3.60840218e-16, 2.24626781e-16, 1.63619563e-01, -2.27085639e-16, 8.36380437e-01])
message: 'Optimization terminated successfully.'
jac: array([ 1.81575805e-01, 5.40387481e-01, 8.18073750e-05, 1.03137662e+00, -1.60038471e-05, 0.00000000e+00])
nit: 4
得到的最优组合权重向量为:
In [10]:
opts['x'].round(3)
Out[10]:
array([-0. , 0. , 0.164, -0. , 0.836])
sharpe最大的组合3个统计数据分别为:
In [11]:
#预期收益率、预期波动率、最优夏普指数
statistics(opts['x']).round(3)
Out[11]:
array([ 0.508, 0.437, 1.162])
7.投资组合优化2——方差最小
接下来,我们通过方差最小来选出最优投资组合。
In [12]:
#但是我们定义一个函数对 方差进行最小化
def min_variance(weights):
return statistics(weights)[1]
optv = sco.minimize(min_variance, noa*[1./noa,],method = 'SLSQP', bounds = bnds, constraints = cons)
optv
Out[12]:
status: 0
success: True
njev: 7
nfev: 50
fun: 0.38542969450547221
x: array([ 1.14787640e-01, 3.28089742e-17, 2.09584008e-01, 3.53487044e-01, 3.22141307e-01])
message: 'Optimization terminated successfully.'
jac: array([ 0.3851725 , 0.43591119, 0.3861807 , 0.3849672 , 0.38553924, 0. ])
nit: 7
方差最小的最优组合权重向量及组合的统计数据分别为:
In [13]:
optv['x'].round(3)
Out[13]:
array([ 0.115, 0. , 0.21 , 0.353, 0.322])
In [14]:
#得到的预期收益率、波动率和夏普指数
statistics(optv['x']).round(3)
Out[14]:
array([ 0.226, 0.385, 0.587])
8.组合的有效前沿
有效前沿有既定的目标收益率下方差最小的投资组合构成。
在最优化时采用两个约束,1.给定目标收益率,2.投资组合权重和为1。
In [15]:
def min_variance(weights):
return statistics(weights)[1]
#在不同目标收益率水平(target_returns)循环时,最小化的一个约束条件会变化。
target_returns = np.linspace(0.0,0.5,50)
target_variance = []
for tar in target_returns:
cons = ({'type':'eq','fun':lambda x:statistics(x)[0]-tar},{'type':'eq','fun':lambda x:np.sum(x)-1})
res = sco.minimize(min_variance, noa*[1./noa,],method = 'SLSQP', bounds = bnds, constraints = cons)
target_variance.append(res['fun'])
target_variance = np.array(target_variance)
下面是最优化结果的展示。
叉号:构成的曲线是有效前沿(目标收益率下最优的投资组合)
红星:sharpe最大的投资组合
黄星:方差最小的投资组合
In [16]:
plt.figure(figsize = (8,4))
#圆圈:蒙特卡洛随机产生的组合分布
plt.scatter(port_variance, port_returns, c = port_returns/port_variance,marker = 'o')
#叉号:有效前沿
plt.scatter(target_variance,target_returns, c = target_returns/target_variance, marker = 'x')
#红星:标记最高sharpe组合
plt.plot(statistics(opts['x'])[1], statistics(opts['x'])[0], 'r*', markersize = 15.0)
#黄星:标记最小方差组合
plt.plot(statistics(optv['x'])[1], statistics(optv['x'])[0], 'y*', markersize = 15.0)
plt.grid(True)
plt.xlabel('expected volatility')
plt.ylabel('expected return')
plt.colorbar(label = 'Sharpe ratio')
Out[16]:
C. 利用python实现数据分析
链接:
炼数成金:Python数据分析。Python是一种面向对象、直译式计算机程序设计语言。也是一种功能强大而完善的通用型语言,已经具有十多年的发展历史,成熟且稳定。Python 具有脚本语言中最丰富和强大的类库,足以支持绝大多数日常应用。 Python语法简捷而清晰,具有丰富和强大的类库。它常被昵称为胶水语言,它能够很轻松的把用其他语言制作的各种模块(尤其是C/C++)轻松地联结在一起。
课程将从Python的基本使用方法开始,一步步讲解,从ETL到各种数据分析方法的使用,并结合实例,让学员能从中借鉴学习。
课程目录:
Python基础
Python的概览——Python的基本介绍、安装与基本语法、变量类型与运算符
了解Python流程控制——条件、循环语句与其他语句
常用函数——函数的定义与使用方法、主要内置函数的介绍
.....
D. Python网络编程6-使用Pysnmp实现简单网管
简单网络管理协议SNMP(Simple Network Management Protocol)用于网络设备的管理。SNMP作为广泛应用于TCP/IP网络的网络管理标准协议,提供了统一的接口,从而实现了不同种类和厂商的网络设备之间的统一管理。
SNMP协议分为三个版本:SNMPv1、SNMPv2c和SNMPv3。
SNMP系统由网络管理系统NMS(Network Management System)、SNMP Agent、被管对象Management object和管理信息库MIB(Management Information Base)四部分组成。
SNMP查询是指NMS主动向SNMP Agent发送查询请求,如图1-3所示。SNMP Agent接收到查询请求后,通过MIB表完成相应指令,并将结果反馈给NMS。SNMP查询操作有三种:Get、GetNext和GetBulk。SNMPv1版本不支持GetBulk操作。
不同版本的SNMP查询操作的工作原理基本一致,唯一的区别是SNMPv3版本增加了身份验证和加密处理。下面以SNMPv2c版本的Get操作为例介绍SNMP查询操作的工作原理。假定NMS想要获取被管理设备MIB节点sysContact的值,使用可读团体名为public,过程如下所示:
SNMP设置是指NMS主动向SNMP Agent发送对设备进行Set操作的请求,如下图示。SNMP Agent接收到Set请求后,通过MIB表完成相应指令,并将结果反馈给NMS。
不同版本的SNMP Set操作的工作原理基本一致,唯一的区别是SNMPv3版本增加了身份验证和加密处理。下面以SNMPv3版本的Set操作为例介绍SNMP Set操作的工作原理。
假定NMS想要设置被管理设备MIB节点sysName的值为HUAWEI,过程如下所示:
SNMPv1和SNMPv2c的Set操作报文格式如下图所示。一般情况下,SNMPv3的Set操作信息是经过加密封装在SNMP PDU中,其格式与SNMPv2c的Set操作报文格式一致。
SNMP Traps是指SNMP Agent主动将设备产生的告警或事件上报给NMS,以便网络管理员及时了解设备当前运行的状态。
SNMP Agent上报SNMP Traps有两种方式:Trap和Inform。SNMPv1版本不支持Inform。Trap和Inform的区别在于,SNMP Agent通过Inform向NMS发送告警或事件后,NMS需要回复InformResponse进行确认。
在Ensp中搭建网络环境,在R2上启用SNMP作为SNMP agent,Linux主机作为NMS;为方便观察SNMP报文格式,在R2使用SNMP的版本为v2c。
通过下面的Python脚本获取R2的系统信息与当前的主机名
运行结果如下
在R2接口上抓包结果如下,Linux主机向R2的161端口发送SNMP get-request报文,可以看到SNMP使用的版本为v2c,设置的团体名为public,随机生成了一个request-id,变量绑定列表(Variable bindings),即要查询的OID,但Value为空;值得注意的是这些信息都是明文传输的,为了安全在实际环境中应使用SNMPv3。
通过下面的Python脚本获取R2的接口信息。
运行结果如下:
在R2接口抓包结果如下,getBuikRequest相比get-request设置了一个max-repetitions字段,表明最多执行get操作的次数。Variable bindings中请求的OID条目只有一条。
下面Python脚本用于设置R2的主机名为SNMPv2R2。
运行结果如下
在路由器上可以看到主机名有R2变为了SNMPv2R2。
get-response数据包内容与set-request中无异。
下面Python脚本用于接收,R2发送的Trap,并做简单解析。
先运行该脚本,之后再R2上手动将一个接口shutdown,结果如下:
接口上抓包结果如下,此时团体名用的是public,data部分表明是trap。
由于Ensp中的通用路由器认证算法只支持des56,而pysnmp不支持该算法,因此使用AR路由器配置SNMPv3。
使用下面Python脚本发送snmpv3 get报文获取设备系统信息。
抓包结果如下,首先发送get-resques进行SNMPv3认证请求,随机生成一个msgID,认证模式为USM,msgflgs中Reportable置1要求对方发送report,其他为置0,表示不进行加密与鉴权;另外安全参数,认证参数、加密参数都为空,此时不携带get请求数据。
路由器给NMS回复report,msgID与resquest一致,Msgflgs中各位都置0,同时回复使用的安全引擎,认证与加密参数为空,不进行认证与加密,因此能看到data中的数据。
AR1收到请求后进行回复,数据包中msgflags标志位中除reportable外其他位都置1,表示不需要回复,同时进行加密与鉴权。同样也可以看到认证用户为testuser,认证参数与加密参数都有填充,data部分也是同样加密。
参考:
什么是SNMP - 华为 (huawei.com)
AR100-S V300R003 MIB参考 - 华为 (huawei.com)
SNMP library for Python — SNMP library for Python 4.4 documentation (pysnmp.readthedocs.io)
E. 使用Matplotlib模拟Python中的三维太阳系
编程的一个用途是通过模拟来帮助我们理解真实世界。这一技术被应用于科学、金融和许多其他定量领域。只要控制现实世界属性的“规则”是已知的,你就可以编写一个计算机程序来 探索 你遵循这些规则所得到的结果。在本文中,您将 用Python模拟三维太阳系 使用流行的可视化库Matplotlib
在这篇文章,你将能够用Python创建你自己的3D太阳系,你可以用你想要的多少太阳和行星。下面是一个简单的太阳系的一个例子,它有一个太阳和两个行星:
你还可以打开动画地板上的二维投影,更好地展示太阳系的三维本质。下面是同样的太阳系模拟,包括2D投影:
下面是这篇文章的概要,以便您知道接下来会发生什么:
在本文中,您将使用面向对象的编程和Matplotlib。如果您希望阅读更多关于任何一个主题的内容,您可以阅读:
让我们从使用Matplotlib在Python中模拟一个3D太阳系开始。
太阳系中的太阳、行星和其他天体都是运动中的天体,它们相互吸引。引力在任何两个物体之间施加。
如果这两个对象有大量M_1和M_2是距离r然后,你可以用以下公式计算它们之间的引力:
常数G是一个引力常数。您将看到如何在模拟的版本中忽略这个常量,在本文中,您将使用任意单位的质量和距离,而不是kg和m。
一旦你知道了两个物体之间的引力,你就可以计算出加速度。a每个物体都是由于这种引力而经历的,使用以下公式:
使用这个加速度,你可以调整运动物体的速度。当速度发生变化时,速度和方向都会发生变化。
当用Python模拟一个三维太阳系时,你需要用三维空间来表示太阳系。因此,这个3D空间中的每个点都可以用三个数字来表示, x -, y -和 z -坐标。例如,如果你想把太阳放在太阳系的中心,你可以将太阳的位置表示为 (0, 0, 0) .
您还需要在3D空间中表示向量。矢量具有大小和方向。你需要像速度、加速度和力这样的量的矢量,因为这些量都有一个方向和一个震级。
在本文中,我将不详细讨论向量代数。相反,我将陈述您需要的任何结果。你可以读到更多关于向量与向量代数如果你愿意的话。
为了在代码中更容易地处理向量,您可以创建一个类来处理它们。编写这个类将作为对类和面向对象编程的快速刷新。你可以读到用Python进行面向对象的编程如果你觉得你需要一个更彻底的解释。虽然您也可以创建一个类来处理3D空间中的点,但这并不是必要的,在本文中我也不会创建一个类。
如果您熟悉向量和面向对象编程,可以跳过本节,只需在定义 Vector 班级。
创建一个名为 vectors.py 中,您将定义 Vector 班级。您将使用此脚本定义类并对其进行测试。然后,可以删除最后的测试代码,只需在这个脚本中保留类定义:
这个 __init__() 方法的 Vector 类有三个参数,表示每个轴上的值。每个参数的默认值为 0 表示该轴的原点。虽然我们不喜欢在Python中使用单个字母名称, x , y ,和 z 是恰当的,因为它们代表了数学中常用的笛卡尔坐标系的术语。
您还定义了两个Dunder方法来将对象表示为一个字符串:
在代码段的末尾,您可以更多地了解这两种类型的字符串表示之间的差异。Python编码书第9章 .
测试代码块的输出如下:
在Python项目中的这个3D太阳系中,如果 Vector 类是可索引的,以便您可以使用 [] 带有索引以提取其中一个值的符号。使用当前形式的类,如果添加 print(test[0]) 在您的脚本中,您将得到一个 TypeError 说 Vector 对象不可订阅。您可以通过向类定义中添加另一个Dud方法来修复这个问题:
通过定义 __getitem__() ,你做了 Vector 可索引的类。向量中的第一项是 x 的价值。 y 的价值。 z 。任何其他索引都会引发错误。测试代码块的输出如下:
test[0] 返回向量中的第一个项, x .
可以定义类的对象的加法和减法。 __add__() 和 __sub__() DunderMethod.这些方法将使您能够使用 + 和 - 执行这些操作的符号。如果没有这些Dud方法,则使用 + 和 - 提出 TypeError .
若要添加或减去两个向量,可以分别添加或减去向量的每个元素:
双管齐下 __add__() 和 __sub__() 返回另一个 Vector 对象,每个元素等于两个原始向量中相应元素的加减。输出如下:
对于乘法和除法,您也可以这样做,尽管在处理向量时,这些操作需要更多的注意。
在处理向量时,不能仅仅引用“乘法”,因为有不同类型的“乘法”。在这个项目中,你只需要标量乘法。标量乘法是指向量与标量相乘(标量有一个数量级,但没有方向)。但是,在本小节中,您还将定义点积两个向量。你想用 * 运算符,既适用于标量乘法,也适用于点积。因此,可以定义 __mul__() DunderMethod:
使用 * 运算符将取决于第二个操作数,即 * 符号,是标量或向量。如果由参数表示的第二个操作数 other ,是类型的 Vector ,计算了点积。但是,如果 other 是类型的 int 或 float ,返回的结果是一个新的 Vector ,按比例调整。
以上代码的输出如下:
如果您想要标量乘法,则需要标量乘法。 后 这个 * 象征。如果您试图运行该语句 3*Vector(3, 5, 9) 相反, TypeError 将被提高,因为 Vector 类不是用于使用的有效操作数。 * 带有类型的对象 int .
两个向量是分不开的。但是,可以将向量除以标量。您可以使用 / 运算符 Vector 如果定义 __truep__() DunderMethod:
产出如下:
如果你有一个向量(x,y,z),您可以找到它的震级使用表达式(x^2+y^2+z^2)。你也可以规范化向量。归一化给出一个方向相同但大小为 1 。您可以通过将向量的每个元素除以矢量的大小来计算归一化向量。
可以定义两个新方法来完成 Vector 班级:
测试代码提供了以下输出:
第三个输出给出了归一化向量的大小,表明它的大小是 1 .
根据使用的IDE或其他工具,在分割时可能会收到警告 self.x , self.y ,和 self.z ,如在 __truep__() 和 normalize() 。您不需要担心这个问题,但是如果您想要修复它,可以通过更改 __init__() 签署下列任何一项:
或
这两个选项都让IDE知道参数应该是浮动的。在第二个选项中,您使用类型暗示来实现。
您现在可以删除此脚本末尾的测试代码,以便您在 vectors.py 是类的定义。
现在,你可以开始研究Python中的3D太阳系了。您将创建两个主要类:
你将使用Matplotlib来创建和可视化太阳系。您可以在终端中使用以下内容来安装Matplotlib:
或
这个 Axes3D Matplotlib中的物体将“托管”太阳系。如果您使用过Matplotlib,并且主要使用了2D绘图,那么您将使用(有意或不知情的) Axes 对象。 Axes3D 的3D等效 Axes ,顾名思义!
现在是开始编写和测试这些类的时候了。您可以创建两个新文件:
接下来,您将开始处理 SolarSystem 班级。
您将在整个项目中使用任意单元。这意味着,与其用米作为距离,而用公斤作为质量,你将使用没有单位的数量。参数 size 用于定义包含太阳系的立方体的大小:
定义 SolarSystem 类的 __init__() 方法,其中包含参数。 size 。您还定义了 bodies 属性。这个属性是一个空列表,当你稍后创建它们时,它将包含太阳系内的所有天体。这个 add_body() 方法可以用来将轨道天体添加到太阳系中。
下一步是介绍Matplotlib。属性创建图形和一组轴。 subplots() 在 matplotlib.pyplot :
你打电话 plt.subplots() ,它返回一个图形和一组轴。返回的值分配给属性。 fig 和 ax 。你打电话 plt.subplots() 有以下论点:
您还可以调用该方法。 tight_layout() 。这是 Figure 类在Matplotlib中。此方法减少了图形边缘的边距。
到目前为止,您可以在控制台/REPL中尝试代码:
这给出了一组空的三维轴的图形:
您将使用 size 参数设置此多维数据集的大小。你会回到 SolarSystem 稍后上课。目前,您可以将您的注意力转向定义 SolarSystemBody 班级。
您可以开始创建 SolarSystemBody 类及其 __init__() 方法。我正在截断 SolarSystem 下面代码中的类定义用于显示。在此代码块和以后的代码块中,包含 # ... 指出您之前编写的未显示的代码:
中的参数。 __init__() 方法是:
你也叫 add_body() 方法中定义的 SolarSystem 类将这个天体添加到太阳系中。稍后,您将向 __init__() 方法。
中定义另一个方法。 SolarSystemBody 用其当前的位置和速度移动物体:
这个 move() 方法重新定义 position 属性的 velocity 属性。我们已经讨论过你是如何用任意单位来计算距离和质量的。你也在使用任意的时间单位。每个‘时间单位’将是循环的一个迭代,您将使用它来运行模拟。因此, move() 将身体按一次迭代所需的数量移动,这是一个时间单位。
你们已经创建了Matplotlib结构,它将容纳太阳系及其所有天体。现在,您可以添加一个 draw() 方法 SolarSystemBody 若要在Matplotlib图上显示主体,请执行以下操作。您可以通过绘制一个标记来完成这一任务。
在这样做之前,您需要在 SolarSystemBody 若要控制将绘制的标记的颜色和大小以表示身体,请执行以下操作:
类属性 min_display_size 和 display_log_base 设置参数,以确定您将在3D图上显示的标记的大小。您设置了一个最小的大小,以便您显示的标记不太小,即使对于小的身体也是如此。您将使用对数标度将质量转换为标记大小,并将此对数的基值设置为另一个类属性。
这个 display_size 属性中的实例属性。 __init__() 方法在计算的标记大小和所设置的最小标记大小之间进行选择。为了在这个项目中确定身体的显示大小,你要使用它的质量。
您还可以添加 colour 属性 __init__() ,暂时默认为黑色。
要测试这些新添加的内容,可以在控制台/REPL中尝试以下内容:
第一次呼叫 body.draw() 在原点绘制物体,因为你使用的是太阳系天体的默认位置。打电话给 body.move() 用一个“时间单位”所需的数量移动身体。因为身体的速度是 (1, 1, 1) ,身体将沿着三个轴中的每一个移动一个单位。第二次呼叫 body.draw() 在第二个位置画太阳系天体。请注意,当您这样做时,轴将自动重新排列。您很快就会在主代码中处理这个问题。
您可以返回到 SolarSystem 通过给太阳系及其天体添加两种新的方法,将其分类和连接起来: update_all() 和 draw_all() :
这个 update_all() 方法穿过太阳系中的每一个物体,移动并画出每一个物体。这个 draw_all() 方法使用太阳系的大小设置三轴的限制,并通过 pause() 功能。此方法还清除轴,为下一个绘图做好准备。
您可以开始构建一个简单的太阳系,并通过创建一个名为 simple_solar_system.py :
运行此脚本时,您将看到一个黑体从情节的中心移动:
您可以更改三维图形的透视图,这样您就可以直接沿着其中一个轴查看3D轴。可以通过将视图的方位和仰角设置为 0 在……里面 SolarSystem.__init__() :
跑动 simple_solar_system.py 现在给出以下观点:
这个 x -轴现在垂直于你的屏幕。因为你在2D显示器上显示一个3D视图,所以你总是有一个方向与你用来显示图形的2D平面垂直。这一限制使得很难区分物体何时沿该轴运动。你可以通过改变身体的速度 simple_solar_system.py 到 (1, 0, 0) 并再次运行脚本。身体似乎是静止的,因为它只是沿着轴移动,从你的屏幕出来!
您可以通过根据它的不同更改标记的大小来改进三维可视化。 x -协调。靠近您的对象看起来更大,而更远的对象看起来更小。您可以对 draw() 方法中的 SolarSystemBody 班级:
self.position[0] 表示身体的位置。 x -轴,即垂直于屏幕的轴。因子 30 除以是一个任意因素,您可以使用它来控制您希望这种效果有多强。
在本教程的后面,您还将添加另一个功能,将有助于可视化的三维运动的恒星和行星。
你有一个太阳系,里面有可以移动的物体。到目前为止,如果您只有一个身体,那么代码可以正常工作。但那不是一个非常有趣的太阳系!如果你有两个或两个以上的物体,它们就会通过相互的引力相互作用。
在这篇文章的开头,我简要回顾了你需要处理两个物体之间的引力的物理。由于在这个项目中使用的是任意单位,所以可以忽略引力常数 G 简单地计算出由于两个物体之间的重力而产生的力,如:
一旦你知道了两个物体之间的力,因为F=ma,您可以计算出每个对象必须使用的加速度:
一旦你知道加速度,你就可以改变物体的速度。
您可以添加两个新方法,一个在 SolarSystemBody 另一个在 SolarSystem ,计算出任何两个物体之间的力和加速度,并穿过太阳系中的所有物体,并计算它们之间的相互作用。
第一种方法计算出两个物体之间的引力,计算每个物体的加速度,并改变两个物体的速度。如果您愿意,可以将这些任务分为三种方法,但在本例中,我将将这些任务放在 SolarSystemBody :
accelerate_e_to_gravity() 对类型的对象调用。 SolarSystemBody 需要另一个 SolarSystemBody 身体作为一种争论。参数 self 和 other 代表两个身体相互作用。此方法的步骤如下:
现在你可以计算出任何两个天体之间的相互作用,你可以计算出太阳系中所有天体之间的相互作用。你可以把你的注意力转移到 SolarSystem 类的类:
这个 calculate_all_body_interactions() 方法贯穿太阳系的所有天体。每个天体与太阳系中的其他天体相互作用:
现在,您已经准备好创建一个简单的太阳系,并测试您到目前为止编写的代码。
在这个项目中,您将关注创建两种类型的天体之一:太阳和行星。您可以为这些机构创建两个类。新类继承自 SolarSystemBody :
这个 Sun 类的默认质量为10,000个单位,并将颜色设置为黄色。使用字符串 'yellow' ,这是Matplotlib中的有效颜色。
在 Planet 类创建一个 itertools.cycle 对象有三种颜色。在这种情况下,这三种颜色是红色、绿色和蓝色。你可以使用你想要的任何RGB颜色,也可以使用任意数量的颜色。在这个类中,使用带有RGB值的元组来定义颜色,而不是使用颜色名称的字符串。这也是在Matplotlib中定义颜色的有效方法。使用 next() 每当你创建一个新的行星时。
您还将默认质量设置为10个单元。
现在,你可以创建一个太阳系,其中一个太阳和两个行星在 simple_solar_system.py :
在这个脚本中,您创建了一个太阳和两个行星。你把太阳和行星分配给变量 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 :
使用List方法 sort 带着 key 参数来定义要用于排序列表的规则。这个 lambda 函数设置此规则。在本例中,您使用的值是 position[0] 表示 x -协调。因此,每次你打电话 update_all() 在模拟中 while 循环中,根据其沿 x -轴心。
运行 simple_solar_system.py 现在的脚本如下:
现在,你可以想象行星的轨道,就像它们围绕太阳运行一样。不断变化的大小显示了它们的 x -位置,当行星在太阳后面时,它们被隐藏在视线之外!
最后,你也可以移除轴线和网格,这样你在模拟中看到的就是太阳和行星。可以通过添加对Matplotlib的调用来做到这一点。 axis() 方法 SolarSystem.draw_all() :
模拟现在看起来是这样的:
使用Matplotlib对Python中的一个三维太阳系进行的模拟现在已经完成。在下一节中,您将添加一个功能,允许您查看 XY -模拟底部的飞机。这有助于可视化太阳系中物体的三维动力学。
在Python的三维太阳系模拟中,为了帮助可视化身体的运动,您可以在动画的“地板”上添加一个2D投影。这个2D投影将显示物体在 XY -飞机。要实现这一点,您需要将另一个绘图添加到显示动画的相同轴上,并且只需在 x -和 y -坐标。你可以锚定 z -与图形底部协调,使2D投影显示在动画的地板上。
您可以首先将一个新参数添加到 __init__() 方法的 SolarSystem 班级:
新参数 projection_2d ,默认为 False ,将允许您在两个可视化选项之间切换。如果 projection_2d 是 False 动画将只显示身体在3D中移动,没有轴和网格,就像你最后看到的结果一样。
让我们开始做一些改变 projection_2d 是 True :
您所做的更改如下:
您还需要在 simple_solar_system.py 打开2D投影:
模拟现在看起来如下:
的二维投影 XY -平面使它更容易跟随轨道物体的路径。
我们将用Python完成另一个三维太阳系的模拟。您将使用已经定义的类来模拟双星系统。创建一个名为 binary_star_system.py 创造两个太阳和两个行星: