Coupled non-linear PDEs using DifferentialEquations.jl and the MOL

Hi everyone,

I am trying to solve 2 coupled non-linear PDEs using the MOL with DifferentialEquations.jl and Finite differences first in 1D and I am stuck somewhere.

My two equations describe the evolution of a porosity wave in a two-phase flow and are, in 1D and in dimensionless form:

\frac{\partial \phi}{\partial t} = - \frac{\partial}{\partial x} (\phi^n (\frac{\partial Pe}{\partial x} + 1)) \\ De \frac{\partial Pe}{\partial t} = \frac{\partial}{\partial x} (\phi^n (\frac{\partial Pe}{\partial x} + 1)) - \phi^m Pe

with De, n and m beeing constants and Φ, the porosity and Pe, the pressure, beeing my two unknows.

My code so far looks like that:

#=
Equations dimensionless:
        ∂ϕ/∂t = - ∂/∂z( ϕ^n (∂/∂z(Pe) + 1) )
    De ∂Pe/∂t =  ∂/∂z( ϕ^n (∂/∂z(Pe) + 1) ) - ϕ^m Pe

discretisation:
        ∂ϕ/∂t = - 1/Δx * ((ϕ_i+1/2) * (((Pe_i+1 - Pe_i) / Δx) + 1)
                        - (ϕ_i-1/2) * (((Pe_i - Pe_i-1) / Δx) + 1))
        ∂Pe/∂t = 1/De * (1/Δx * ((ϕ_i+1/2) * (((Pe_i+1 - Pe_i) / Δx) + 1)
                                - (ϕ_i-1/2) * (((Pe_i - Pe_i-1) / Δx) + 1))
                - ϕ_i * Pe_i)

=#

using DifferentialEquations
using Plots

const nx = 501
const Lx = 150
const Δx = Lx / (nx - 1)

# Constants of the problem
const n = 3
const m = 2
const De = 1

# Initial conditions
const Phi0 = 1
const dPhi1 = 8
const dPhi2 = 1
const z1 = 10
const z2 = 40
const lambda = 1

const dPe1 = dPhi1 / De
const dPe2 = dPhi2 / De

ϕ0 = zeros(nx)
Pe0 = zeros(nx)

for i in 1:nx
    ϕ0[i] =
        Phi0 +
        dPhi1 * exp(-((i - z1)^2) / lambda^2) +
        dPhi2 * exp(-((i - z2)^2) / lambda^2)
    Pe0[i] =
        -dPe1 * exp(-((i - z1)^2) / lambda^2) -
        dPe2 * exp(-((i - z2)^2) / lambda^2)
end


# Definition of the problem
u0 = [ϕ0 Pe0]
p = [De, n, m]
t = [0, 20]

function porosity_wave(du,u,p,t)

    ϕ = @view u[:, 1]
    Pe = @view u[:, 2]
    De, n, m = p

    @inbounds for i in 1:nx
        # boundary condition with dirichlet ϕ = 1, Pe = 0 on both sides
        if (i == 1)
            # porosity
            du[i, 1] =
                - (((ϕ[i+1]^n + ϕ[i]^n) / 2 * ((Pe[i+1] - Pe[i]) / Δx + 1)
                - (ϕ[i]^n + 1^n) / 2 * ((Pe[i] - 0) / Δx + 1)) / Δx)

            # pressure
            du[i, 2] = 1 / De * (
                (((ϕ[i+1]^n + ϕ[i]^n) / 2 * ((Pe[i+1] - Pe[i]) / Δx + 1)
                - (ϕ[i]^n + 1) / 2 * ((Pe[i] - 0) / Δx + 1)) / Δx)
                - ϕ[i]^m * Pe[i])

        elseif (i == nx)

            # porosity
            du[i, 1] =
                - (((1 + ϕ[i]^n) / 2 * ((0 - Pe[i]) / Δx + 1)
                - (ϕ[i]^n + ϕ[i-1]^n) / 2 * ((Pe[i] - Pe[i-1]) / Δx + 1)) / Δx)

            # pressure
            du[i, 2] = 1 / De * (
                (((1 + ϕ[i]^n) / 2 * ((0 - Pe[i]) / Δx + 1)
                - (ϕ[i]^n + ϕ[i-1]^n) / 2 * ((Pe[i] - Pe[i-1]) / Δx + 1)) / Δx)
                - ϕ[i]^m * Pe[i])

        else

            # porosity
            du[i, 1] =
                - (((ϕ[i+1]^n + ϕ[i]^n) / 2 * ((Pe[i+1] - Pe[i]) / Δx + 1)
                - (ϕ[i]^n + ϕ[i-1]^n) / 2 * ((Pe[i] - Pe[i-1]) / Δx + 1)) / Δx)

            # pressure
            du[i, 2] = 1 / De *
                (((ϕ[i+1]^n + ϕ[i]^n) / 2 * ((Pe[i+1] - Pe[i]) / Δx + 1)
                - (ϕ[i]^n + ϕ[i-1]^n) / 2 * ((Pe[i] - Pe[i-1]) / Δx + 1)) / Δx
                - ϕ[i]^m * Pe[i])
        end
    end
end

prob = ODEProblem(porosity_wave, u0, t, p)

# solver for stiff equations
sol = solve(prob, TRBDF2(linsolve=LinSolveGMRES()))

but I don’t find the solution I expect. Here is what ϕ should look like in time:

and here is 1 timestep for example with my code:

So basically it blows up.

As I am quite new to DifferentialEquations.jl and the MOL, did I do something wrong with the way I am discretizing the equations? Or is it the wrong solver for my problem?

Thx a lot in advance,

I see instabilities coming from the left boundary. Check those terms.

I see what you mean, but I don’t think it is a question of boundary. It is the position of my initial anomalies (which should not exist of course as the wave should move from the left to the right with time).

Here is the proof if I change the initial position of the two pics:

At initial conditions:

and here the last timestep:

So the problem seems to lie elsewhere.

Can you try with a direct linear solver? I am not sure you can use the default of LinSolveGMRES.

Or can you try CVODE_BDF from Sundials using the gmres option?

Thx for your help rveltz.

can you try CVODE_BDF from Sundials using the gmres option?

I have tried a couple of solvers ( CVODE_BDF with Sundials, TRBDF2(linsolve=LinSolveFactorize(qr!))…) and they all produce the same results which is this one (here with DefaultLinSolve()):

TRBDF2_DefautlLinSolve

Can you try with a direct linear solver?

I am not sure how to do that with DifferentiaEquations, is LinSolveFactorize(qr!) a direct linear solver for you?

Unfortunately , this is not the solution to my equation.
So I guess two choices if the problem is not the solver: my discretization is wrong or the way I use DifferentialEquations is wrong.

Concerning the discretization, I checked already a couple of time, and it is honestly quite simple as it is similar to a classical nonlinear diffusion equation for the most important term.

For DifferentialEquations, I have almost no expertise but if nothing has shocked Chris, I don’t know what to think.

I have a working example here where they use SNES from Petsc.jl and they compute the Jacobian with a simple backward Euler, so I don’t understand what I am missing https://github.com/JuliaParallel/PETSc.jl/blob/jek/gen/examples/porosity_waves.jl .

If you have any ideas, I will take them!

This won’t calculate properly.

            du[i, 1] = - (((ϕ[i+1]^n + ϕ[i]^n) / 2 * ((Pe[i+1] - Pe[i]) / Δx + 1) -
                (ϕ[i]^n + 1^n) / 2 * ((Pe[i] - 0) / Δx + 1)) / Δx)

You instead have something equivalent to the following:

u[i] = -4
-5

which, -5 on its own is a valid expression so it won’t error, but it also is not going to give u[i] = -9.

2 Likes

Thx for the reply.

I see what you mean, with an issue similar to this topic: Best practice for avoiding line break bugs in long expressions

So I’ve put everything on 1 line again (which is not great for readibility but anyway) and… It doesn’t change anything :sweat_smile:

I don’t know why, but julia seems to understand my notation if it is in a loop, like this work:

test = zeros(2)
test2 = zeros(2)

for i in 1:2
    test[i] = 
        1 +
        2
    test2[i] = 1 + 2
    println("test[i]", test[i])
    println("test2[i]", test2[i])
end

shows:

test[i]3.0
test2[i]3.0
test[i]3.0
test2[i]3.0

So anyway, I am back to my problem.

you just need to end the line with an opertor +, * etc so that Julia knows it must parse the next line

2 Likes

you just need to end the line with an opertor +, * etc so that Julia knows it must parse the next line

Thank you for the tips, I’ve learned something.

So, I decided to benchmark properly with the other code from Petsc.jl (I should have done that from the beginning…) and… The solutions are the same (slight difference, but I guess it is because my method is more accurate in time):

test

So I was looking for a bug that doesn’t exist.
Sorry for that, I didn’t expect the solution of my equations to behave exactly that way at these conditions. I’ve learned by experience that generally when it works on the first try, there is something fishy but it wasn’t the case here :sweat_smile:

Thank you for your time!

2 Likes

A side question: it seems to me that you intermittently have negative values in the solution. Given what you are modelling, I was surprised by that. In other words: are you sure that the solution is fine?

Hi vettert,

Thx for your interest on the question.

Short answer:

I am not, and that is why I thought there was a problem with my solution initially. What I don’t know is if this decrease in ϕ is a numerical artefact (oscillations…) or if it is something physically meaningful. There is no analytical solution for this problem and it is quite stiff and nonlinear so there is no easy way to know for what I understand. But I found the same results as the people who implemented the equations in a paper, so it comforts me that it should be correct.

But still, ϕ is never negative (which would be a problem I agree, as it should be the porosity in my media), it is below 1 (sorry if the scale was misleading on my plots).

Long answer:

For a bit of context, these equations are used to model magma ascent in rocks at more or less 20-30 km depth in the crust in a viscous-elastic setting with a compacting rock but an incompressible magma without any shearing.

It’s coming from this paper: “Modeling of compaction driven flow in poro-viscoelastic medium using adaptive wavelet collocation method”, Vasilyev et al, 1998 if you want to know more about the question.

I’ve reproduced the settings in this paper, as did the julia code from Petsc.jl I’ve linked previously.

The figures are quite old so it is a bit tough to read, but here is a figure similar to my model, where on the left is ϕ and on the right is Pe :

This De number tells you if you are more elastic or viscous dominant. A value of 1 is then something in between both extremes.

It is similar to the picture on my first post and hence to my model.

If you zoom in on this graph (yes, a very scientific approach), you can see that they also have a negative porosity in front of the anomalies, so I will say we have the same results, and they used a different method than me.

What does it mean physically? I’m still trying to answer that question.

Maybe nothing, as we don’t have lots of way to see what happens at 20 km depth and on that timescale and it may be dominated by other processes (the joy of geophysics).

Otherwise, it means that magma that is ascending in these 2 anomalies is dragging so much that you are below the value of 1 at its initial position for a while.

I hope I didn’t confuse you with all of that!