这个微分方程可以很容易地解析求解:
dy/dt = 0.01 * y * (1-y)
重新排列以收集对边的 y 和 t 项
100 dt = 1/(y * (1-y)) dy
lhs 微积分到 100 * t,rhs 稍微复杂一些。我们总是可以将两个商的乘积写成两个商 * 一些常数的和:
1/(y * (1-y)) = A/y + B/(1-y)
A 和 B 的值可以通过将 rhs 放在同一个分母上并比较两边的常数项和一阶 y 项来计算。在这种情况下很简单,A=B=1。因此我们必须整合
1/y + 1/(1-y) dy
第一项积分到 ln(y),第二项可以通过变量 u = 1-y 到 -ln(1-y) 的变化积分。因此,我们的积分方程如下:
100 * t + C = ln(y) - ln(1-y)
不要忘记积分常数(这里写在lhs上很方便)。我们可以结合这两个对数项:
100 * t + C = ln( y / (1-y) )
为了将 t 求解为 y 的精确值,我们首先需要计算出 C 的值。我们使用初始条件来执行此操作。很明显,如果 y 从 1 开始,则 dy/dt = 0,并且 y 的值永远不会改变。因此,在开头插入 y 和 t 的值
100 * 0 + C = ln( y(0) / (1 - y(0) )
这将为 C 提供一个值(假设 y 不是 0 或 1),然后使用 y=0.8 来获得 t 的值。请注意,由于对数和因子 100 乘以 ty 将在相对较短的 t 值范围内达到 0.8,除非 y 的初始值非常小。当然,重新排列上面的等式以用 t 表示 y 也很简单,然后您也可以绘制函数。
编辑:数值积分
对于无法解析求解的更复杂的 ODE,您将不得不尝试进行数值计算。最初我们只知道函数在零时刻 y(0) 的值(我们至少必须知道这一点才能唯一地定义函数的轨迹),以及如何评估梯度。数值积分的想法是,我们可以利用我们对梯度的了解(它告诉我们函数是如何变化的)来计算函数在我们起点附近的值是多少。最简单的方法是欧拉积分:
y(dt) = y(0) + dy/dt * dt
欧拉积分假设梯度在 t=0 和 t=dt 之间是恒定的。一旦 y(dt) 已知,梯度也可以在那里计算,进而用于计算 y(2 * dt) 等等,逐步建立函数的完整轨迹。如果您正在寻找一个特定的目标值,只需等到轨迹超过该值,然后在最后两个位置之间进行插值以获得精确的 t。
欧拉积分(以及所有其他数值积分方法)的问题在于其结果仅在其假设有效时才是准确的。由于成对时间点之间的梯度不是恒定的,因此每个积分步骤都会产生一定量的误差,随着时间的推移,误差会逐渐累积,直到答案完全不准确。为了提高积分的质量,有必要对梯度使用更复杂的近似。例如,查看 Runge-Kutta 方法,这是一个积分器系列,以增加计算时间为代价去除误差项的渐进阶数。如果你的函数是可微的,知道二阶甚至三阶导数也可以用来减少积分误差。
当然幸运的是,其他人在这里完成了艰苦的工作,您不必太担心解决诸如数值稳定性之类的问题或对所有细节有深入的了解(尽管大致了解正在发生的事情有很大帮助)。查看http://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.ode.html#scipy.integrate.ode以获取您应该能够立即使用的集成器类的示例。例如
from scipy.integrate import ode
def deriv(t, y):
return 0.01 * y * (1 - y)
my_integrator = ode(deriv)
my_integrator.set_initial_value(0.5)
t = 0.1 # start with a small value of time
while t < 3000:
y = my_integrator.integrate(t)
if y > 0.8:
print "y(%f) = %f" % (t, y)
break
t += 0.1
当 y 超过 0.8 时,此代码将打印出第一个 t 值(如果它从未达到 0.8,则不打印)。如果您想要更准确的 t 值,请保留前一个 t 的 y 并在它们之间进行插值。