使用 Runge-Kutta 求解耦合微分方程
Using Runge-Kutta to solve coupled differential equations
我有一个耦合方程组:流体静力平衡方程、质量连续性方程和理想气体的状态方程。这些是,在数学语法中,
\frac{dP}{dr}=- \rho*g
,
其中\rho
是密度,g
是重力加速度。
\frac{dM}{dr}=4*pi* r^2*\rho
和
p=\rho* k_B* T/(\mu *m_p)
,
其中 k_B
是玻尔兹曼常数,\mu
是平均分子量,m_p
是质子质量。
我想使用 Runge-Kutta 数值技术求解这些耦合方程,在此展示我为解决此问题而设计的 python 代码:
from scipy.constants import m_p,G,k,pi
from pylab import *
#mu may be changed for different molecular composition:
mu=2
def g(r_atm, p_atm):
T=165
return 4*pi*r_atm**2*mu*m_p*p_atm/(k*T)
def f(r_atm,p_atm, m_atm):
T=165
return -mu*m_p*p_atm*G*m_atm/(k*T*r_atm**2)
def rk4_1(g,f, r0, p0,m0, r1, n):
r_atm = [0]*(n + 1)
p_atm = [0]*(n + 1)
m_atm=[0]*(n + 1)
h = (r1 - r0)/n
# h=-20
r_atm[0]=r0
p_atm[0]=p0
m_atm[0]=m0
for i in range(0,10000000):
if p_atm[i]<100000:
k0 = h*g(r_atm[i], p_atm[i])
l0 = h*f(r_atm[i], p_atm[i], m_atm[i])
k1 = h*g(r_atm[i] + 0.5*h, p_atm[i] + 0.5*k0)
l1 = h*f(r_atm[i] + 0.5*h, p_atm[i] + 0.5*l0, m_atm[i]+0.5*k0)
k2 = h*g(r_atm[i] + 0.5*h, p_atm[i] + 0.5*k1)
l2 = h*f(r_atm[i] + 0.5*h, p_atm[i] + 0.5*l1, m_atm[i]+0.5*k1)
k3 = h*g(r_atm[i] + h, p_atm[i] + k2)
l3 = h*f(r_atm[i] + h, p_atm[i] + l2, m_atm[i]+k2)
r_atm[i+1] = r0 + (i+1)*h
p_atm[i+1] = p_atm[i] + (l0 + 2*l1 + 2*l2 + l3)/6
m_atm[i+1] = m_atm[i] + (k0 + 2*k1 + 2*k2 + k3)/6
else:
break
return h, r_atm, p_atm, m_atm
h, r_atm, p_atm, m_atm = rk4_1(g,f, 6.991e7, 1e-6*1e5, 1.898e27, 2.0e7,10000000) #bar to pascals (*1e5)
对于压力 p_atm
、半径 r_atm
和质量 m_atm
的初始条件,我使用我在 h, r_atm, p_atm, m_atm = rk4_1(g,f, 6.991e7, 1e-6*1e5, 1.898e27, 2.0e7,10000000)
中显示的值。请注意,我是从高层大气(初始条件给定的地方)开始处理这个边值问题,然后在大气中向下推进(注意 h 是负数)。我的目的是评估从 10^-1
帕斯卡到 100000
帕斯卡的数值积分。我从 运行 这段代码中得到的结果是,压力在三个步骤中简单地上升到 ~1e+123
,所以显然有一些非常错误的东西在流动,但是换个角度或视角会有所帮助,因为这是我第一次使用 Runga-Kutta 方法。
正如 Wolph 所说,除以 n
可能只会得到 h=0
,具体取决于您使用的 Python 版本。如果您使用 2.x,则应在开头包含 from __future__ import division
,或以其他方式处理除法(例如,除以 float(n)
)。 (哦,我想也许您还打算在循环中使用 n
,而不是硬编码 range(0,10000000)
?代码中存在一些缩进错误,但我猜那只是从这里发布的。)
但这似乎不是主要问题。你说你很早就有高压;当我 运行 它时,它真的很低吗?即使有适当的除法,我得到 p_atm[3] = -2.27e+97
,从那开始,我开始得到无穷大(inf
和 -inf
)和 nan
s.
在不更好地了解具体问题的情况下,很难查看您的实施是否存在错误,或者这是否只是数值不稳定的问题。它 看起来 适合我,但我很可能错过了一些东西(有点难以阅读。)如果这是你第一次使用 Runge–Kutta,我强烈建议使用现有的实施,而不是试图自己把它弄好。数值计算和避免浮点问题可能非常具有挑战性。您已经在使用 scipy
— 为什么不使用他们的 R–K 方法实现或相关的数值积分解决方案?例如,看看 scipy.integrate。如果不出意外,如果 scipy
集成商无法解决您的问题,至少您更清楚自己面临的挑战是什么。
顺便说一句,这是一个使用小数的版本,它似乎工作得更好:
from decimal import Decimal as D
from scipy.constants import m_p,G,k,pi
m_p = D(m_p)
G = D(G)
k = D(k)
pi = D(pi)
# mu may be changed for different molecular composition:
mu = D(2)
def g(r_atm, p_atm):
T = D(165)
return D(4) * pi * r_atm ** D(2) * mu * m_p * p_atm/(k * T)
def f(r_atm,p_atm, m_atm):
T = D(165)
return -mu * m_p * p_atm * G * m_atm/(k * T * r_atm ** D(2))
def rk4_1(g,f, r0, p0,m0, r1, n):
r_atm = [D(0)] * (n + 1)
p_atm = [D(0)] * (n + 1)
m_atm = [D(0)] * (n + 1)
h = (r1 - r0) / n
# h = -20
r_atm[0] = r0
p_atm[0] = p0
m_atm[0] = m0
for i in range(0, 10000000):
if p_atm[i] < 100000:
k0 = h * g(r_atm[i], p_atm[i])
l0 = h * f(r_atm[i], p_atm[i], m_atm[i])
k1 = h * g(r_atm[i] + D('0.5') * h, p_atm[i] + D('0.5') * k0)
l1 = h * f(r_atm[i] + D('0.5') * h, p_atm[i] + D('0.5') * l0,
m_atm[i]+D('0.5') * k0)
k2 = h * g(r_atm[i] + D('0.5') * h, p_atm[i] + D('0.5') * k1)
l2 = h * f(r_atm[i] + D('0.5') * h, p_atm[i] + D('0.5') * l1,
m_atm[i]+D('0.5') * k1)
k3 = h * g(r_atm[i] + h, p_atm[i] + k2)
l3 = h * f(r_atm[i] + h, p_atm[i] + l2, m_atm[i]+k2)
r_atm[i + 1] = r0 + (i + 1) * h
p_atm[i + 1] = p_atm[i] + (l0 + D('2') * l1 + D('2') * l2 +
l3)/D('6')
m_atm[i + 1] = m_atm[i] + (k0 + D('2') * k1 + D('2') * k2 + k3)/D('6')
else:
break
return h, r_atm, p_atm, m_atm
h, r_atm, p_atm, m_atm = rk4_1(
g,
f,
D('6.991e7'),
D('1e-6') * D('1e5'),
D('1.898e27'),
D('2.0e7'),
10000000,
) # bar to pascals (*1e5)
print 'h', h
我有一个耦合方程组:流体静力平衡方程、质量连续性方程和理想气体的状态方程。这些是,在数学语法中,
\frac{dP}{dr}=- \rho*g
,
其中\rho
是密度,g
是重力加速度。
\frac{dM}{dr}=4*pi* r^2*\rho
和
p=\rho* k_B* T/(\mu *m_p)
,
其中 k_B
是玻尔兹曼常数,\mu
是平均分子量,m_p
是质子质量。
我想使用 Runge-Kutta 数值技术求解这些耦合方程,在此展示我为解决此问题而设计的 python 代码:
from scipy.constants import m_p,G,k,pi
from pylab import *
#mu may be changed for different molecular composition:
mu=2
def g(r_atm, p_atm):
T=165
return 4*pi*r_atm**2*mu*m_p*p_atm/(k*T)
def f(r_atm,p_atm, m_atm):
T=165
return -mu*m_p*p_atm*G*m_atm/(k*T*r_atm**2)
def rk4_1(g,f, r0, p0,m0, r1, n):
r_atm = [0]*(n + 1)
p_atm = [0]*(n + 1)
m_atm=[0]*(n + 1)
h = (r1 - r0)/n
# h=-20
r_atm[0]=r0
p_atm[0]=p0
m_atm[0]=m0
for i in range(0,10000000):
if p_atm[i]<100000:
k0 = h*g(r_atm[i], p_atm[i])
l0 = h*f(r_atm[i], p_atm[i], m_atm[i])
k1 = h*g(r_atm[i] + 0.5*h, p_atm[i] + 0.5*k0)
l1 = h*f(r_atm[i] + 0.5*h, p_atm[i] + 0.5*l0, m_atm[i]+0.5*k0)
k2 = h*g(r_atm[i] + 0.5*h, p_atm[i] + 0.5*k1)
l2 = h*f(r_atm[i] + 0.5*h, p_atm[i] + 0.5*l1, m_atm[i]+0.5*k1)
k3 = h*g(r_atm[i] + h, p_atm[i] + k2)
l3 = h*f(r_atm[i] + h, p_atm[i] + l2, m_atm[i]+k2)
r_atm[i+1] = r0 + (i+1)*h
p_atm[i+1] = p_atm[i] + (l0 + 2*l1 + 2*l2 + l3)/6
m_atm[i+1] = m_atm[i] + (k0 + 2*k1 + 2*k2 + k3)/6
else:
break
return h, r_atm, p_atm, m_atm
h, r_atm, p_atm, m_atm = rk4_1(g,f, 6.991e7, 1e-6*1e5, 1.898e27, 2.0e7,10000000) #bar to pascals (*1e5)
对于压力 p_atm
、半径 r_atm
和质量 m_atm
的初始条件,我使用我在 h, r_atm, p_atm, m_atm = rk4_1(g,f, 6.991e7, 1e-6*1e5, 1.898e27, 2.0e7,10000000)
中显示的值。请注意,我是从高层大气(初始条件给定的地方)开始处理这个边值问题,然后在大气中向下推进(注意 h 是负数)。我的目的是评估从 10^-1
帕斯卡到 100000
帕斯卡的数值积分。我从 运行 这段代码中得到的结果是,压力在三个步骤中简单地上升到 ~1e+123
,所以显然有一些非常错误的东西在流动,但是换个角度或视角会有所帮助,因为这是我第一次使用 Runga-Kutta 方法。
正如 Wolph 所说,除以 n
可能只会得到 h=0
,具体取决于您使用的 Python 版本。如果您使用 2.x,则应在开头包含 from __future__ import division
,或以其他方式处理除法(例如,除以 float(n)
)。 (哦,我想也许您还打算在循环中使用 n
,而不是硬编码 range(0,10000000)
?代码中存在一些缩进错误,但我猜那只是从这里发布的。)
但这似乎不是主要问题。你说你很早就有高压;当我 运行 它时,它真的很低吗?即使有适当的除法,我得到 p_atm[3] = -2.27e+97
,从那开始,我开始得到无穷大(inf
和 -inf
)和 nan
s.
在不更好地了解具体问题的情况下,很难查看您的实施是否存在错误,或者这是否只是数值不稳定的问题。它 看起来 适合我,但我很可能错过了一些东西(有点难以阅读。)如果这是你第一次使用 Runge–Kutta,我强烈建议使用现有的实施,而不是试图自己把它弄好。数值计算和避免浮点问题可能非常具有挑战性。您已经在使用 scipy
— 为什么不使用他们的 R–K 方法实现或相关的数值积分解决方案?例如,看看 scipy.integrate。如果不出意外,如果 scipy
集成商无法解决您的问题,至少您更清楚自己面临的挑战是什么。
顺便说一句,这是一个使用小数的版本,它似乎工作得更好:
from decimal import Decimal as D
from scipy.constants import m_p,G,k,pi
m_p = D(m_p)
G = D(G)
k = D(k)
pi = D(pi)
# mu may be changed for different molecular composition:
mu = D(2)
def g(r_atm, p_atm):
T = D(165)
return D(4) * pi * r_atm ** D(2) * mu * m_p * p_atm/(k * T)
def f(r_atm,p_atm, m_atm):
T = D(165)
return -mu * m_p * p_atm * G * m_atm/(k * T * r_atm ** D(2))
def rk4_1(g,f, r0, p0,m0, r1, n):
r_atm = [D(0)] * (n + 1)
p_atm = [D(0)] * (n + 1)
m_atm = [D(0)] * (n + 1)
h = (r1 - r0) / n
# h = -20
r_atm[0] = r0
p_atm[0] = p0
m_atm[0] = m0
for i in range(0, 10000000):
if p_atm[i] < 100000:
k0 = h * g(r_atm[i], p_atm[i])
l0 = h * f(r_atm[i], p_atm[i], m_atm[i])
k1 = h * g(r_atm[i] + D('0.5') * h, p_atm[i] + D('0.5') * k0)
l1 = h * f(r_atm[i] + D('0.5') * h, p_atm[i] + D('0.5') * l0,
m_atm[i]+D('0.5') * k0)
k2 = h * g(r_atm[i] + D('0.5') * h, p_atm[i] + D('0.5') * k1)
l2 = h * f(r_atm[i] + D('0.5') * h, p_atm[i] + D('0.5') * l1,
m_atm[i]+D('0.5') * k1)
k3 = h * g(r_atm[i] + h, p_atm[i] + k2)
l3 = h * f(r_atm[i] + h, p_atm[i] + l2, m_atm[i]+k2)
r_atm[i + 1] = r0 + (i + 1) * h
p_atm[i + 1] = p_atm[i] + (l0 + D('2') * l1 + D('2') * l2 +
l3)/D('6')
m_atm[i + 1] = m_atm[i] + (k0 + D('2') * k1 + D('2') * k2 + k3)/D('6')
else:
break
return h, r_atm, p_atm, m_atm
h, r_atm, p_atm, m_atm = rk4_1(
g,
f,
D('6.991e7'),
D('1e-6') * D('1e5'),
D('1.898e27'),
D('2.0e7'),
10000000,
) # bar to pascals (*1e5)
print 'h', h