三重积分,Python,约束积分集

1 投票
4 回答
4108 浏览
提问于 2025-04-16 16:26

我想计算一个球体和一个无限长圆柱体在某个距离b处的交集体积,于是决定用一个简单的Python脚本来实现。我的要求是计算时间少于1秒,并且结果要有超过3位有效数字。

我的想法是这样的:我们把半径为R的球体放在原点,然后把半径为R'的圆柱体放在z轴上,从(b,0,0)开始延伸。接着,我们在球体内部进行积分,使用一个阶跃函数:如果我们在圆柱体内部,返回1;如果不在,则返回0。这样,我们就可以在同时在球体和圆柱体内部的区域进行积分,也就是计算它们的交集。

我尝试使用scipy.integrate.tplquad来实现这个积分,但没有成功。我觉得可能是因为阶跃函数的不连续性,导致我收到了这样的警告。当然,我也可能是做错了什么。如果我没有犯什么低级错误,我可以尝试先确定交集的范围,这样就不需要使用阶跃函数了,但我想先听听大家的反馈。有没有人能发现我的错误,或者指点我一个简单的解决方案。

警告:已达到最大细分数(50)。
如果增加限制没有改善,建议分析
被积函数,以确定困难所在。如果能
确定局部困难的位置(奇点、不连续性),可能
会通过拆分区间并在子区间上调用积分器来获得好处。也许应该使用专用的积分器。

代码:

from scipy.integrate import tplquad
from math import sqrt


def integrand(z, y, x):
    if Rprim >= (x - b)**2 + y**2:
        return 1.
    else:
        return 0.

def integral():
    return tplquad(integrand, -R, R, 
                   lambda x: -sqrt(R**2 - x**2),          # lower y
                   lambda x: sqrt(R**2 - x**2),           # upper y
                   lambda x,y: -sqrt(R**2 - x**2 - y**2), # lower z
                   lambda x,y: sqrt(R**2 - x**2 - y**2), # upper z
                   epsabs=1.e-01, epsrel=1.e-01
                   )

R=1
Rprim=1
b=0.5
print integral()

4 个回答

2

在处理一个在两个区域内是常数的间断函数时,进行三重自适应数值积分是个非常糟糕的主意,尤其是如果你想要速度或准确性的话。

我建议一个更好的方法是从分析上简化这个问题。

可以通过变换将圆柱体与某个轴对齐。这样做会把球体移动到一个不在原点的位置。

接下来,找出球体与圆柱体在这个轴上相交的范围。

然后在这个轴的变量上进行积分。在轴上任何固定值的相交区域,实际上就是两个圆的相交面积,这个面积可以通过三角函数和一些简单的计算得出。

最后,你将得到一个精确的结果,几乎不需要计算时间。

3

假设你能把你的数据处理得很好,让球体的中心点在 [0, 0, 0] 这个位置,并且半径是 1,那么用一种简单的随机近似方法就能很快给你一个不错的答案。所以,下面这个思路可以作为一个好的起点:

import numpy as np

def in_sphere(p, r= 1.):
    return np.sqrt((p** 2).sum(0))<= r

def in_cylinder(p, c, r= 1.):
    m= np.mean(c, 1)[:, None]
    pm= p- m
    d= np.diff(c- m)
    d= d/ np.sqrt(d** 2).sum()
    pp= np.dot(np.dot(d, d.T), pm)
    return np.sqrt(((pp- pm)** 2).sum(0))<= r

def in_sac(p, c, r_c):
    return np.logical_and(in_sphere(p), in_cylinder(p, c, r_c))

if __name__ == '__main__':
    n, c= 1e6, [[0, 1], [0, 1], [0, 1]]
    p= 2* np.random.rand(3, n)- 2
    print (in_sac(p, c, 1).sum()/ n)* 2** 3
1

我按照eat的建议,用简单的蒙特卡罗积分法解决了这个问题,但我的实现速度太慢了。我的需求增加了,所以我按照woodchips的建议,重新从数学上整理了这个问题。

简单来说,我把x的范围设定为z和y的函数,同时把y也设定为z的函数。然后,我实际上是在交集区域内,对f(z,y,z)=1进行积分,使用了这些范围。这样做是为了提高速度,让我能够绘制体积与b的关系图,同时也能让我在稍微修改的情况下,积分更复杂的函数。

我把我的代码放在这里,供有兴趣的人参考。

from scipy.integrate import quad
from math import sqrt
from math import pi

def x_max(y,r):
    return sqrt(r**2-y**2)

def x_min(y,r):
    return max(-sqrt(r**2 - y**2), -sqrt(R**2 - y**2) + b) 

def y_max(r):
    if (R<b and b-R<r) or (R>b and b-R>r):
        return sqrt( R**2 - (R**2-r**2+b**2)**2/(4.*b**2) )
    elif r+R<b:
        return 0.
    else: #r+b<R
        return r

def z_max():
    if R>b:
        return R
    else:
        return sqrt(2.*b*R - b**2) 

def delta_x(y, r):
    return  x_max(y,r) - x_min(y,r)

def int_xy(z):
    r = sqrt(R**2 - z**2)
    return quad(delta_x, 0., y_max(r), args=(r))

def int_xyz():
    return quad(lambda z: int_xy(z)[0], 0., z_max())

R=1.
Rprim=1.
b=0.5
print 4*int_xyz()[0]

撰写回答