【python库mpi4py的蒙特卡洛法求定积分的并行与串行实现】

2024-01-20 15:10

本文主要是介绍【python库mpi4py的蒙特卡洛法求定积分的并行与串行实现】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

蒙特卡罗模拟方法:是一种计算技术,用于通过统计抽样研究和预测复杂系统的行为。它最早是由数学家斯坦尼斯瓦夫·乌拉姆和约翰·冯·诺依曼在20世纪40年代开发的,用于解决物理学中的复杂问题,例如中子在核链式反应中的行为。蒙特卡洛模拟包括生成大量可能的场景,称为“随机样本”,以便对复杂系统的行为进行建模和预测。通常,这些随机样本是使用随机数生成器或通过模拟随机性的其他方式生成的。

mpi4py:mpi4py库是一个Python包,它为分布式并行计算的消息传递接口(MPI)标准提供了一个高效且用户友好的接口。MPI是一种广泛使用的规范,用于高性能计算(HPC)和并行计算环境中进程之间的消息传递和通信。mpi4py库使Python开发人员能够利用MPI的强大功能,并利用跨多个处理器、节点或集群的并行处理。mpi4py针对性能进行了优化,旨在最大限度地减少从Python调用MPI函数的相关开销。它建立在高质量和高效的C级MPI绑定之上,使用户能够充分利用基于MPI的HPC系统的性能。mpi4py与大多数流行的MPI实现兼容,如Open MPI、MPICH和Intel MPI。这意味着您可以使用mpi4py来开发在各种HPC平台上无缝运行的并行Python应用程序。

配置环境:

下载MicrosoftMPI:点击此处下载;安装mpi4py库:在终端运行pip install mpi4py,或者使用anaconda的conda install mpi4py命令。

一、先看最简单的情况,也就是函数f在积分域上恒为正值的情况。

下面以定积分eq?%5Cint_%7B1%7D%5E%7B3%7Df%28x%29dx为例,其中eq?f%28x%29%3Dx%5E%7B3%7D-4x%5E%7B2%7D+5x-2e%5E%7Bx%5E%7B2%7D-3x%7D

1.1 蒙特卡洛模拟求定积分的思想

  1. 取y=maxf(x),y=0,x=a(积分下限),x=b(积分上限)围成的矩形域之内的点(x,y),如果f(x)>y,则该点位于y=f(x),y=0,x=a,x=b所围成的区域之内(下面称之为函数域),那么计数器加1;
  2. 重复第1步n(模拟次数)次,之后得到随机点位于函数域之内的概率;
  3. 将2得到的概率乘上矩形域的面积,就可以得到数值积分了。

1.2 求函数最值

在上面的计算中存在一个问题,一个不知道该函数在[a,b]区间的最大值maxf(x),所以还要设计一个求函数最大值的程序。

我们知道函数在最值处的导数值为0,所以只需要将所有区间内导数值为0的点找出来,然后带入函数并与边界值比较就可以得到函数的最大值了。导数的计算又不是一件容易的事。

在这里采用离散化区间并用向前差分代替导数的思想:

  1. 将区间离散化为:eq?x_%7B0%7D%3Da%2Cx_%7B1%7D%2Cx_%7B2%7D...x_%7Bk-1%7D%2Cx_%7Bk%7D%3Db
  2. 用向前差分代替导数eq?f%5E%7B%27%7D%28x_%7Bi%7D%29%3D%5Cfrac%7Bx_%7Bi+1%7D-x_%7Bi%7D%7D%7Bdx%7D,eq?dx为离散化点之间的距离,然后找到导函数值之积为负的相邻两点(零点存在性定理),取算术平均,就得到了函数eq?f%28x%29的近似稳定点了。

dx越小得到的近似稳定点误差越小,之后将所有近似稳定点和边界值带入函数就可以得到函数的近似最大值了。同样的道理,也可以求出函数的近似最小值。也可以用中心差分代替向前差分来求,会有更高的精度。这里没有用求函数最值的库,不然就没意思了。

向前差分法求函数在区间的最大值与最小值代码块如下:

import numpy as np
import random
import math
def func(x):y = x ** 3 - 4 * x ** 2 + 5 * x - 2 * math.exp(x ** 2 - 3 * x)return y
a, b = 1, 3
stable_p=[]
dx = 0.001
lst_x = [i for i in np.arange(a, b+2*dx, dx)]
lst_dy = []
for i in range(len(lst_x)):if i != len(lst_x)-1:dy = (func(lst_x[i+1]) - func(lst_x[i])) / dxlst_dy.append(dy)if i != 0 and lst_dy[i-1] * lst_dy[i] < 0:stable_p.append(lst_x[i] - dx/2)                                       
value = [func(a), func(b)]  # 极值与边界值列表
for j in stable_p:value.append(func(j))                                               
maxi, mini = max(value), min(value)                           
print('函数在[{:},{:}]上稳定点为:{:}'.format(a, b, stable_p))
print('函数在[{:},{:}]上最大值为为:{:4f},最小值为:{:4f}'.format(a, b, maxi, mini))  

结果如下: 

函数在[1,3]上稳定点为:[1.1134999999999875, 1.7094999999999219] 
函数在[1,3]上最大值为为:4.000000,最小值为:1.633509

 下面是f(x)的导数图像:
68c1c49cce2a4c1e9166f5b670451d80.png

 从图像可以看到导数为0的点大约在1.112与1.705附近,所以求出的稳定点是比较准确的。

下面是函数f(x)的图像:

884db11bc44b4d6b9377ecb98ef689f2.png

 算出可以看出最大值与最小值基本准确。

1.3 代码实现

将得到函数的最大值广播到所有进程:

maxi = comm.bcast(maxi if rank == 0 else None, root=0)  # 主进程广播maxi到其他进程

各个进程得到函数最大值后开始模拟:

# 各进程开始模拟 
random.seed(0) 
cnt = 0 
if rank == 0:for i in range(zero_man):x, y = a + (b - a) * random.random(), maxi * random.random()  # 生成矩形区域内的点if func(x) > y:cnt += 1 
else:for i in range(normal_man):x, y = a + (b - a) * random.random(), maxi * random.random()  # 生成矩形区域内的点if func(x) > y:cnt += 1

 通过规约将每个进程的cnt加到主进程中:

cnt = comm.reduce(cnt, root=0, op=MPI.SUM)  # 将所有进程中的cnt相加到主进程中

主进程计算概率和矩形域面积,最后两者相乘得到数值积分:

if rank == 0:p = cnt / nSquare = maxi * (b - a)positive_funcs_int = p * Squaretb = time.time() - t0bprint('模拟次数:{:},积分值为:{:}'.format(n, positive_funcs_int))

为了和串行程序做对比,在最后加了串行程序,并输出加速比和效率以作并行计算的分析:

# 开始串行
cnt = 0     
t0c = time.time()     
for i in range(n):    x, y = a + (b - a) * random.random(), maxi * random.random()  # 生成矩形区域内的点if func(x) > y:       cnt += 1
p = cnt / n     
Square = maxi * (b - a)     
positive_funcs_int = p * Square     
tc = time.time() - t0c     
print('并行时间:{:0<10.5f}秒'.format(tb))     
print('串行时间:{:0<10.5f}秒'.format(tc))     
print('加速比:{:5f}'.format(tc/tb))     
print('效率:{:5f}'.format(tc/tb/size))

1.4 结果验证及并行性分析

结果验证:

下面以模拟次数一千万次,进程数8为例,在终端调至文件所在路径并输入:

mpiexec -np 8 python int_mpi.py

结果如下:

模拟次数:10000000,积分值为:4.3613472 
并行时间:2.90684000秒 
串行时间:13.9555400秒 
加速比:4.800939 
效率:60.011739%

使用MATLAB的符号积分计算函数int可以得到一个误差很小的积分值

syms f(x)
f(x)=x^3-4*x^2+5*x-2*exp(x^2-3*x);
int_f=double(int(f,[1,3]))
%输出值为4.361952754808197

 使用蒙特卡洛模拟得到的积分值相对误差小于万分之一。

并行分析:模拟次数分别为一万,十万,一百万,一千万;进程数分别为4,8,12,16。

029e5a86bcc34196b3e4fd911f063631.png

efb970c4b0b64c4da220f8cbeeef7472.png

进程数为8时,串行并行用时对比:

522440f91b524bdf9dccdf24950cb318.png

 二、也是一种简单的情况,函数f在积分域上恒为负值

在情况一得到了函数的最小值,主进程改广播最小值,并将其他为最大值的地方改成最小值,判断随机点是否位于函数域内的符号反号,即变成<,最后在得到的积分的那步加上一个负号就可以了,具体改动如下:

mini = comm.bcast(maxi if rank == 0 else None, root=0)  # 主进程广播maxi到其他进程if rank == 0:for i in range(zero_man):x, y = a + (b - a) * random.random(), mini * random.random()  # 生成矩形区域内的点if func(x) < y:cnt += 1
else:for i in range(normal_man):x, y = a + (b - a) * random.random(), mini * random.random()  # 生成矩形区域内的点if func(x) < y:cnt += 1negative_funcs_int = - p * Square

三、一般情况,即函数在积分域上变号

一般情况下,对f(x)作一个变换g(x)=f(x)-minf(x),则g(x)在积分区域上是恒大于零的,这时候套用情况一就得到了定积分\int_{a}^{b}g(x)dx,由定积分的性质可以得到\int_{a}^{b}f(x)dx=\int_{a}^{b}g(x)dx+(b-a)minf(x)

对代码改动部分如下啊:

maxi, mini = max(value), min(value)     
maxi = comm.bcast(maxi if rank == 0 else None, root=0)  # 主进程广播最大值到其他进程
mini = comm.bcast(mini if rank == 0 else None, root=0)  # 主进程广播最小值到其他进程if rank == 0: for i in range(zero_man):x, y = a + (b - a) * random.random(), (maxi - mini) * random.random()  # 生成矩形区域内的点if func(x) - mini > y:cnt += 1 
else:for i in range(normal_man):x, y = a + (b - a) * random.random(), (maxi - mini) * random.random()  # 生成矩形区域内的点if func(x) -mini > y:cnt += 1Square = (maxi - mini) * (b - a)     
funcs_int =p * Square + mini * (b - a)

下面以积分\int_{1}^{3}f(x)dx,其中f(x)=(4x^{2}-5x-2e^{x^{2}-3x})sinx为例,并验证正确性。一亿次模拟运行结果如下:

模拟次数:100000000,积分值为:2.139715230135973 
并行时间:46.0982100秒 
串行时间:205.280740秒 
加速比:4.453118 
效率:55.663969%

函数图像如下:

使用MATLAB符号积分得到该积分的较精确值为2.141742485969445。

 相对误差大约为0.947‰

四、蒙特卡洛模拟求定积分并行程序分析

用蒙特卡洛求数值积分的误差不太好估计,矩形域面积和模拟次数有关,当矩形域面积过大时,就要用更多的点才能得到更准确的概率以得到较准确的积分值,就第一种情况区间面积为8,模拟一百万次比较准确;

该并行程序的算法时间复杂度要主要取决于积分函数,因为每次模拟都要调用函数,如果函数比较复杂,计算量就上来了,但每次调用函数计算量固定,随机数生成是比较快的,所以总的时间复杂度为O(n) ,由情况一的运行情况也可以看出程序的时间复杂度为O(n)

蒙特卡洛模拟是可并行性很好的算法,因为各个进程之间没有交叉任务,只需要各干各的最后汇总即可。

五、代码汇总

5.1 一般情况下的串行并行的合并程序:

from mpi4py import MPI 
import random 
import numpy as np 
import math 
import time 
# 数值积分(x^2-5x-2exp(x^2-3x))sin(x^2),积分区间为1到3.
# 精确值2.141742485969445.def func(x):y = math.sin(x ** 2) * (4 * x ** 2 - 5 * x - 2 * math.exp(x ** 2 - 3 * x))return ycomm = MPI.COMM_WORLD rank = comm.Get_rank()  # 获得线程号 
size = comm.Get_size()  # 获得进程数 
a, b = 1, 3  # 积分区间 
n = 10000000  # 模拟次数 
normal_man = n // size 
t0b = time.time() 
if rank == 0:# 主进程分配任务zero_man = n - normal_man * (size - 1)# 计算函数最大值dy_dic = {}  # 各点的向前差分stable_p = []  # 稳定点dx = 0.001lst_x = [i for i in np.arange(a, b+dx, dx)]lst_dy = []     for i in range(len(lst_x)):if i != len(lst_x)-1:dy = (func(lst_x[i+1]) - func(lst_x[i])) / dx             lst_dy.append(dy)if i != 0 and lst_dy[i-1] * lst_dy[i] < 0:                   stable_p.append(lst_x[i] - dx/2)value = [func(a), func(b)]  # 极值与边界值列表for j in stable_p:value.append(func(j))maxi, mini= max(value), min(value)
maxi = comm.bcast(maxi if rank == 0 else None, root=0)  # 主进程广播最大值到其他进程
mini = comm.bcast(mini if rank == 0 else None, root=0)  # 主进程广播最小值到其他进程 
# 各进程开始模拟 
random.seed(0) 
cnt = 0 
if rank == 0:for i in range(zero_man):x, y = a + (b - a) * random.random(), (maxi - mini) * random.random()  # 生成矩形区域内的点if func(x) - mini > y: cnt += 1 
else: for i in range(normal_man): x, y = a + (b - a) * random.random(), (maxi - mini) * random.random()  # 生成矩形区域内的点 if func(x) - mini > y: cnt += 1 cnt = comm.reduce(cnt, root=0, op=MPI.SUM)  # 将所有进程中的cnt相加到主进程中 
if rank == 0: p = cnt / n Square = (maxi - mini) * (b - a) funcs_int = p * Square + mini * (b - a)tb = time.time() - t0b print('模拟次数:{:}\n积分值为:{:0<10.10f}'.format(n, funcs_int)) # 开始串行 cnt = 0 t0c = time.time() for i in range(n): x, y = a + (b - a) * random.random(), (maxi - mini) * random.random()  # 生成矩形区域内的点 if func(x) - mini > y: cnt += 1 p = cnt / n Square = (maxi - mini) * (b - a) funcs_int =p * Square + mini * (b - a)tc = time.time() - t0c print('并行时间:{:0<10.10f}秒'.format(tb)) print('串行时间:{:0<10.10f}秒'.format(tc)) print('加速比:{:0<10.10f}'.format(tc/tb)) print('效率:{:0<10.9f}%'.format(tc/tb/size*100))

5.2 一般情况下的并行程序:

from mpi4py import MPI  
import random  
import numpy as np  
import math  
import time  
# 数值积分(x^2-5x-2exp(x^2-3x))sin(x^2),积分区间为1到3. 
# 精确值2.141742485969445. def func(x):  y = math.sin(x ** 2) * (4 * x ** 2 - 5 * x - 2 * math.exp(x ** 2 - 3 * x)) return y  comm = MPI.COMM_WORLD rank = comm.Get_rank()  # 获得线程号  
size = comm.Get_size()  # 获得进程数  
a, b = 1, 3  # 积分区间  
n = 10000000  # 模拟次数  
normal_man = n // size  
t0b = time.time()  
if rank == 0: # 主进程分配任务 zero_man = n - normal_man * (size - 1) # 计算函数最大值 dy_dic = {}  # 各点的向前差分 stable_p = []  # 稳定点 dx = 0.001 lst_x = [i for i in np.arange(a, b+dx, dx)]  lst_dy = [] for i in range(len(lst_x)):    if i != len(lst_x)-1:   dy = (func(lst_x[i+1]) - func(lst_x[i])) / dxlst_dy.append(dy)  if i != 0 and lst_dy[i-1] * lst_dy[i] < 0:stable_p.append(lst_x[i] - dx/2)value = [func(a), func(b)]  # 极值与边界值列表 for j in stable_p:  value.append(func(j)) maxi, mini= max(value), min(value) 
maxi = comm.bcast(maxi if rank == 0 else None, root=0)  # 主进程广播最大值到其他进程 
mini = comm.bcast(mini if rank == 0 else None, root=0)  # 主进程广播最小值到其他进程 # 各进程开始模拟  
random.seed(0)  
cnt = 0  
if rank == 0: for i in range(zero_man): x, y = a + (b - a) * random.random(), (maxi - mini) * random.random()  # 生成矩形区域内的点  if func(x) - mini > y:   cnt += 1  
else: for i in range(normal_man):  x, y = a + (b - a) * random.random(), (maxi - mini) * random.random()  # 生成矩形区域内的点   if func(x) - mini > y:  cnt += 1 
cnt = comm.reduce(cnt, root=0, op=MPI.SUM)  # 将所有进程中的cnt相加到主进程中 # 主进程计算结果 
if rank == 0: p = cnt / n Square = (maxi - mini) * (b - a)  funcs_int = p * Square + mini * (b - a)  tb = time.time() - t0b  print('模拟次数:{:}\n积分值为:{:0<10.10f}'.format(n, funcs_int)) print('并行时间:{:0<10.10f}秒'.format(tb))

5.3 一般情况下的串行程序(一般程序,即不需要下mpi4py和MicrosoftMPI,可直接运行):

import random  
import numpy as np  
import math  
import time  
# 数值积分(x^2-5x-2exp(x^2-3x))sin(x^2),积分区间为1到3. 
# 精确值2.141742485969445.  
# 初始化def func(x): y = math.sin(x ** 2) * (4 * x ** 2 - 5 * x - 2 * math.exp(x ** 2 - 3 * x))return ya, b = 1, 3  # 积分区间
n = 10000000  # 模拟次数 # 求函数最值 
dy_dic = {}  # 各点的向前差分     
stable_p = []  # 稳定点     
dx = 0.001     
lst_x = [i for i in np.arange(a, b+dx, dx)]     
lst_dy = []     
for i in range(len(lst_x)):         if i != len(lst_x)-1:dy = (func(lst_x[i+1]) - func(lst_x[i])) / dx lst_dy.append(dy)if i != 0 and lst_dy[i-1] * lst_dy[i] < 0:                                    stable_p.append(lst_x[i] - dx/2)value = [func(a), func(b)]  # 极值与边界值列表
for j in stable_p:value.append(func(j))
maxi, mini= max(value), min(value)# 开始模拟     
cnt = 0      
t0c = time.time()      
for i in range(n):     x, y = a + (b - a) * random.random(), (maxi - mini) * random.random()  # 生成矩形区域内的点if func(x) - mini > y:cnt += 1 
p = cnt / n  
Square = (maxi - mini) * (b - a)  
funcs_int =p * Square + mini * (b - a) 
tc = time.time() - t0c      
print('模拟次数:{:}\n积分值为:{:0<10.10f}'.format(n, funcs_int))
print('运行时间:{:0<10.10f}秒'.format(tc))

有任何问题欢迎发表评论,我看到第一时间会回复。

这篇关于【python库mpi4py的蒙特卡洛法求定积分的并行与串行实现】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/626421

相关文章

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P