Skip to main content Link Search Menu Expand Document (external link)

Modified Picard Iteration for the Mixed-Form Richards Equation

Table of contents
  1. Code
    1. Main program
    2. Aux. programs
  2. Output
    1. Figures
    2. Text

Code

Main program

sp_08_02.m | View on GitHub
% Supplemental program 8.2

% ---------------------------------------------------------------------
% Use modified Picard iteration to solve the Richards equation for
% infiltration with surface soil moisture as the boundary condition.
% ---------------------------------------------------------------------

% --- Define soil layers

% Number of soil layers

soil.nsoi = 150;

% Soil layer thickness (cm)

for i = 1:soil.nsoi
   soil.dz(i) = 1.0;
end

% Soil depth (cm) at i+1/2 interface between layers i and i+1 (negative distance from surface)

soil.z_plus_onehalf(1) = -soil.dz(1);
for i = 2:soil.nsoi
   soil.z_plus_onehalf(i) = soil.z_plus_onehalf(i-1) - soil.dz(i);
end

% Soil depth (cm) at center of layer i (negative distance from surface)

soil.z(1) = 0.5 * soil.z_plus_onehalf(1);
for i = 2:soil.nsoi
   soil.z(i) = 0.5 * (soil.z_plus_onehalf(i-1) + soil.z_plus_onehalf(i));
end

% Thickness between between z(i) and z(i+1)

for i = 1:soil.nsoi-1
   soil.dz_plus_onehalf(i) = soil.z(i) - soil.z(i+1);
end
soil.dz_plus_onehalf(soil.nsoi) = 0.5 * soil.dz(soil.nsoi);

% --- Soil parameters

 soil.functions = 'van_Genuchten';  % Use van Genuchten relationships
%soil.functions = 'Campbell';       % Use Campbell relationships

switch soil.functions

   case 'Campbell'
   % example from Hornberger & Wiberg (2005, Fig. 8.3)
   ityp = 0;              % Soil texture flag
   theta_sat = 0.25;      % Volumetric water content at saturation
   psi_sat = -25.0;       % Matric potential at saturation (cm)
   bc = 0.2;              % Exponent
   Ksat = 3.4e-03;        % Hydraulic conductivity at saturation (cm/s)
   params = [theta_sat psi_sat bc Ksat ityp];

   case 'van_Genuchten'

   % Haverkamp et al. (1977): sand !!! DOES NOT WORK !!!
%  ityp = 1;              % Soil texture flag
%  theta_res = 0.075;     % Residual water content
%  theta_sat = 0.287;     % Volumetric water content at saturation
%  vg_alpha = 0.027;      % Inverse of the air entry potential (/cm)
%  vg_n = 3.96;           % Pore-size distribution index
%  vg_m = 1;              % Exponent
%  Ksat = 34 / 3600;      % Hydraulic conductivity at saturation (cm/s)

   % Haverkamp et al. (1977): Yolo light clay
   ityp = 2;              % Soil texture flag
   theta_res = 0.124;     % Residual water content
   theta_sat = 0.495;     % Volumetric water content at saturation
   vg_alpha = 0.026;      % Inverse of the air entry potential (/cm)
   vg_n = 1.43;           % Pore-size distribution index
   vg_m = 1 - 1 / vg_n;   % Exponent
   Ksat = 0.0443 / 3600;  % Hydraulic conductivity at saturation (cm/s)

   params = [theta_res theta_sat vg_alpha vg_n vg_m Ksat ityp];
end

% --- Initial soil moisture and matric potential

for i = 1:soil.nsoi
   if (ityp == 0)
      soil.theta(i) = 0.10;
   elseif (ityp == 1)
      soil.theta(i) = 0.10;
   elseif (ityp == 2)
      soil.theta(i) = 0.24;
   end
   soil.psi(i) = matric_potential (soil.functions, params, soil.theta(i));
end

% --- Surface boundary condition: saturation (minus some small delta)

soil.theta0 = theta_sat - 1.0e-03;
if (ityp == 1)
   soil.theta0 = 0.267;
end
soil.psi0 = matric_potential (soil.functions, params, soil.theta0);

% --- Convergence criterion for delta_psi and water_balance_error

dpsi_tolerance = 1.0e-06;
water_balance_error = 1.0e-06;

% --- Time step (seconds)

dt = 10;
if (ityp == 1)
   dt = 5;
end

% --- Length of simulation (number of time steps)

% Hornberger & Wiberg: 15, 30, or 60 minutes
if (ityp == 0)
%  ntim = 15 * 60 / dt;
%  ntim = 30 * 60 / dt;
   ntim = 60 * 60 / dt;
end

% Haverkamp et al. (1977) - sand: duration is in hours
if (ityp == 1)
%  ntim = 0.05 * 3600 / dt;
%  ntim = 0.1 * 3600 / dt;
%  ntim = 0.2 * 3600 / dt;
%  ntim = 0.3 * 3600 / dt;
%  ntim = 0.4 * 3600 / dt;
   ntim = 0.8 * 3600 / dt;
end

% Haverkamp et al. (1977) - Yolo light clay: duration is in seconds
if (ityp == 2)
%  ntim = 1.0e4 / dt;
%  ntim = 1.0e5 / dt;
%  ntim = 5.0e5 / dt;
   ntim = 1.0e6 / dt;
end

% --- Initialize accumulators for water balance check

sum_in = 0;
sum_out = 0;
sum_store = 0;

% --- Time stepping loop: NTIM iterations with a time step of DT seconds

for itim = 1:ntim

   % Hour of day

   hour = itim * (dt/86400 * 24);
   fprintf('hour = %8.3f \n',hour)

   % Calculate soil moisture

   [soil] = picard (soil, params, dt, dpsi_tolerance, water_balance_error);

   % Sum fluxes for relative mass balance error

   sum_in = sum_in + abs(soil.Q0) * dt;
   sum_out = sum_out + abs(soil.QN) * dt;
   sum_store = sum_store + soil.dtheta;

   % Save cumulative infiltration

   xout(itim) = hour;
   yout(itim) = sum_in;

end

% --- Print mass balance

fprintf('infiltration (cm) = %8.3f \n',sum_in)
fprintf('drainage (cm) = %8.3f \n',sum_out)
fprintf('storage (cm) = %8.3f \n',sum_store)
relerr = ((sum_in - sum_out) - sum_store) / (sum_in - sum_out) * 100;
fprintf('mass balance error (percent) = %8.3f \n',relerr)

% --- Graph data

plot(soil.theta,soil.z)
xlabel('Volumetric moisture content')
ylabel('Depth (cm)')

% --- Write soil moisture as output file

A = [soil.theta; soil.z];
fileID = fopen('data1.txt','w');
fprintf(fileID,'%12s %12s\n','theta','z');
fprintf(fileID,'%12.3f %12.3f\n', A);
fclose(fileID);

% --- Write cumulative infiltration as output file

B = [xout; yout];
fileID = fopen('data2.txt','w');
fprintf(fileID,'%12s %12s\n','hour','infil');
fprintf(fileID,'%12.5f %12.5f\n', B);
fclose(fileID);

Aux. programs

Campbell.m | View on GitHub
function [theta, K, cap] = Campbell (params, psi)

% -----------------------------
% Campbell (1974) relationships
% -----------------------------

% --- Soil parameters

theta_sat = params(1);    % Volumetric water content at saturation
psi_sat = params(2);      % Matric potential at saturation
b = params(3);            % Exponent
Ksat = params(4);         % Hydraulic conductivity at saturation

% --- Volumetric soil moisture (theta) for specified matric potential (psi)

if (psi <= psi_sat)
   theta = theta_sat * (psi / psi_sat)^(-1/b);
else
   theta = theta_sat;
end

% --- Hydraulic conductivity (K) for specified matric potential (psi)

if (psi <= psi_sat)
   K = Ksat * (theta / theta_sat)^(2*b+3);
else
   K = Ksat;
end

% --- Specific moisture capacity (cap) for specified matric potential (psi)

if (psi <= psi_sat)
   cap = -theta_sat / (b * psi_sat) * (psi / psi_sat)^(-1/b-1);
else
   cap = 0;
end
matric_potential.m | View on GitHub
function [psi] = matric_potential (type, params, theta)

% --- Calculate psi for a given theta

switch type
   case 'van_Genuchten'

   theta_res = params(1);    % Residual water content
   theta_sat = params(2);    % Volumetric water content at saturation
   alpha = params(3);        % Inverse of the air entry potential
   n = params(4);            % Pore-size distribution index
   m = params(5);            % Exponent

   Se = (theta - theta_res) / (theta_sat - theta_res);
   psi = -((Se^(-1/m) - 1)^(1/n)) / alpha;

   case 'Campbell'

   theta_sat = params(1);    % Volumetric water content at saturation
   psi_sat = params(2);      % Matric potential at saturation
   b = params(3);            % Exponent

   psi = psi_sat * (theta / theta_sat)^-b;

end
picard.m | View on GitHub
function [soil] = picard (soil, params, dt, dpsi_tolerance, water_balance_error)

% -------------------------------------------------------------
% Use modified Picard iteration to solve the Richards equation
% -------------------------------------------------------------

% Input
% dt                   ! Time step (s)
% dpsi_tolerance       ! Convergence criterion for dpsi
% water_balance_error  ! Water balance error tolerance
% soil.nsoi            ! Number of soil layers
% soil.functions       ! van Genuchten or Campbell relationships
% soil.dz_plus_onehalf ! Thickness between between z(i) and z(i+1) (cm)
% soil.dz              ! Soil layer thickness (cm)
% soil.psi0            ! Soil surface matric potential boundary condition (cm)
%
% Input/output
% soil.theta           ! Volumetric soil moisture
% soil.psi             ! Matric potential (cm)
%
% Output
% soil.K               ! Hydraulic conductivity (cm H2O/s)
% soil.cap             ! Specific moisture capacity (/cm)
% soil.Q0              ! Infiltration flux (cm H2O/s)
% soil.QN              ! Drainage flux (cm H2O/s)
% soil.dtheta          ! Change in soil moisture (cm H2O)
% soil.err             ! Water balance error (cm H2O)

% --- Initialization

for i = 1:soil.nsoi

   % Save current soil moisture for time n
   theta0(i) = soil.theta(i);

   % Initialize delta_psi to a large value
   dpsi(i) = 1.0e36;

end

% --- Iteration loop

m = 0;
while (max(abs(dpsi)) > dpsi_tolerance) 

   % Increment iteration counter

   m = m + 1;

   % Stop if too many iterations

   if (m > 50)
      error ('Too many iterations')
   end

   % Hydraulic properties for current psi
   % theta - volumetric soil moisture
   % K -  hydraulic conductivity
   % cap - specific moisture capacity

   for i = 1:soil.nsoi
      switch soil.functions
         case 'van_Genuchten'
         [soil.theta(i), soil.K(i), soil.cap(i)] = van_Genuchten (params, soil.psi(i));
         case 'Campbell'
         [soil.theta(i), soil.K(i), soil.cap(i)] = Campbell (params, soil.psi(i));
       end
    end

   % Hydraulic conductivity at i+1/2 interface between layers i and i+1 is the arithmetic mean

   for i = 1:soil.nsoi-1
      K_plus_onehalf(i) = 0.5 * (soil.K(i) + soil.K(i+1));
   end

   % Hydraulic conductivity at i=1/2 between surface (i=0) and first layer i=1

   K_onehalf = soil.K(1);

   % dz at i=1/2 between surface (i=0) and first layer i=1

   dz_onehalf = 0.5 * soil.dz(1);

   % Terms for tridiagonal matrix

   i = 1;
   a(i) = 0;
   c(i) = -K_plus_onehalf(i) / soil.dz_plus_onehalf(i);
   b(i) = soil.cap(i) * soil.dz(i) / dt - a(i) - c(i);
   d(i) = K_onehalf / dz_onehalf * (soil.psi0 - soil.psi(i)) ... 
        - K_plus_onehalf(i) / soil.dz_plus_onehalf(i) * (soil.psi(i) - soil.psi(i+1)) ...
        + K_onehalf - K_plus_onehalf(i) - (soil.theta(i) - theta0(i)) * soil.dz(i) / dt;

   for i = 2:soil.nsoi-1
      a(i) = -K_plus_onehalf(i-1) / soil.dz_plus_onehalf(i-1);
      c(i) = -K_plus_onehalf(i) / soil.dz_plus_onehalf(i);
      b(i) = soil.cap(i) * soil.dz(i) / dt - a(i) - c(i);
      d(i) = K_plus_onehalf(i-1) / soil.dz_plus_onehalf(i-1) * (soil.psi(i-1) - soil.psi(i)) ...
           - K_plus_onehalf(i) / soil.dz_plus_onehalf(i) * (soil.psi(i) - soil.psi(i+1)) ...
           + K_plus_onehalf(i-1) - K_plus_onehalf(i) - (soil.theta(i) - theta0(i)) * soil.dz(i) / dt;
   end

   i = soil.nsoi;
   a(i) = -K_plus_onehalf(i-1) / soil.dz_plus_onehalf(i-1);
   c(i) = 0;
   b(i) = soil.cap(i) * soil.dz(i) / dt - a(i) - c(i);
   d(i) = K_plus_onehalf(i-1) / soil.dz_plus_onehalf(i-1) * (soil.psi(i-1) - soil.psi(i)) ...
        + K_plus_onehalf(i-1) - soil.K(i) - (soil.theta(i) - theta0(i)) * soil.dz(i) / dt;

   % Solve for the change in psi

   [dpsi] = tridiagonal_solver (a, b, c, d, soil.nsoi);

   % Update psi

   for i = 1:soil.nsoi
      soil.psi(i) = soil.psi(i) + dpsi(i);
   end

end

% --- Check water balance

soil.Q0 = -K_onehalf / dz_onehalf * (soil.psi0 - soil.psi(1)) - K_onehalf;
soil.QN = -soil.K(soil.nsoi);

soil.dtheta = 0;
for i = 1:soil.nsoi
   soil.dtheta = soil.dtheta + (soil.theta(i) - theta0(i)) * soil.dz(i);
end

soil.err = soil.dtheta - (soil.QN - soil.Q0) * dt;
if (abs(soil.err) > water_balance_error)
   error ('Water conservation error')
end
tridiagonal_solver.m | View on GitHub
function [u] = tridiagonal_solver (a, b, c, d, n)

% Solve for U given the set of equations R * U = D, where U is a vector
% of length N, D is a vector of length N, and R is an N x N tridiagonal
% matrix defined by the vectors A, B, C each of length N. A(1) and
% C(N) are undefined and are not referenced.
%
%     |B(1) C(1) ...  ...  ...                     |
%     |A(2) B(2) C(2) ...  ...                     |
% R = |     A(3) B(3) C(3) ...                     |
%     |                    ... A(N-1) B(N-1) C(N-1)|
%     |                    ... ...    A(N)   B(N)  |
%
% The system of equations is written as:
%
%    A_i * U_i-1 + B_i * U_i + C_i * U_i+1 = D_i
%
% for i = 1 to N. The solution is found by rewriting the
% equations so that:
%
%    U_i = F_i - E_i * U_i+1

% --- Forward sweep (1 -> N) to get E and F

e(1) = c(1) / b(1);

for i = 2: 1: n-1
   e(i) = c(i) / (b(i) - a(i) * e(i-1));
end

f(1) = d(1) / b(1);

for i = 2: 1: n
   f(i) = (d(i) - a(i) * f(i-1)) / (b(i) - a(i) * e(i-1));
end

% --- Backward substitution (N -> 1) to solve for U

u(n) = f(n);

for i = n-1: -1: 1
   u(i) = f(i) - e(i) * u(i+1);
end
van_Genuchten.m | View on GitHub
function [theta, K, cap] = van_Genuchten (params, psi)

% ----------------------------------
% van Genuchten (1980) relationships
% ----------------------------------

% --- Soil parameters

theta_res = params(1);   % Residual water content
theta_sat = params(2);   % Volumetric water content at saturation
alpha = params(3);       % Inverse of the air entry potential
n = params(4);           % Pore-size distribution index
m = params(5);           % Exponent
Ksat = params(6);        % Hydraulic conductivity at saturation
ityp = params(7);        % Soil texture flag

% --- Effective saturation (Se) for specified matric potential (psi)

if (psi <= 0)
   Se = (1 + (alpha * abs(psi))^n)^-m;
else
   Se = 1;
end

% --- Volumetric soil moisture (theta) for specified matric potential (psi)

theta = theta_res + (theta_sat - theta_res) * Se;

% --- Hydraulic conductivity (K) for specified matric potential (psi)

if (Se <= 1)
   K = Ksat * sqrt(Se) * (1 - (1 - Se^(1/m))^m)^2;

   % Special case for Haverkamp et al. (1977) sand (ityp = 1) and Yolo light clay (ityp = 2)

   if (ityp == 1)
      K = Ksat * 1.175e6 / (1.175e6 + abs(psi)^4.74);
   end
   if (ityp == 2)
      K = Ksat * 124.6/ (124.6 + abs(psi)^1.77);
   end

else

   K = Ksat;

end

% --- Specific moisture capacity (cap) for specified matric potential (psi)

if (psi <= 0)
   num = alpha * m * n * (theta_sat - theta_res) * (alpha * abs(psi))^(n-1);
   den =  (1 + (alpha * abs(psi))^n)^(m+1);
   cap = num / den;
else
   cap = 0;
end

Output

Figures

Figure 1

Text

data1.txt | View on GitHub | View raw
theta            z
       0.494       -0.500
       0.494       -1.500
       0.494       -2.500
       0.494       -3.500
       0.494       -4.500
       0.494       -5.500
       0.493       -6.500
       0.493       -7.500
       0.493       -8.500
       0.493       -9.500
       0.493      -10.500
       0.493      -11.500
       0.493      -12.500
       0.493      -13.500
       0.493      -14.500
       0.492      -15.500
       0.492      -16.500
       0.492      -17.500
       0.492      -18.500
       0.492      -19.500
       0.492      -20.500
       0.491      -21.500
       0.491      -22.500
       0.491      -23.500
       0.491      -24.500
       0.490      -25.500
       0.490      -26.500
       0.490      -27.500
       0.490      -28.500
       0.489      -29.500
       0.489      -30.500
       0.489      -31.500
       0.488      -32.500
       0.488      -33.500
       0.488      -34.500
       0.487      -35.500
       0.487      -36.500
       0.486      -37.500
       0.486      -38.500
       0.485      -39.500
       0.485      -40.500
       0.484      -41.500
       0.483      -42.500
       0.483      -43.500
       0.482      -44.500
       0.481      -45.500
       0.480      -46.500
       0.479      -47.500
       0.478      -48.500
       0.477      -49.500
       0.475      -50.500
       0.474      -51.500
       0.472      -52.500
       0.470      -53.500
       0.468      -54.500
       0.466      -55.500
       0.464      -56.500
       0.461      -57.500
       0.458      -58.500
       0.455      -59.500
       0.451      -60.500
       0.447      -61.500
       0.443      -62.500
       0.438      -63.500
       0.432      -64.500
       0.426      -65.500
       0.419      -66.500
       0.412      -67.500
       0.404      -68.500
       0.395      -69.500
       0.386      -70.500
       0.376      -71.500
       0.366      -72.500
       0.355      -73.500
       0.345      -74.500
       0.335      -75.500
       0.324      -76.500
       0.314      -77.500
       0.305      -78.500
       0.296      -79.500
       0.288      -80.500
       0.281      -81.500
       0.275      -82.500
       0.269      -83.500
       0.264      -84.500
       0.260      -85.500
       0.256      -86.500
       0.253      -87.500
       0.251      -88.500
       0.248      -89.500
       0.247      -90.500
       0.245      -91.500
       0.244      -92.500
       0.243      -93.500
       0.243      -94.500
       0.242      -95.500
       0.242      -96.500
       0.241      -97.500
       0.241      -98.500
       0.241      -99.500
       0.241     -100.500
       0.240     -101.500
       0.240     -102.500
       0.240     -103.500
       0.240     -104.500
       0.240     -105.500
       0.240     -106.500
       0.240     -107.500
       0.240     -108.500
       0.240     -109.500
       0.240     -110.500
       0.240     -111.500
       0.240     -112.500
       0.240     -113.500
       0.240     -114.500
       0.240     -115.500
       0.240     -116.500
       0.240     -117.500
       0.240     -118.500
       0.240     -119.500
       0.240     -120.500
       0.240     -121.500
       0.240     -122.500
       0.240     -123.500
       0.240     -124.500
       0.240     -125.500
       0.240     -126.500
       0.240     -127.500
       0.240     -128.500
       0.240     -129.500
       0.240     -130.500
       0.240     -131.500
       0.240     -132.500
       0.240     -133.500
       0.240     -134.500
       0.240     -135.500
       0.240     -136.500
       0.240     -137.500
       0.240     -138.500
       0.240     -139.500
       0.240     -140.500
       0.240     -141.500
       0.240     -142.500
       0.240     -143.500
       0.240     -144.500
       0.240     -145.500
       0.240     -146.500
       0.240     -147.500
       0.240     -148.500
       0.240     -149.500

data2.txt | View on GitHub | View raw sp_08_02_out.txt (standard output) | View on GitHub | View raw