2

We’re trying to solve a one-dimensional Coupled Continuity-Poisson problem in Fipy. When applying Dirichlet’s conditions, it gives the correct results, but when we change the boundaries conditions to Neumann’s which is closer to our problem, it gives “The Factor is exactly singular’’ error. Any help is highly appreciated. The code is as follows (0<x<2.5):

from fipy import *
from fipy import Grid1D, CellVariable, TransientTerm, DiffusionTerm, Viewer
import numpy as np
import math
import matplotlib.pyplot as plt
from matplotlib import cm
from cachetools import cached, TTLCache #caching to increase the speed of python
cache = TTLCache(maxsize=100, ttl=86400) #creating the cache object: the 
#first argument= the number of objects we store in the cache.
#____________________________________________________
nx=50
dx=0.05
L=nx*dx
e=math.e
m = Grid1D(nx=nx, dx=dx)
print(np.log(e))
#____________________________________________________
phi = CellVariable(mesh=m, hasOld=True, value=0.)
ne = CellVariable(mesh=m, hasOld=True, value=0.)
phi_face = phi.faceValue
ne_face = ne.faceValue
x = m.cellCenters[0]
t0 = Variable()
phi.setValue((x-1)**3)
ne.setValue(-6*(x-1))
#____________________________________________________
@cached(cache)
def S(x,t):
    f=6*(x-1)*e**(-t)+54*((x-1)**2)*e**(-2.*t)
    return f
#____________________________________________________
#Boundary Condition:
valueleft_phi=3*e**(-t0)
valueright_phi=6.75*e**(-t0)
valueleft_ne=-6*e**(-t0)
valueright_ne=-6*e**(-t0)
phi.faceGrad.constrain([valueleft_phi], m.facesLeft)
phi.faceGrad.constrain([valueright_phi], m.facesRight)
ne.faceGrad.constrain([valueleft_ne], m.facesLeft)
ne.faceGrad.constrain([valueright_ne], m.facesRight)
#____________________________________________________
eqn0 = DiffusionTerm(1.,var=phi)==ImplicitSourceTerm(-1.,var=ne)
eqn1 = TransientTerm(1.,var=ne) == 
VanLeerConvectionTerm(phi.faceGrad,var=ne)+S(x,t0) 
eqn = eqn0 & eqn1
#____________________________________________________
steps = 1.e4
dt=1.e-4
T=dt*steps
F=dt/(dx**2)
print('F=',F)
#____________________________________________________
vi = Viewer(phi)
with open('out2.txt', 'w') as output:
    while t0()<T:
        print(t0)
        phi.updateOld()
        ne.updateOld()
        res=1.e30
        #for sweep in range(steps):
        while res > 1.e-4:
            res = eqn.sweep(dt=dt)
        t0.setValue(t0()+dt)
        for m in range(nx):
            output.write(str(phi[m])+' ') #+ os.linesep
        output.write('\n')
        if __name__ == '__main__':
            vi.plot()
#____________________________________________________
data = np.loadtxt('out2.txt')
X, T = np.meshgrid(np.linspace(0, L, len(data[0,:])), np.linspace(0, T, 
len(data[:,0])))
fig = plt.figure(3)
ax = fig.add_subplot(111,projection='3d')
ax.plot_surface(X, T, Z=data)
plt.show(block=True)
  • The code above is not well-formed Python (the indentation doesn't make any sense). Please fix so we can be sure we're diagnosing what you're actually doing. – jeguyer Jul 14 '22 at 14:26
  • There are still some typos that prevent this code from running, but I can work with it, thank you. – jeguyer Jul 18 '22 at 22:34

1 Answers1

0

The issue with these equations, particularly eqn0, is that they admit an infinite number of solutions when Neumann boundary conditions are applied on both boundaries. You can fix this by pinning a value somewhere with an internal fixed value. E.g., based on the analytical solution given in the comments, phi = (x-1)**3 * exp(-t), we can pin phi = 0 at x = 1 with

mask = (m.x > 1-dx/2) & (m.x < 1+dx/2)
largeValue = 1e6
value = 0.
#____________________________________________________
eqn0 = (DiffusionTerm(1.,var=phi)==ImplicitSourceTerm(-1.,var=ne) 
        + ImplicitSourceTerm(mask * largeValue, var=phi) - mask * largeValue * value)

At this point, the solutions still do not agree with the expected solutions. This is because, while you have called ne.faceGrad.constrain() for the left and right boundaries, does not appear in the discretized equations. You can see this if you plot ne; the gradient is zero at both boundaries despite the constraint because FiPy never "sees" the constraint.

What does appear is the flux . By applying fixed flux boundary conditions, I obtain the expected solutions:

ne_left = 6 * numerix.exp(-t0)
ne_right = -9 * numerix.exp(-t0)

eqn1 = (TransientTerm(1.,var=ne) 
        == VanLeerConvectionTerm(phi.faceGrad * m.interiorFaces,var=ne)
        + S(x,t0) 
        + (m.facesLeft * ne_left * phi.faceGrad).divergence
        + (m.facesRight * ne_right * phi.faceGrad).divergence)

You can probably get better convergence properties with

eqn1 = (TransientTerm(1.,var=ne) 
        == DiffusionTerm(coeff=ne.faceValue * m.interiorFaces, var=phi)
        + S(x,t0) 
        + (m.facesLeft * ne_left * phi.faceGrad).divergence
        + (m.facesRight * ne_right * phi.faceGrad).divergence)

but either formulation seems to work.

Note: phi.faceGrad.constrain() is fine, because the flux does appear in DiffusionTerm(coeff=1., var=phi).

Separately, it appears (based on "The Factor is exactly singular") that you are solving with the SciPy LinearLUSolver. The PETSc LinearLUSolver does better, but the baseline value of the solution wanders all over the place. Calling

res = eqn.sweep(dt=dt, solver=LinearGMRESSolver())

also seems to produce stable results (without pinning an internal value). This behavior probably shouldn't be relied on; pinning a value is the right thing to do.

jeguyer
  • 2,379
  • 1
  • 11
  • 15
  • Thank you for your guidance, it really helped us a lot and we could run the program. I have a question. When we pin a fixed value it diverges after spending 0.2 of its given time. This is not the problem when we change the solver as you have pointed out, but the program runs very slowly. Is there any way to solve this problem? Thanks again. – photonics_student Jul 23 '22 at 18:10
  • Pinning with the default solver runs to completion for me. Pinning with the GMRES solver issues a lot of iteration warnings, but runs to completion. Running GMRES without pinning does not seem to converge for the whole range of times, but as I've already said, pinning is the right answer. To help any more, you'll need to tell me about your environment. This works for me with SciPy, which is what I think you're using. – jeguyer Jul 24 '22 at 20:03
  • Please close https://stackoverflow.com/q/72974898/2019542, as it appears to be a duplicate of this question. – jeguyer Jul 24 '22 at 20:04
  • You can speed things up some by changing to `steps = 1.e3` and `dt=1.e-3`. You'll get convergence warnings, but the final solution looks reasonable. – jeguyer Jul 24 '22 at 20:05
  • Thank you very much for helping us this far. The exact solutions of this numerical equation are phi=(x-1)**3 * exp(-t) and ne=-6*(x-1)*exp(-t). In other words, the Coupled Poisson-Drift Equation is satisfied by these values. By comparing the real results(phi and ne) with the given result of Fipy, it can be seen that the results diverge from the specific result after a given time. – photonics_student Jul 28 '22 at 11:50
  • OK, I see. I've revised my answer, based on your analytical solutions to explain why it wasn't working and what to do about it. – jeguyer Jul 30 '22 at 02:47
  • Thank you very much. It solved our problem. – photonics_student Aug 15 '22 at 12:56