Runtime error: Factor is exactly singular

Runtime error: Factor is exactly singular

我正在尝试实现 2 个温度模型,以下等式:

  1. C_e(∂T_e)/∂t=∇[k_e∇T_e ]-G(T_e-T_ph )+ A(r,t)

  2. C_ph(∂T_ph)/∂t=∇[k_ph∇T_ph] + G(T_e-T_ph)

代码

from fipy.tools import numerix
import  scipy
import fipy
import numpy as np
from fipy import CylindricalGrid1D
from fipy import Variable, CellVariable, TransientTerm, DiffusionTerm, Viewer, LinearLUSolver, LinearPCGSolver, \
    LinearGMRESSolver, ImplicitDiffusionTerm, Grid1D

FIPY_SOLVERS = scipy

## Mesh


nr = 50
dr = 1e-7
# r = nr * dr
mesh = CylindricalGrid1D(nr=nr, dr=dr, origin=0)
x = mesh.cellCenters[0]

# Variables
T_e = CellVariable(name="electronTemp", mesh=mesh,hasOld=True)
T_e.setValue(300)
T_ph = CellVariable(name="phononTemp", mesh=mesh, hasOld=True)
T_ph.setValue(300)
G = CellVariable(name="EPC", mesh=mesh)
t = Variable()
# Material parameters
C_e = CellVariable(name="C_e", mesh=mesh)
k_e = CellVariable(name="k_e", mesh=mesh)

C_ph = CellVariable(name="C_ph", mesh=mesh)
k_ph = CellVariable(name="k_ph", mesh=mesh)

C_e = 4.15303 - (4.06897 * numerix.exp(T_e / -85120.8644))
C_ph = 4.10446 - 3.886 * numerix.exp(-T_ph / 373.8)
k_e = 0.1549 * T_e**-0.052
k_ph =1.24 + 16.29 * numerix.exp(-T_ph / 151.57)


G = numerix.exp(21.87 + 10.062 * numerix.log(numerix.log(T_e )- 5.4))

# Boundary conditions
T_e.constrain(300, where=x > 4.5e-6)
T_ph.constrain(300, where=x > 4.5e-6)

# Source  (,) = ()−1 −/   , () =  exp (−2/2)/√22
sig = 1.0e-6
tau = 1e-15
S_e = 35

d_r = (S_e * 1.6e-9 * numerix.exp(-x**2 /sig**2)) / (numerix.sqrt(2. * 3.14 * sig**2))
A_t = numerix.exp(-t/tau)
a = (numerix.sqrt(2. * 3.14)) / (3.14 * sig)

A_r = a * d_r * tau**-1 * A_t 



eq0 = (TransientTerm(var=T_e, coeff=C_e) == DiffusionTerm(var=T_e, coeff=k_e) - G*(T_e - T_ph) + A_r

eq1 =(TransientTerm(var=T_ph, coeff=C_ph) == DiffusionTerm(var=T_ph, coeff=k_ph) + G*(T_e - T_ph)
eq = eq0 & eq1

dt = 1e-18
steps = 7000
elapsed = 0.
vi = Viewer((T_e, T_ph), datamin=0., datamax=2e4)


for step in range(steps):
    T_e.updateOld()
    T_ph.updateOld()
    vi.plot()
    res = 1e100
    dt *= 1.1
    while res > 1:
        res = eq.sweep(dt=dt)
        print(t, res)
    t.setValue(t + dt)

问题

代码在非常小的 dt = 1e-18 上工作正常,但我需要 运行 它直到 e 1e-10

这个时间步将花费很长时间,设置dt *= 1.1 resduals 在某个点开始增加然后

给出以下 运行时间错误:

factor is exactly singular

即使增量很小 dt*= 1.005 也会弹出同样的问题。 使用 dt= 1.001 运行s 长时间退出的代码,然后残差卡在某个值。

问题

  1. 方程式的 fipy 形式有什么错误吗?
  2. 错误原因是什么?
  3. 是时间步长增加导致的错误吗?如果是,我怎样才能增加我的时间步长?

我对代码进行了一些更改,可以使您的运行时间达到 1e-10。主要变化是

  • G 的术语使用 ImplicitSourceTerm。这稳定了解决方案。
  • 在扫描步骤中应用了underRelaxation=0.5。这会减慢扫描循环中的更新速度,从而抑制反馈循环。
  • 已删除 FIPY_SOLVERS=scipy。这没有做任何事情。 FIPY_SOLVERS 是您在 Python 环境之外设置的环境变量。
  • 边界条件的应用方式似乎很奇怪,所以我以更规范的方式应用它们。
  • 扫描循环固定为 10 次扫描以快速达到稳定状态。请注意,随着解接近稳定稳态,残差不一定会变好。如果您需要准确的瞬态,可能想回到残差检查。
from fipy.tools import numerix
import  scipy
import fipy
import numpy as np
from fipy import CylindricalGrid1D
from fipy import Variable, CellVariable, TransientTerm, DiffusionTerm, Viewer, LinearLUSolver, LinearPCGSolver, \
    LinearGMRESSolver, ImplicitDiffusionTerm, Grid1D, ImplicitSourceTerm

## Mesh


nr = 50
dr = 1e-7
# r = nr * dr
mesh = CylindricalGrid1D(nr=nr, dr=dr, origin=0)
x = mesh.cellCenters[0]

# Variables
T_e = CellVariable(name="electronTemp", mesh=mesh,hasOld=True)
T_e.setValue(300)
T_ph = CellVariable(name="phononTemp", mesh=mesh, hasOld=True)
T_ph.setValue(300)
G = CellVariable(name="EPC", mesh=mesh)
t = Variable()
# Material parameters
C_e = CellVariable(name="C_e", mesh=mesh)
k_e = CellVariable(name="k_e", mesh=mesh)

C_ph = CellVariable(name="C_ph", mesh=mesh)
k_ph = CellVariable(name="k_ph", mesh=mesh)

C_e = 4.15303 - (4.06897 * numerix.exp(T_e / -85120.8644))
C_ph = 4.10446 - 3.886 * numerix.exp(-T_ph / 373.8)
k_e = 0.1549 * T_e**-0.052
k_ph =1.24 + 16.29 * numerix.exp(-T_ph / 151.57)


G = numerix.exp(21.87 + 10.062 * numerix.log(numerix.log(T_e )- 5.4))

# Boundary conditions
T_e.constrain(300, where=mesh.facesRight)
T_ph.constrain(300, where=mesh.facesRight)

# Source  (,) = ()−1 −/   , () =  exp (−2/2)/√22
sig = 1.0e-6
tau = 1e-15
S_e = 35

d_r = (S_e * 1.6e-9 * numerix.exp(-x**2 /sig**2)) / (numerix.sqrt(2. * 3.14 * sig**2))
A_t = numerix.exp(-t/tau)
a = (numerix.sqrt(2. * 3.14)) / (3.14 * sig)

A_r = a * d_r * tau**-1 * A_t



eq0 = (
    TransientTerm(var=T_e, coeff=C_e) == \
    DiffusionTerm(var=T_e, coeff=k_e) - \
    ImplicitSourceTerm(coeff=G, var=T_e) + \
    ImplicitSourceTerm(var=T_ph, coeff=G) + \
    A_r)

eq1 = (TransientTerm(var=T_ph, coeff=C_ph) == DiffusionTerm(var=T_ph, coeff=k_ph) + ImplicitSourceTerm(var=T_e, coeff=G) - ImplicitSourceTerm(coeff=G, var=T_ph))
eq = eq0 & eq1

dt = 1e-18
steps = 7000
elapsed = 0.
vi = Viewer((T_e, T_ph), datamin=0., datamax=2e4)


for step in range(steps):
    T_e.updateOld()
    T_ph.updateOld()
    vi.plot()
    res = 1e100
    dt *= 1.1
    count = 0
    while count < 10:
        res = eq.sweep(dt=dt, underRelaxation=0.5)
        print(t, res)
        count += 1
    print('elapsed:', t.value)
    t.setValue(t + dt)

关于您的问题。

I there any error in the fipy formalism of the equations?

实际上,没有。形式主义没有错,但最好使用 ImplicitSourceTerm.

What causes the error?

这个系统有两个不稳定的来源。方程中的源项在明确写入时在特定时间步长以上不稳定。使用 ImplcitSourceTerm 消除了这种不稳定性。方程的耦合也存在某种不稳定性。我认为在放松的情况下使用会有所帮助。

Is the error because of time step increase? If yes, how can I increase my time step?

以上解释。

除了@wd15的回答:

你的方程非常非线性。您可能会受益于 Newton iterations 以获得良好的收敛。

正如@TimRoberts 所说,无限制地以几何方式增加时间步长可能不是一个好主意。

我最近发布了一个名为 steppyngstounes 的包,它负责调整时间步长。虽然是一个独立的包,但它旨在与 FiPy 一起使用。例如,您可以将求解循环更改为:

from steppyngstounes import FixedStepper, PIDStepper

T_e.updateOld()
T_ph.updateOld()

for checkpoint in FixedStepper(start=0, stop=1e-10, size=1e-12):
    for step in PIDStepper(start=checkpoint.begin,
                           stop=checkpoint.end,
                           size=dt):
        res = 1e100
        for sweep in range(10):
            res = eq.sweep(dt=dt, underRelaxation=0.5)
            print(t, sweep, res)
        if step.succeeded(error=res / 1000):
            T_e.updateOld()
            T_ph.updateOld()
            t.value = step.end
        else:
            T_e.value = T_e.old
            T_ph.value = T_ph.old

        print('elapsed:', t.value)

    # the last step might have been smaller than possible,
    # if it was near the end of the checkpoint range
    dt = step.want
    _ = checkpoint.succeeded()
    vi.plot()

此代码将每 1e-12 个时间单位更新一次查看器,并自适应地在这些检查点之间切换。程序包中还有其他步进器可以帮助采用几何或指数增长的检查点,如果这能让事情变得更有趣的话。

通过减少扫描次数并让适配器在开始时采取更小的时间步长,您可能会获得更好的整体性能。我发现没有时间步小到足以使初始残差低于 777.9。在前几个步骤之后,误差指标可能会更加激进,从而提供更准确的结果。