Intro to PFLOTRAN’s QA Test Suite

How To Run The Test Suite

To run the test suite, open a terminal and navigate to the qa_tests/ directory:

$ hg clone ssh://hg@bitbucket.org/pflotran/pflotran-doc-sandbox pflotran-doc-qa
$ cd pflotran-doc-qa/qa_tests

Run the python script from within the qa_tests/ directory, and use the -E flag to indicate which pflotran executable you want to use. To run all tests, you must include the -ALL argument flag also.

$ python run_qa_tests.py -E=$PFLOTRAN_DIR/src/pflotran/pflotran -ALL

The script also includes several options which allow you to run only part of the test suite. The following options are available. Several options can be chosen at a time (but -E must always be indicated).

Usage:

The -E flag is required and must indicate the path to the
PFLOTRAN executable which will run the QA tests.

  -E=path/to/PFLOTRAN/executable

If mpirun is not the MPI executable you desire, please the
correct path to the executable using the -MPI flag.

  -MPI=path/to/MPI/executable

At least one of the following flags must also be given:

  -ALL
  -1D             -2D             -3D
  -GENERAL_MODE   -RICHARDS_MODE  -TH_MODE
  -THERMAL        -FLOW           -GAS
  -STEADY         -TRANSIENT
  -NUM_TRIES=
  -REMOVE
  -SCREEN_ON
  -HELP

Inside The run_qa_tests.sh Script

Each test is executed via it’s unique python function, which must be called in the test’s directory location.

os.chdir('thermal/steady/1D/BC_1st_kind/th_mode'); cwd = os.getcwd()
qa.thermal_steady_1D_BC1stkind(cwd,'1D_steady_thermal_BC_1st_kind',remove,screen_on,pf_exe)
os.chdir('../../../../..')

The unique test scripts reside in the file qa_tests_engine.py. Within this script, each test generates the analytical solution, runs the PFLOTRAN simulation, reads the PFLOTRAN output, and compares the PFLOTRAN solution to the analytical solution both mathematically and visually. If the test does not pass, then the script will reduce the grid spacing by a factor of 2, and re-run the test. The test will be re-run for -NUM_TRIES number of times. For example, -NUM_TRIES=5. The default number of tries is 3.

The graph that visually compares the PFLOTRAN solution against the analytical solution will be created in the specific test directory called comparison_plot.png. To view it, you must navigate to the desired test directory and open the file.

Python Helper Functions

The unique, test-specific functions use several general helper functions, defined in the module qa_tests_helper in the file qa_tests_helper.py. The member functions are documented below.

# This module contains helper functions for the set of QA Tests.
#
# Author: Jennifer M. Frederick
# Date: 08/02/2016


import numpy as np
import math
import matplotlib.pyplot as plt
import h5py
import os

################################################################################
def calc_relative_error(s_benchmark,s_pflotran,ierr):
  """ This function calculates the relative error between the benchmark 
      solution and the PFLOTRAN solution.
      
      :param s_benchmark: benchmark solution array
      :param s_pflotran: PFLOTRAN solution array
      :returns: max_percent_error, the maximum relative error between the 
                benchmark and PFLOTRAN solution arrays
  """
  #shift solution to avoid dividing by zero, but note that
  #the relative error calculated will change depending on
  #the offset that is chosen here:
  offset = 0.001
  s_benchmark = s_benchmark + offset
  s_pflotran = s_pflotran + offset
  # calculate relative percent error
  percent_error = abs(100.*(s_pflotran-s_benchmark)/s_benchmark)
  max_percent_error = np.nanmax(percent_error)
  if ierr == 0:
    print 'Relative Maximum Error: ',max_percent_error,'%'
  else:
    print 'Relative Maximum Error: N/A'
  
  return max_percent_error


################################################################################
def does_pass(max_percent_error,try_count,num_tries):
  """ This function decides if a test passes or fails by comparing the maximum
      relative error to a passing criteria value.
      
      :param max_percent_error: the maximum relative error between the benchmark
                                and PFLOTRAN solution arrays
      :param try_count: the number of time the test has been tried
      :returns: true, false
  """
  passing_crit = 2.0  # [%]
  
  # Decide if test passes
  if abs(max_percent_error) > passing_crit:
    print '-- Test FAIL --\n'
    test_pass = False
  else:
    print '-- Test PASS --\n'
    test_pass = True
  if (try_count > (num_tries-1)) and (not test_pass):
    print 'Simulation failed ' + str(try_count) + ' times. Aborting test!\n'
  return test_pass
  
  
################################################################################
def get_mode(path):
  """ This function gets the PFLOTRAN simulation mode from the path string.
      
      :param path: the path string of the qa test
      :returns: mode, the PFLOTRAN simulation mode string
  """
  k = 0
  for letter in reversed(path):
    if letter == '/':
      ind_reverse = k
      break
    k = k + 1
  ind = len(path) - ind_reverse
  mode = path[ind:len(path)]
  if mode == 'th_mode':
    mode = 'TH Mode'
  if mode == 'general_mode':
    mode = 'GENERAL Mode'
  if mode == 'richards_mode':
    mode = 'RICHARDS Mode'
    
  return mode


################################################################################
def check(solution):
  """ This function checks if the PFLOTRAN simulation output was read correctly.
      
      :param solution: the PFLOTRAN solution array
      :returns: ierr, integer error code
  """
  ierr = 0
  if len(np.shape(solution)) == 1:
    value = solution[0]
  elif len(np.shape(solution)) == 2:
    value = solution[0,0]
  else:
    value = solution[0,0,0]
    
  if value == -999:
    ierr = 1

  return ierr


################################################################################
def print_discretization(lxyz,nxyz,dxyz):
  """ This function prints the discretization information to screen.
      
      :param lxyz: domain size dimensions [m]
      :param nxyz: grid cell number [-]
      :param dxyz: grid cell spacing dimensions [m]
  """
  print 'L = ' + str(lxyz) + ' [m]'
  print 'n = ' + str(nxyz)
  print 'd = ' + str(dxyz) + ' [m]'

  return


################################################################################
def record_error(error_analysis,dxyz_record,max_percent_error,dxyz,try_count):
  """ This function records the error and grid spacing for each test try.
      
      :param error_analysis: array of the test error
      :param dxyz_record: record of the dx dy dz [m]
      :param max_percent_error: maximum relative error of the test
      :param dxyz: grid cell spacing dimensions [m]
  """
  error_analysis[try_count-1] = max_percent_error
  dxyz_record[try_count-1,:] = dxyz

  return


################################################################################
def plot_error(error_analysis,dxyz_record,path,try_count):
  
  # exit if only 1 try was made
  if try_count == 1:
    return;

  e_max = math.ceil(np.max(error_analysis))
  e_min = 0.
  d_max = math.ceil(np.max(dxyz_record))
  d_min = 0.
  pass_line = np.zeros(2) + 2.0
  
  mode = get_mode(path)
  note = mode + ' Error Analysis'

  # Plot the PFLOTRAN error analysis
  plt.figure(figsize=(5,5))
  plt.plot(dxyz_record[0:try_count,0],error_analysis[0:try_count],'s-',
	   dxyz_record[0:try_count,1],error_analysis[0:try_count],'o-',
	   dxyz_record[0:try_count,2],error_analysis[0:try_count],'^-',
	   [d_min,d_max],pass_line,'.-')
  plt.legend(('X-direction','Y-direction','Z-direction'),'best',numpoints=1)
  plt.xlabel('Grid Spacing [m]')
  plt.ylabel('Max Rel. Error [%]')
  plt.xlim([d_min,d_max])
  plt.ylim([e_min,e_max])
  plt.annotate(note, xy=(.03, .990),xycoords='figure fraction',
	       horizontalalignment='left',verticalalignment='top',fontsize=14)
  plt.savefig(path+'/error_plot.png')

  return;


################################################################################
def read_pflotran_output_1D(filename,index_string,remove):
  """ This function reads a 1D PFLOTRAN solution from an HDF5 file format.
      
      :param filename: the filename that contains the PFLOTRAN solution, which
                       must be in HDF5 output format
      :param index_string: the name of the path within the HDF5 file where the
                           data is contained
      :param remove: a Boolean that indicates if the HDF5 output file should be
                     removed after it is read
      :returns: solution, a 1D array which contains the PFLOTRAN solution
  """
  solution = -999
  try:
    f = h5py.File(filename,'r+')
    data = np.array(f[index_string][:],'=f8')
    solution = data[:,0,0]
    f.close()
  except IOError:
    print 'PFLOTRAN HDF5 output is missing.'
    
  if remove:
    os.system('rm *.h5')
    
  return solution;


################################################################################
def read_pflotran_output_2D(filename,index_string,remove):
  """ This function reads a 2D PFLOTRAN solution from an HDF5 file format.
      
      :param filename: the filename that contains the PFLOTRAN solution, which
                       must be in HDF5 output format
      :param index_string: the name of the path within the HDF5 file where the
                           data is contained
      :param remove: a Boolean that indicates if the HDF5 output file should be
                     removed after it is read
      :returns: solution_array, a 2D array which contains the PFLOTRAN solution
  """
  solution_array = -999
  try:
    f = h5py.File(filename,'r+')
    data = np.array(f[index_string][:],'=f8')
    solution_array = data[:,:,0]
    f.close()
  except IOError:
    print 'PFLOTRAN HDF5 output is missing.'
    
  if remove:
    os.system('rm *.h5')
    
  return solution_array


################################################################################
def read_pflotran_output_3D(filename,index_string,remove):
  """ This function reads a 3D PFLOTRAN solution from an HDF5 file format.
      
      :param filename: the filename that contains the PFLOTRAN solution, which
                       must be in HDF5 output format
      :param index_string: the name of the path within the HDF5 file where the
                           data is contained
      :param remove: a Boolean that indicates if the HDF5 output file should be
                     removed after it is read
      :returns: solution_array, a 3D array which contains the PFLOTRAN solution
  """
  solution_array = -999
  try:
    f = h5py.File(filename,'r+')
    data = np.array(f[index_string][:],'=f8')
    solution_array = data[:,:,:]
    f.close()
  except IOError:
    print 'PFLOTRAN HDF5 output is missing.'
    
  if remove:
    os.system('rm *.h5')
    
  return solution_array


################################################################################
def plot_1D_steady(path,x_benchmark,s_benchmark,x_pflotran,s_pflotran,x_string,
                   s_string,e_string):

  s_max = math.ceil(np.max(s_benchmark*10.0))/10.0
  s_min = math.floor(np.min(s_benchmark*10.0))/10.0
  if s_min < 0:
    s_min = 0
  x_max = math.ceil(np.max(x_benchmark))
  x_min = math.floor(np.min(x_benchmark))
  
  mode = get_mode(path)
  note = mode + ' ' + e_string + '% error'

  # Plot the PFLOTRAN and analytical solutions
  plt.figure(figsize=(5,5))
  plt.plot(x_pflotran,s_pflotran,'o',x_benchmark,s_benchmark)
  plt.xlabel(x_string)
  plt.ylabel(s_string)
  plt.xlim([x_min,x_max])
  plt.ylim([s_min,s_max])
  plt.legend(('PFLOTRAN','analytical'),'best',numpoints=1)
  plt.annotate(note, xy=(.03, .990),xycoords='figure fraction',
	       horizontalalignment='left',verticalalignment='top',fontsize=14)
  plt.savefig(path+'/comparison_plot.png')

  return;


################################################################################
def plot_2D_steady(path,x_array,y_array,s_benchmark,s_pflotran,xy_string,
                   s_string,e_string):
  s_max = math.ceil(np.max(s_benchmark*10.0))/10.0
  s_min = math.floor(np.min(s_benchmark*10.0))/10.0
  if s_min < 0:
    s_min = 0
  x_max = math.ceil(np.max(x_array))
  x_min = math.floor(np.min(x_array))
  y_max = math.ceil(np.max(y_array))
  y_min = math.floor(np.min(y_array))
  levels = np.linspace(s_min,s_max,11)
  X,Y = np.meshgrid(y_array,x_array)
  
  mode = get_mode(path)
  note = mode + ' ' + e_string + '% error'

  # Plot the PFLOTRAN and analytical solutions
  plt.figure(figsize=(7,5))
  plt.contourf(Y,X,s_benchmark,levels,alpha=0.75)
  C = plt.contour(Y,X,s_pflotran,levels,colors='black',linewidth=0.5)
  plt.clabel(C,inline=True,fontsize=10)
  plt.xlabel(xy_string)
  plt.ylabel(xy_string)
  plt.xlim([x_min,x_max])
  plt.ylim([y_min,y_max])
  plt.title(s_string)
  ann_title = 'Analytical (fill) vs. PFLOTRAN (contour)'+' '+note
  plt.annotate(ann_title,xy=(.03,.990),xycoords='figure fraction',
	       horizontalalignment='left',verticalalignment='top',fontsize=14)
  plt.savefig(path+'/comparison_plot.png')

  return;


################################################################################
def plot_3D_steady(path,x_array,y_array,z_levels,s_benchmark,s_pflotran,
                   xy_string,s_string,e_string):
  plot_2D_transient(path,z_levels,' ind. slice',x_array,y_array,s_benchmark,
                    s_pflotran,xy_string,s_string,e_string)
  return;


################################################################################
def plot_1D_transient(path,t_array,t_unit,x_benchmark,s_benchmark,x_pflotran,
                      s_pflotran,x_string,s_string,e_string):
  s_max = math.ceil(np.max(s_benchmark*10.0))/10.0
  s_min = math.floor(np.min(s_benchmark*10.0))/10.0
  if s_min < 0:
    s_min = 0
  x_max = math.ceil(np.max(x_benchmark))
  x_min = math.floor(np.min(x_benchmark))
  
  mode = get_mode(path)
  note = mode + ' ' + e_string + '% error'

  # Plot the PFLOTRAN and analytical solutions
  plt.figure(figsize=(10,10))
  plt.subplot(221)
  plt.plot(x_pflotran,s_pflotran[0,:],'o',x_benchmark,s_benchmark[0,:])
  plt.xlabel(x_string)
  plt.ylabel(s_string)
  plt.xlim([x_min,x_max])
  plt.ylim([s_min,s_max])
  plt.title('t='+str(t_array[0])+t_unit)
  plt.legend(('PFLOTRAN','analytical'),'best',numpoints=1)
  plt.subplot(222)
  plt.plot(x_pflotran,s_pflotran[1,:],'o',x_benchmark,s_benchmark[1,:])
  plt.xlabel(x_string)
  plt.ylabel(s_string)
  plt.xlim([x_min,x_max])
  plt.ylim([s_min,s_max])
  plt.title('t='+str(t_array[1])+t_unit)
  plt.legend(('PFLOTRAN','analytical'),'best',numpoints=1)
  plt.subplot(223)
  plt.plot(x_pflotran,s_pflotran[2,:],'o',x_benchmark,s_benchmark[2,:])
  plt.xlabel(x_string)
  plt.ylabel(s_string)
  plt.xlim([x_min,x_max])
  plt.ylim([s_min,s_max])
  plt.title('t='+str(t_array[2])+t_unit)
  plt.legend(('PFLOTRAN','analytical'),'best',numpoints=1)
  plt.subplot(224)
  plt.plot(x_pflotran,s_pflotran[3,:],'o',x_benchmark,s_benchmark[3,:])
  plt.xlabel(x_string)
  plt.ylabel(s_string)
  plt.xlim([x_min,x_max])
  plt.ylim([s_min,s_max])
  plt.title('t='+str(t_array[3])+t_unit)
  plt.legend(('PFLOTRAN','analytical'),'best',numpoints=1)
  plt.annotate(note, xy=(.03, .990),xycoords='figure fraction',
	       horizontalalignment='left',verticalalignment='top',fontsize=14)
  plt.savefig(path+'/comparison_plot.png')

  return;


################################################################################
def plot_2D_transient(path,t_array,t_unit,x_array,y_array,s_benchmark,
                      s_pflotran,xy_string,s_string,e_string):
  s_max = math.ceil(np.max(s_benchmark*10.0))/10.0
  s_min = math.floor(np.min(s_benchmark*10.0))/10.0
  if s_min < 0:
    s_min = 0
  x_max = math.ceil(np.max(x_array))
  x_min = math.floor(np.min(x_array))
  y_max = math.ceil(np.max(y_array))
  y_min = math.floor(np.min(y_array))
  levels = np.linspace(s_min,s_max,11)
  X,Y = np.meshgrid(y_array,x_array)
  
  mode = get_mode(path)
  note = mode + ' ' + e_string + '% error'

  # Plot the PFLOTRAN and analytical solutions
  plt.figure(figsize=(11,10))
  plt.subplot(221)
  plt.contourf(Y,X,s_benchmark[0,:,:],levels,alpha=0.75)
  C = plt.contour(Y,X,s_pflotran[0,:,:],levels,colors='black',linewidth=0.5)
  plt.clabel(C,inline=True,fontsize=10)
  plt.xlabel(xy_string)
  plt.ylabel(xy_string)
  plt.xlim([x_min,x_max])
  plt.ylim([y_min,y_max])
  plt.title(s_string+' '+str(t_array[0])+t_unit)
  plt.subplot(222)
  plt.contourf(Y,X,s_benchmark[1,:,:],levels,alpha=0.75)
  C = plt.contour(Y,X,s_pflotran[1,:,:],levels,colors='black',linewidth=0.5)
  plt.clabel(C,inline=True,fontsize=10)
  plt.xlabel(xy_string)
  plt.ylabel(xy_string)
  plt.xlim([x_min,x_max])
  plt.ylim([y_min,y_max])
  plt.title(s_string+' '+str(t_array[1])+t_unit)
  plt.subplot(223)
  plt.contourf(Y,X,s_benchmark[2,:,:],levels,alpha=0.75)
  C = plt.contour(Y,X,s_pflotran[2,:,:],levels,colors='black',linewidth=0.5)
  plt.clabel(C,inline=True,fontsize=10)
  plt.xlabel(xy_string)
  plt.ylabel(xy_string)
  plt.xlim([x_min,x_max])
  plt.ylim([y_min,y_max])
  plt.title(s_string+' '+str(t_array[2])+t_unit)
  plt.subplot(224)
  plt.contourf(Y,X,s_benchmark[3,:,:],levels,alpha=0.75)
  C = plt.contour(Y,X,s_pflotran[3,:,:],levels,colors='black',linewidth=0.5)
  plt.clabel(C,inline=True,fontsize=10)
  plt.xlabel(xy_string)
  plt.ylabel(xy_string)
  plt.xlim([x_min,x_max])
  plt.ylim([y_min,y_max])
  plt.title(s_string+' '+str(t_array[3])+t_unit)
  ann_title = 'Analytical (fill) vs. PFLOTRAN (contour)'+' '+note
  plt.annotate(ann_title,xy=(.03,.990),xycoords='figure fraction',
	       horizontalalignment='left',verticalalignment='top',fontsize=14)
  plt.savefig(path+'/comparison_plot.png')

  return;


################################################################################
def add_to_report(path,outcome,error,ierr):
  # look for /qa_tests in path
  k = 0
  for letter in path:
    if letter == '/':
      ind = k
      if path[ind:ind+9] == '/qa_tests':
	ind_write = k
	filename = path[0:ind+9]+'/report.txt'
    k = k + 1
  target = open(filename, 'a')
  target.write(path[ind_write:len(path)])
  target.write("\n")
  if ierr == 0:
    target.write("maximum relative error: ")
    target.write(str(error))
    target.write("%\n")
    target.write("Test ")
    if outcome:
      target.write("PASS\n\n")
    else:
      target.write("FAIL\n\n")
  else:
    target.write("PFLOTRAN simulation error!\n")
    target.write("Test FAIL\n\n")

  return;


################################################################################
def print_report(path):
  filename = path+'/report.txt'
  f = open(filename,'r')
  count = 0
  pass_count = 0
  fail_count = 0
  for line in f:
    if line[0:4] == 'Test':
      count = count + 1
      if line[5:9] == 'PASS':
	pass_count = pass_count + 1
      if line[5:9] == 'FAIL':
	fail_count = fail_count + 1
  f.close
  
  grade = float(pass_count)/float(count)
  if (grade >= 0.90):
    letter_grade = 'A'
  if ((grade >= 0.80) & (grade < 0.90)):
    letter_grade = 'B' 
  if ((grade >= 0.70) & (grade < 0.80)):
    letter_grade = 'C'
  if ((grade >= 0.60) & (grade < 0.70)):
    letter_grade = 'D'
  if (grade < 0.60):
    letter_grade = 'F'

  print 'See report.txt for summary of test outcomes.'
  print 'Total number of tests: '+str(count)
  print 'Passing: '+str(pass_count)
  print 'Failing: '+str(fail_count)
  print 'Grade: '+ letter_grade
    
  target = open(filename, 'a')
  target.write('--------------------------')
  target.write("\n")
  target.write("Total number of tests: ")
  target.write(str(count))
  target.write("\n")
  target.write("Passing tests: ")
  target.write(str(pass_count))
  target.write("\n")
  target.write("Failing tests: ")
  target.write(str(fail_count))
  target.write("\n")
  target.write("Grade: " + letter_grade)
    
  return;


################################################################################
def check_fig_make_blank(path):
  
  return;

QA Tests Report Card

The most recent report card is included below:

/qa_tests/thermal/steady/1D/BC_1st_kind/th_mode
maximum relative error: 0.0%
Test PASS

/qa_tests/thermal/steady/1D/BC_1st_2nd_kind/th_mode
maximum relative error: 1.455076048e-14%
Test PASS

/qa_tests/thermal/steady/2D/BC_1st_kind/th_mode
maximum relative error: 2.62154197078e-14%
Test PASS

/qa_tests/thermal/steady/2D/BC_1st_2nd_kind/th_mode
maximum relative error: 2.68981956299e-14%
Test PASS

/qa_tests/thermal/steady/3D/BC_1st_kind/th_mode
maximum relative error: 1.07320165901%
Test PASS

/qa_tests/thermal/steady/1D/BC_1st_kind/general_mode
maximum relative error: 5.332894594e-07%
Test PASS

/qa_tests/thermal/steady/1D/BC_1st_2nd_kind/general_mode
maximum relative error: 1.07607656498e-07%
Test PASS

/qa_tests/thermal/steady/2D/BC_1st_kind/general_mode
maximum relative error: 2.62154197078e-14%
Test PASS

/qa_tests/thermal/steady/2D/BC_1st_2nd_kind/general_mode
maximum relative error: 2.27621327447e-14%
Test PASS

/qa_tests/thermal/steady/3D/BC_1st_kind/general_mode
maximum relative error: 1.06959529731e-06%
Test PASS

/qa_tests/thermal/transient/1D/BC_1st_kind/th_mode
maximum relative error: 0.732037252074%
Test PASS

/qa_tests/thermal/transient/1D/BC_1st_2nd_kind/th_mode
maximum relative error: 3.90387003691%
Test FAIL

/qa_tests/thermal/transient/1D/BC_2nd_kind/th_mode
maximum relative error: 1.05474623692%
Test PASS

/qa_tests/thermal/transient/2D/BC_1st_2nd_kind/th_mode
maximum relative error: 5.59930036302%
Test FAIL

/qa_tests/thermal/transient/1D/BC_1st_kind/general_mode
maximum relative error: 0.732037246266%
Test PASS

/qa_tests/thermal/transient/1D/BC_1st_2nd_kind/general_mode
maximum relative error: 3.90716096275%
Test FAIL

/qa_tests/thermal/transient/1D/BC_2nd_kind/general_mode
maximum relative error: 1.05476751588%
Test PASS

/qa_tests/thermal/transient/2D/BC_1st_2nd_kind/general_mode
maximum relative error: 5.5993003564%
Test FAIL

/qa_tests/flow/steady/1D/BC_1st_kind/richards_mode
maximum relative error: 1.03522223038e-12%
Test PASS

/qa_tests/flow/steady/1D/BC_1st_2nd_kind/richards_mode
maximum relative error: 0.0%
Test PASS

/qa_tests/flow/steady/1D/hydrostatic/richards_mode
maximum relative error: 0.0294468902773%
Test PASS

/qa_tests/flow/steady/2D/BC_1st_kind/richards_mode
maximum relative error: 1.71000850924e-14%
Test PASS

/qa_tests/flow/steady/2D/BC_1st_2nd_kind/richards_mode
maximum relative error: 1.99545814356e-14%
Test PASS

/qa_tests/flow/steady/3D/BC_1st_kind/richards_mode
maximum relative error: 4.43497879344e-14%
Test PASS

/qa_tests/flow/steady/1D/BC_1st_kind/th_mode
maximum relative error: 0.0%
Test PASS

/qa_tests/flow/steady/1D/BC_1st_2nd_kind/th_mode
maximum relative error: 0.0%
Test PASS

/qa_tests/flow/steady/1D/hydrostatic/th_mode
maximum relative error: 0.0294468902773%
Test PASS

/qa_tests/flow/steady/2D/BC_1st_kind/th_mode
maximum relative error: 1.71000850924e-14%
Test PASS

/qa_tests/flow/steady/2D/BC_1st_2nd_kind/th_mode
maximum relative error: 1.99545814356e-14%
Test PASS

/qa_tests/flow/steady/3D/BC_1st_kind/th_mode
maximum relative error: 4.43497879344e-14%
Test PASS

/qa_tests/flow/steady/1D/BC_1st_kind/general_mode
maximum relative error: 0.0%
Test PASS

/qa_tests/flow/steady/1D/BC_1st_2nd_kind/general_mode
maximum relative error: 0.0%
Test PASS

/qa_tests/flow/steady/1D/hydrostatic/general_mode
maximum relative error: 0.0294468902773%
Test PASS

/qa_tests/flow/steady/2D/BC_1st_kind/general_mode
maximum relative error: 1.71000850924e-14%
Test PASS

/qa_tests/flow/steady/2D/BC_1st_2nd_kind/general_mode
maximum relative error: 1.99545814356e-14%
Test PASS

/qa_tests/flow/steady/3D/BC_1st_kind/general_mode
maximum relative error: 4.43497879344e-14%
Test PASS

/qa_tests/flow/transient/1D/BC_1st_kind/richards_mode
maximum relative error: 1.05237827417%
Test PASS

/qa_tests/flow/transient/1D/BC_2nd_kind/richards_mode
maximum relative error: 0.889634118869%
Test PASS

/qa_tests/flow/transient/1D/BC_1st_2nd_kind/richards_mode
maximum relative error: 0.715350381962%
Test PASS

/qa_tests/flow/transient/2D/BC_1st_2nd_kind/richards_mode
maximum relative error: 1.89706868171%
Test PASS

/qa_tests/flow/transient/1D/BC_1st_kind/th_mode
maximum relative error: 1.05237827418%
Test PASS

/qa_tests/flow/transient/1D/BC_2nd_kind/th_mode
maximum relative error: 0.889634118897%
Test PASS

/qa_tests/flow/transient/1D/BC_1st_2nd_kind/th_mode
maximum relative error: 0.715350382689%
Test PASS

/qa_tests/flow/transient/2D/BC_1st_2nd_kind/th_mode
maximum relative error: 1.89706868155%
Test PASS

/qa_tests/flow/transient/1D/BC_1st_kind/general_mode
maximum relative error: 1.05465774061%
Test PASS

/qa_tests/flow/transient/1D/BC_2nd_kind/general_mode
maximum relative error: 0.889634119313%
Test PASS

/qa_tests/flow/transient/1D/BC_1st_2nd_kind/general_mode
maximum relative error: 0.715350382165%
Test PASS

/qa_tests/flow/transient/2D/BC_1st_2nd_kind/general_mode
maximum relative error: 1.89710291533%
Test PASS

/qa_tests/gas/steady/1D/BC_1st_kind/general_mode
maximum relative error: 0.20012755453%
Test PASS

/qa_tests/gas/steady/1D/BC_1st_2nd_kind/general_mode
maximum relative error: 0.550442792533%
Test PASS

/qa_tests/gas/steady/2D/BC_1st_2nd_kind/general_mode
maximum relative error: 0.647017125998%
Test PASS

/qa_tests/gas/steady/3D/BC_2nd_kind/general_mode
maximum relative error: 9.43101717093%
Test FAIL

--------------------------
Total number of tests: 52
Passing tests: 47
Failing tests: 5
Grade: A