【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

相关文章

SpringBoot实现二维码生成的详细步骤与完整代码

《SpringBoot实现二维码生成的详细步骤与完整代码》如今,二维码的应用场景非常广泛,从支付到信息分享,二维码都扮演着重要角色,SpringBoot是一个非常流行的Java基于Spring框架的微... 目录一、环境搭建二、创建 Spring Boot 项目三、引入二维码生成依赖四、编写二维码生成代码五

Python Selenium动态渲染页面和抓取的使用指南

《PythonSelenium动态渲染页面和抓取的使用指南》在Web数据采集领域,动态渲染页面已成为现代网站的主流形式,本文将从技术原理,环境配置,核心功能系统讲解Selenium在Python动态... 目录一、Selenium技术架构解析二、环境搭建与基础配置1. 组件安装2. 驱动配置3. 基础操作模

MyBatisX逆向工程的实现示例

《MyBatisX逆向工程的实现示例》本文主要介绍了MyBatisX逆向工程的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录逆向工程准备好数据库、表安装MyBATisX插件项目连接数据库引入依赖pom.XML生成实体类、

C#实现查找并删除PDF中的空白页面

《C#实现查找并删除PDF中的空白页面》PDF文件中的空白页并不少见,因为它们有可能是作者有意留下的,也有可能是在处理文档时不小心添加的,下面我们来看看如何使用Spire.PDFfor.NET通过C#... 目录安装 Spire.PDF for .NETC# 查找并删除 PDF 文档中的空白页C# 添加与删

Python将字库文件打包成可执行文件的常见方法

《Python将字库文件打包成可执行文件的常见方法》在Python打包时,如果你想将字库文件一起打包成一个可执行文件,有几种常见的方法,具体取决于你使用的打包工具,下面就跟随小编一起了解下具体的实现方... 目录使用 PyInstaller基本方法 - 使用 --add-data 参数使用 spec 文件(

Python MCPInspector调试思路详解

《PythonMCPInspector调试思路详解》:本文主要介绍PythonMCPInspector调试思路详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋... 目录python-MCPInspector调试1-核心知识点2-思路整理1-核心思路2-核心代码3-参考网址

Java实现MinIO文件上传的加解密操作

《Java实现MinIO文件上传的加解密操作》在云存储场景中,数据安全是核心需求之一,MinIO作为高性能对象存储服务,支持通过客户端加密(CSE)在数据上传前完成加密,下面我们来看看如何通过Java... 目录一、背景与需求二、技术选型与原理1. 加密方案对比2. 核心算法选择三、完整代码实现1. 加密上

将图片导入Python的turtle库的详细过程

《将图片导入Python的turtle库的详细过程》在Python编程的世界里,turtle库以其简单易用、图形化交互的特点,深受初学者喜爱,随着项目的复杂度增加,仅仅依靠线条和颜色来绘制图形可能已经... 目录开篇引言正文剖析1. 理解基础:Turtle库的工作原理2. 图片格式与支持3. 实现步骤详解第

Python的pip在命令行无法使用问题的解决方法

《Python的pip在命令行无法使用问题的解决方法》PIP是通用的Python包管理工具,提供了对Python包的查找、下载、安装、卸载、更新等功能,安装诸如Pygame、Pymysql等Pyt... 目录前言一. pip是什么?二. 为什么无法使用?1. 当我们在命令行输入指令并回车时,一般主要是出现以

Java使用WebView实现桌面程序的技术指南

《Java使用WebView实现桌面程序的技术指南》在现代软件开发中,许多应用需要在桌面程序中嵌入Web页面,例如,你可能需要在Java桌面应用中嵌入一部分Web前端,或者加载一个HTML5界面以增强... 目录1、简述2、WebView 特点3、搭建 WebView 示例3.1 添加 JavaFX 依赖3