1D Transient Transport, Initial Condition

The Problem Description

The PFLOTRAN Input File (SUBSURFACE_TRANSPORT Mode)

The Python Script

The Problem Description

This problem is adapted from Faure, G. (1991), Principles and Applications of Inorganic Chemistry: A Comprehensive Textbook for Geology Students. New York: Macmillan Pub.Co., Section 19.3, pg.395, “Transport of Matter: Diffusion (Fick’s Second Law).”

The domain is a 1D column extending along the positive x-axis and is made up of 35x1x1 cubic grid cells with a length of 20 meters. The material is assigned the following properties: porosity = 1.0; tortuosity = 1.0; diffusion coefficient = 1.0e9 m2/s; which are homogenous in the domain.

The initial concentration of a generic tracer is assigned to 1.0e-20 (a value that is essentially zero but purposefully not exactly zero) everywhere, except at the center cell, where it is assigned a concentration of 20 Molar (mol/L). This initial pulse of tracer will diffuse over time, spreading itself out in both directions.

In PFLOTRAN, the initial concentration pulse in the center cell is homogeneous within that cell. However, the analytical solution describing the solution to Fick’s Second Law for an initial concentration pulse resembles a Dirac Delta function, which has an infinite value over a miniscule width. As the resolution of the grid increases, the initial condition on the PFLOTRAN grid will approach the Dirac Delta, but practically this limit cannot be reached. Therefore, for early times in the simulation, when the Dirac Delta pulse is beginning to relax, the PFLOTRAN solution will not match the analytical solution very well. However, the total amount of solute is the same, which can be verified by taking the integral of the concentration curve (ie, area under the solution curve).

The simulation is run for 80 years. The solution is plotted and compared at 10, 20, 40, and 80 years.

Fick’s Second Law governs the evolving tracer concentration,

\[{{\partial c} \over {\partial t}} = D {{\partial^{2} c} \over {\partial x^{2}}}\]

The solution is given by,

\[c(x) = \frac{c_0}{\sqrt{4 \pi Dt}}e^{-\frac{x^{2}}{4Dt}}\]

where \(D\) is the diffusion coefficient, and \(c_0\) is the initial tracer concentration.

_images/visit_figure23.png

The PFLOTRAN domain set-up.

If you do not see this image, you must run the QA test suite to generate this figure.

Comparison of the PFLOTRAN vs. analytical solution for SUBSURFACE_TRANSPORT mode.

The PFLOTRAN Input File (SUBSURFACE_TRANSPORT Mode)

The SUBSURFACE_TRANSPORT Mode PFLOTRAN input file can be downloaded here.

 
SIMULATION
  SIMULATION_TYPE SUBSURFACE
  PROCESS_MODELS
    SUBSURFACE_TRANSPORT transport
      MODE GIRT
    /
  /
END

SUBSURFACE

CHEMISTRY
  PRIMARY_SPECIES
    tracer
  /
  #LOG_FORMULATION
  OUTPUT
    TOTAL
    ALL
  /
END

NUMERICAL_METHODS TRANSPORT
  LINEAR_SOLVER
    SOLVER DIRECT
  /
END

GRID
  TYPE structured
  NXYZ 575 1 1
  DXYZ
   0.034782608695652174d0
   1.0d0
   1.0d0
  END
END

REGION all
  COORDINATES
    0.0d0 0.0d0 0.0d0
    20.0d0 1.0d0 1.0d0
  /
END

REGION first_cell
  COORDINATES
    0.d0 0.d0 0.d0
    0.d0 1.d0 1.d0
  /
END

REGION middle_cell
  COORDINATES
    9.982608695652173d0 0.0d0 0.0d0
    10.017391304347827d0 1.0d0 1.0d0
  /
END


MATERIAL_PROPERTY soil1
  ID 1
  POROSITY 1.d0
  TORTUOSITY 1.d0
END

FLUID_PROPERTY
  DIFFUSION_COEFFICIENT 1.d-9
END

STRATA
  REGION all
  MATERIAL soil1
END

TIME
  FINAL_TIME 80.d0 y
  INITIAL_TIMESTEP_SIZE 1.d0 h
  MAXIMUM_TIMESTEP_SIZE 1.d-1 yr
END

OUTPUT
  SNAPSHOT_FILE
    TIMES y 10. 20. 40. 80.
    FORMAT HDF5
  /
END

TRANSPORT_CONDITION initial
  TYPE DIRICHLET
  CONSTRAINT_LIST
    0.d0 initial
  /
END

TRANSPORT_CONDITION initial_conc
  TYPE DIRICHLET
  CONSTRAINT_LIST
    0.d0 initial_conc
  /
END

INITIAL_CONDITION initial
  REGION all
  TRANSPORT_CONDITION initial
END

INITIAL_CONDITION intial_conc
  REGION middle_cell
  TRANSPORT_CONDITION initial_conc
END


CONSTRAINT initial_conc
  CONCENTRATIONS 
    tracer 20.d0      T
  /
END

CONSTRAINT initial
  CONCENTRATIONS
    tracer 1.d-20     T
  /
END

END_SUBSURFACE

The Python Script

def transport_transient_1D_IC(path,input_prefix,remove,screen_on,pf_exe,mpi_exe,
                                                                      num_tries):
#==============================================================================#
# Based on: 
# Faure, G.(1991). Principles and Applications of Inorganic Geochemistry: 
# A Comprehensive Textbook for Geology Students. New York: Macmillan Pub.Co.      
# Section 19.3, pg.395 
# "TRANSPORT OF MATTER: DIFFUSION" (Fick's Second Law)
# 
# Fick's Second Law (Eqn. 19.63)
# c = (pi * D * t)**-0.5 * e**(- x**2 / (4 * D * t)) * c_i
#
#    where, 
#
#        t is time
#        x is distance
#        c is solute concentration at a given time
#        D is diffusion coefficient
#      c_i is initial solute concentration 
# 
# Date: 02/13/18
#*******************************************************************************
  nxyz = np.zeros(3) + 1
  dxyz = np.zeros(3) + 1.
  lxyz = np.zeros(3) + 1.
  error_analysis = np.zeros(num_tries)
  dxyz_record = np.zeros((num_tries,3))
  nxyz_record = np.zeros((num_tries,3))
  test_pass = False
  try_count = 0

  # Initial Discretization
  # Start refined enough that default NUM_TRIES=3 reaches <2% max relative error
  # (pure diffusion; coarser starts need 5+ doublings to pass the 2% criterion).
  lxyz[0] = 20.      # [m] lx
  nxyz[0] = 143      # [-] nx --> must start with odd number
  dxyz = lxyz/nxyz   # [m]                       
  pi = math.pi       # constant
  D = 1.0e-9         # [m^2/sec]
  c_i = 20.          # [M] concentration

  while (not test_pass) and (try_count < num_tries):
    print_discretization(lxyz,nxyz,dxyz)
    nx = int(nxyz[0]); ny = int(nxyz[1]); nz = int(nxyz[2])
    dx = dxyz[0]; dy = dxyz[1]; dz = dxyz[2]
    Lx = lxyz[0]; Ly = lxyz[1]; Lz = lxyz[2]
    try_count = try_count + 1
  
    x_soln = np.linspace(0.+(dx/2.),Lx-(dx/2.),nx) - Lx/2.  # [m]
    x_pflotran = np.linspace(0.+(dx/2.),Lx-(dx/2.),nx)      # [m]
    t_soln = np.array([10.,20.,40.,80.])                    # [y]
    c_soln = np.zeros((4,nx))                               # [M]
    c_pflotran = np.zeros((4,nx))                           # [M]

    vol = dx*dy*dz
    #print('cell volume = ' + str(vol) + ' m3')

    # create the analytical solution
    for time in range(4):
      t = t_soln[time]*365.0*24.0*3600.0     # [years -> sec]
      c_soln[time,:] = \
               (vol*c_i)*pow((4*pi*D*t),-0.5)*np.exp(-pow(x_soln,2.)/(4.*D*t))

    # run PFLOTRAN simulation
    run_pflotran(input_prefix,nxyz,dxyz,lxyz,remove,screen_on,pf_exe,mpi_exe)

    index_string = 'Time:  1.00000E+01 y/Total_tracer [M]'
    c_pflotran[0,:] = read_pflotran_output_1D(path+
                '/transient_1D_IC_subsurface_transport.h5',index_string,False)
    index_string = 'Time:  2.00000E+01 y/Total_tracer [M]'
    c_pflotran[1,:] = read_pflotran_output_1D(path+
                '/transient_1D_IC_subsurface_transport.h5',index_string,False)
    index_string = 'Time:  4.00000E+01 y/Total_tracer [M]'
    c_pflotran[2,:] = read_pflotran_output_1D(path+
                '/transient_1D_IC_subsurface_transport.h5',index_string,False)
    index_string = 'Time:  8.00000E+01 y/Total_tracer [M]'
    c_pflotran[3,:] = read_pflotran_output_1D(path+
                '/transient_1D_IC_subsurface_transport.h5',index_string,remove)
    ierr = check(c_pflotran)
    
    for g in range(4):
      area_soln = 0.
      area_p_soln = 0.
      for k in range(nx):
        area_soln = area_soln + c_soln[g,k]*dx
        area_p_soln = area_p_soln + c_pflotran[g,k]*dx 
      #print('t='+str(g)+' analytical soln area = ' + str(area_soln) )
      #print('t='+str(g)+'   pflotran soln area = ' + str(area_p_soln) )

    max_percent_error = calc_relative_error(c_soln,c_pflotran,ierr)
    record_error(error_analysis,nxyz_record,dxyz_record,max_percent_error,
                 nxyz,dxyz,try_count)
    test_pass = does_pass(max_percent_error,try_count,num_tries)
    nxyz[0] = nxyz[0]*2.
    # ensure only odd number of cells
    if (nxyz[0]%2 == 0):
      nxyz[0] = nxyz[0] + 1
    dxyz = lxyz/nxyz

  # Plot the PFLOTRAN and analytical solutions
  plot_1D_transient(path,t_soln,'years',x_soln,c_soln,
                    x_soln,c_pflotran,'Distance [m]',
                    'Concentration [M]',"{0:.2f}".format(max_percent_error))
  # Plot error analysis
  plot_error(error_analysis,nxyz_record,dxyz_record,path,try_count,1)
   
  # Add test result to report card
  add_to_report(path,test_pass,max_percent_error,ierr)
 
  return;

Refer to section Python Helper Functions for documentation on the qa_tests_helper module, which defines the helper functions used in the Python script above.