Issues with Time Stepping in Richards Equation Simulation

Hello everyone,

I am trying to simulate the Richards equations for unsaturated flow in soil. The equation can be written as

where theta describes soil water retention.

I can’t add more than one embedded image, but we also define the hydraulic conductivity as a function of pressure head (h) :

Se^\tau(1-(1-Se^{1/m})^m)^2

where Se is given by:

Se=\frac{\theta-\theta_r}{\theta_s-\theta_r}

The soil water retention theta and the pressure head h are related in the following way:

theta(h)=\theta_r+\frac{\theta_s-\theta_r}{(1 + |\alpha h|^n)^m}

when h < 0. All other parameters theta_s, theta_r, alpha, n, m, tau, and K_s$ are constants.

I tried to implement a Picard iteration, per Unstable solutions when solving Richards’ equation., but the solver converges a lot faster than expected, and I’m pretty sure the solution it finds is incorrect. Additionally, it will only run for sufficiently large dt (around 50-60s minimum), which seems very strange. For any smaller dt, the Newton solver doesn’t converge.

I was feeling pretty confident in my variational form, but I am fairly new to fenics, so I am wondering if I the set-up is incorrect, or if Picard is not implemented correctly in the time loop. My current code is below:

from fenics import *
import matplotlib.pyplot as plt
import numpy as np
import math


# Define Functions 

def h(theta):
    Se = (theta - theta_r)/(theta_s - theta_r)
    return -(((Se**(-1/m))-1)**(1/n))/alpha


def K(theta):        
    Se = (theta - theta_r)/(theta_s - theta_r)
    return K_s*(Se**tau)*(1-(1-(Se**(1/m)))**m)**2


# Define Parameters

theta_r = 0.102
theta_s = 0.5
n = 2
alpha = 0.0335 
m = 1 - 1/n
K_s = 0.00922   
tau = 0.4


# Time and Spatial Variables

nx = 100     # number of intervals
min_x = -1   # min x value
max_x = 0    # max x value

T = 720
dt = 60 
num_steps = math.floor(T/dt) 


# Define Boundary Conditions 

theta_top = Expression('0.2', degree=0) # Must be between theta_r and theta_s
theta_bottom = Expression('0.4', degree=0) 

def boundary_bottom(x):
    return bool(x[0]-min_x < DOLFIN_EPS)

def boundary_top(x):
    return bool(-x[0] < DOLFIN_EPS)


# Create a Mesh and Function Space

mesh = IntervalMesh(nx, min_x, max_x)
V = FunctionSpace(mesh, 'P', 1)

bc1 = DirichletBC(V, theta_top, boundary_top)
bc2 = DirichletBC(V, theta_bottom, boundary_bottom)
bc = [bc1, bc2]


# Define Initial Value 

theta_0 = Expression('-0.2*x[0] + 0.2', degree=1) #Linear profile
theta_n = interpolate(theta_0, V)
theta_k = interpolate(theta_0, V)

# Define Variational Problem 

theta = Function(V)
v = TestFunction(V)

z_ex=Expression('x[0]',degree=1)
z=project(z_ex,V)
G = ((theta-theta_n)/dt)*v*dx + (K(theta_k))*dot(grad(h(theta)+z), grad(v))*dx

# Picard Set-Up

tol = 1.0E-5
max_iter = 25

# Time Loop 

temp_theta_90 = [0]*101
time = [0]*101

t = 0
count = 0
for n in range(num_steps):
    eps = 10000.0 
    iter = 0

    temp_theta = theta_n.vector().get_local()
    
    if count < 101:
        temp_theta_90[count] = temp_theta[90]
        time[count] = t
       
    while eps > tol and iter < max_iter:
        iter += 1
        solve(G == 0, theta, bc)
        diff = theta.vector().get_local() - theta_k.vector().get_local()
        eps = np.linalg.norm(diff, ord=np.Inf)
        theta_k.assign(theta)

    theta_n.assign(theta)
    t += dt
    count += 1

plt.plot(time, temp_theta_90)

I would greatly appreciate any insights you might have. Thank you for your time and assistance!

I used the modified Picard iteration (https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/wr026i007p01483) and the lumped mass maxtrix (https://fenicsproject.org/qa/4284/mass-matrix-lumping/) to solve the Richards equation in CG space, and the results are very good.Maybe you can try with these methods.