求解无溢出错误的对数变换 ODE 系统

发布于 2025-01-18 18:20:37 字数 4820 浏览 5 评论 0原文

我有一个 ODE 系统,其中状态变量和自变量跨越多个数量级(在 t=0 时初始值约为 0,预计到 t=1017 时将变为大约 1010)。我还想确保我的状态变量保持正值。

根据此 Stack Overflow 帖子 ,增强正性的一种方法是对常微分方程进行对数变换来求解变量的对数演化,而不是变量本身。然而,当我用常微分方程尝试此操作时,我收到溢出错误,可能是因为状态变量和时间变量的动态范围/数量级很大。我做错了什么或者对数转换不适用于我的情况吗?

这是一个由 scipy.integrate.solve_ivp 成功解决的最小工作示例:

import numpy as np
from scipy.interpolate import interp1d
from scipy.integrate import solve_ivp

# initialize times at which we are given certain input quantities/parameters
# this is seconds corresponding to the age of the universe in billions of years
times = np.linspace(0.1,10,500) * 3.15e16 

# assume we are given the amount of new mass flowing into the system in units of g/sec
# for this toy example we will assume a log-normal distribution and then interpolate it for our integrator function
mdot_grow_array = np.random.lognormal(mean=0,sigma=1,size=len(times))*1.989e33 / 3.15e7
interp_grow = interp1d(times,mdot_grow_array,kind='cubic')

# assume there is also a conversion efficiency for some fraction of mass to be converted to another form
# for this example we'll assume the fractions are drawn from a uniform random distribution and again interpolate
mdot_convert_array = np.random.uniform(0,0.1,len(times)) / 3.15e16 # fraction of M1 per second converted to M2
interp_convert = interp1d(times,mdot_convert_array,kind='cubic')

# set up our integrator function
def integrator(t,y):
    print('Working on t=',t/3.15e16) # to check status of integration in billions of years
    
    # unpack state variables
    M1, M2 = y
    
    # get the interpolated value of new mass flowing in at this time
    mdot_grow_now = interp_grow(t)
    mdot_convert_now = interp_convert(t)    
    
    # assume some fraction of the mass gets converted to another form 
    mdot_convert = mdot_convert_now * M1 
    
    # return the derivatives
    M1dot = mdot_grow_now - mdot_convert
    M2dot = mdot_convert
    
    return M1dot, M2dot
    
# set up initial conditions and run solve_ivp for the whole time range 
# should start with M1=M2=0 initially but then solve_ivp does not work at all, so just use [1,1] instead
initial_conditions = [1.0,1.0] 

# note how the integrator gets stuck at very small timesteps early on
sol = solve_ivp(integrator,(times[0],times[-1]),initial_conditions,dense_output=True,method='RK23')

这是相同的示例,但现在按照上面引用的 Stack Overflow 帖子进行了日志转换(因为 dlogx/dt = 1/x * dx/dt,我们只需将 LHS 替换为 x*dlogx/dt 并将两边除以 x 即可隔离LHS 上的 dlogx/dt;并且我们确保在状态变量上使用 np.exp() – 现在使用 logx 而不是x ——在积分器函数内):

import numpy as np
from scipy.interpolate import interp1d
from scipy.integrate import solve_ivp

# initialize times at which we are given certain input quantities/parameters
# this is seconds corresponding to the age of the universe in billions of years
times = np.linspace(0.1,10,500) * 3.15e16 

# assume we are given the amount of new mass flowing into the system in units of g/sec
# for this toy example we will assume a log-normal distribution and then interpolate it for our integrator function
mdot_grow_array = np.random.lognormal(mean=0,sigma=1,size=len(times))*1.989e33 / 3.15e7
interp_grow = interp1d(times,mdot_grow_array,kind='cubic')

# assume there is also a conversion efficiency for some fraction of mass to be converted to another form
# for this example we'll assume the fractions are drawn from a uniform random distribution and again interpolate
mdot_convert_array = np.random.uniform(0,0.1,len(times)) / 3.15e16 # fraction of M1 per second converted to M2
interp_convert = interp1d(times,mdot_convert_array,kind='cubic')

# set up our integrator function
def integrator(t,logy):
    print('Working on t=',t/3.15e16) # to check status of integration in billions of years
    
    # unpack state variables
    M1, M2 = np.exp(logy)
    
    # get the interpolated value of new mass flowing in at this time
    mdot_grow_now = interp_grow(t)
    mdot_convert_now = interp_convert(t)    
    
    # assume some fraction of the mass gets converted to another form 
    mdot_convert = mdot_convert_now * M1 
    
    # return the derivatives
    M1dot = (mdot_grow_now - mdot_convert) / M1 
    M2dot = (mdot_convert) / M2
    
    return M1dot, M2dot
    
# set up initial conditions and run solve_ivp for the whole time range 
# should start with M1=M2=0 initially but then solve_ivp does not work at all, so just use [1,1] instead
initial_conditions = [1.0,1.0] 

# note how the integrator gets stuck at very small timesteps early on
sol = solve_ivp(integrator,(times[0],times[-1]),initial_conditions,dense_output=True,method='RK23')
    

I have a system of ODEs where my state variables and independent variable span many orders of magnitude (initial values are around 0 at t=0 and are expected to become about 10¹⁰ by t=10¹⁷). I also want to ensure that my state variables remain positive.

According to this Stack Overflow post, one way to enforce positivity is to log-transform the ODEs to solve for the evolution of the logarithm of a variable instead of the variable itself. However when I try this with my ODEs, I get an overflow error probably because of the huge dynamic range / orders of magnitude of my state variables and time variable. Am I doing something wrong or is log-transform just not applicable in my case?

Here is a minimal working example that is successfully solved by scipy.integrate.solve_ivp:

import numpy as np
from scipy.interpolate import interp1d
from scipy.integrate import solve_ivp

# initialize times at which we are given certain input quantities/parameters
# this is seconds corresponding to the age of the universe in billions of years
times = np.linspace(0.1,10,500) * 3.15e16 

# assume we are given the amount of new mass flowing into the system in units of g/sec
# for this toy example we will assume a log-normal distribution and then interpolate it for our integrator function
mdot_grow_array = np.random.lognormal(mean=0,sigma=1,size=len(times))*1.989e33 / 3.15e7
interp_grow = interp1d(times,mdot_grow_array,kind='cubic')

# assume there is also a conversion efficiency for some fraction of mass to be converted to another form
# for this example we'll assume the fractions are drawn from a uniform random distribution and again interpolate
mdot_convert_array = np.random.uniform(0,0.1,len(times)) / 3.15e16 # fraction of M1 per second converted to M2
interp_convert = interp1d(times,mdot_convert_array,kind='cubic')

# set up our integrator function
def integrator(t,y):
    print('Working on t=',t/3.15e16) # to check status of integration in billions of years
    
    # unpack state variables
    M1, M2 = y
    
    # get the interpolated value of new mass flowing in at this time
    mdot_grow_now = interp_grow(t)
    mdot_convert_now = interp_convert(t)    
    
    # assume some fraction of the mass gets converted to another form 
    mdot_convert = mdot_convert_now * M1 
    
    # return the derivatives
    M1dot = mdot_grow_now - mdot_convert
    M2dot = mdot_convert
    
    return M1dot, M2dot
    
# set up initial conditions and run solve_ivp for the whole time range 
# should start with M1=M2=0 initially but then solve_ivp does not work at all, so just use [1,1] instead
initial_conditions = [1.0,1.0] 

# note how the integrator gets stuck at very small timesteps early on
sol = solve_ivp(integrator,(times[0],times[-1]),initial_conditions,dense_output=True,method='RK23')

And here is the same example but now log-transformed following the Stack Overflow post referenced above (since dlogx/dt = 1/x * dx/dt, we simply replace the LHS with x*dlogx/dt and divide both sides by x to isolate dlogx/dt on the LHS; and we make sure to use np.exp() on the state variables – now logx instead of x – within the integrator function):

import numpy as np
from scipy.interpolate import interp1d
from scipy.integrate import solve_ivp

# initialize times at which we are given certain input quantities/parameters
# this is seconds corresponding to the age of the universe in billions of years
times = np.linspace(0.1,10,500) * 3.15e16 

# assume we are given the amount of new mass flowing into the system in units of g/sec
# for this toy example we will assume a log-normal distribution and then interpolate it for our integrator function
mdot_grow_array = np.random.lognormal(mean=0,sigma=1,size=len(times))*1.989e33 / 3.15e7
interp_grow = interp1d(times,mdot_grow_array,kind='cubic')

# assume there is also a conversion efficiency for some fraction of mass to be converted to another form
# for this example we'll assume the fractions are drawn from a uniform random distribution and again interpolate
mdot_convert_array = np.random.uniform(0,0.1,len(times)) / 3.15e16 # fraction of M1 per second converted to M2
interp_convert = interp1d(times,mdot_convert_array,kind='cubic')

# set up our integrator function
def integrator(t,logy):
    print('Working on t=',t/3.15e16) # to check status of integration in billions of years
    
    # unpack state variables
    M1, M2 = np.exp(logy)
    
    # get the interpolated value of new mass flowing in at this time
    mdot_grow_now = interp_grow(t)
    mdot_convert_now = interp_convert(t)    
    
    # assume some fraction of the mass gets converted to another form 
    mdot_convert = mdot_convert_now * M1 
    
    # return the derivatives
    M1dot = (mdot_grow_now - mdot_convert) / M1 
    M2dot = (mdot_convert) / M2
    
    return M1dot, M2dot
    
# set up initial conditions and run solve_ivp for the whole time range 
# should start with M1=M2=0 initially but then solve_ivp does not work at all, so just use [1,1] instead
initial_conditions = [1.0,1.0] 

# note how the integrator gets stuck at very small timesteps early on
sol = solve_ivp(integrator,(times[0],times[-1]),initial_conditions,dense_output=True,method='RK23')
    

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

热血少△年 2025-01-25 18:20:37

[…]对数变换不适用于我的情况吗?

我不知道你的变换哪里出了问题,但它肯定达不到你想象的效果。当且仅当满足以下两个条件时,对数转换作为避免负值的一种方法才有意义并且有效:

  1. 如果动态变量的值接近零(从上面),则其模型中的导数也接近零(从上面) 。
  2. 由于数值噪声,您的导数可能会变为负值,但实际上并非如此。

相反,在以下情况下没有必要或不起作用:

  • 如果条件 1 失败,因为模型中的导数永远不会接近零,而是严格为正数,那么一开始就没有问题,因为您的导数在模型的任何合理实施中不应变得消极。 (你可能会通过实施一些壮观的数字歼灭来实现这一目标,但这是一项相当困难的壮举,而且不是我认为合理的实施。)

  • 如果条件 1 失败,因为您的导数在模型中变得真正为负,对数不会拯救你,因为动力学想要将导数推到零以下,而对数不能代表这一点。由于对数变得非常负或自适应积分失败,您通常会收到溢出错误。

  • 即使条件 1 适用,条件 2 通常也可以通过在实现模型时避免数值歼灭和类似情况来处理。

除非我弄错了,否则你的模型属于第一类。如果 M1 趋于零,mdot_convert 趋于零,因此 M1dot = mdot_grow_now - mdot_convert 严格为正数,因为 mdot_grow_now > 是。无论如何,M2dot 是严格正数。因此,您不会从对数转换中获得任何好处。事实上,在绝大多数情况下,你的动态变量会迅速增加。

话虽如此,您可能需要研究的一些事情是:

[…] is log-transform just not applicable in my case?

I don’t know where your transform went wrong, but it will certainly not achieve what you think it does. Log-transforming as a means to avoid negative values makes sense and works if and only if the following two conditions hold:

  1. If the value of a dynamical variable approaches zero (from above), its derivative also approaches zero (from above) in your model.
  2. Due to numerical noise, your derivative may turn negative though it actually isn’t.

Conversely, it is not necessary or doesn’t work in the following cases:

  • If Condition 1 fails because your derivative never approaches zero in your model, but is strictly positive, you have no problem to begin with, as your derivative should not become negative in any reasonable implementation of your model. (You might make it happen by implementing some spectacular numerical annihilation, but that’s quite a difficult feat to achieve and not what I would consider a reasonable implementation.)

  • If Condition 1 fails because your derivative becomes truly negative in your model, logarithms won’t save you, because the dynamics wants to push the derivative below zero and the logarithms cannot represent this. You usually get an overflow error due to the logarithms becoming extremely negative or the adaptive integration fails.

  • Even if Condition 1 applies, Condition 2 can usually be handled by avoiding numerical annihilations and similar when implementing your model.

Unless I am mistaken, your model falls into the first category. If M1 goes to zero, mdot_convert goes towards zero and thus M1dot = mdot_grow_now - mdot_convert is strictly positive, because mdot_grow_now is. M2dot is strictly positive anyway. Thus, you gain nothing from log-transforming. In fact, in the vast majority of cases, your dynamical variables will quickly increase.

With all that being said, some things you might want to look into are:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文