MODFLOW 6  version 6.7.0.dev3
USGS Modular Hydrologic Model
UTVDScheme.f90
Go to the documentation of this file.
2  use kindmodule, only: dp, i4b
3  use constantsmodule, only: done, dzero, dsame, dhalf
6  use basedismodule, only: disbasetype
7  use tspfmimodule, only: tspfmitype
8  use igradient, only: igradienttype
9 
10  use disutilsmodule, only: node_distance
12 
13  implicit none
14  private
15 
16  public :: utvdschemetype
17 
18  !> @brief Total Variation Diminishing (TVD) interpolation scheme.
19  !!
20  !! This class implements a high-resolution, TVD interpolation scheme for use in transport modeling.
21  !! It extends a generic interpolation scheme interface and supports multiple TVD limiters (van Leer,
22  !! Koren, Superbee, van Albada, etc.) for controlling numerical diffusion and oscillations.
23  !! The default limiter is van Leer, but others can be selected by changing the `limiter_id` member.
24  !!
25  !! The scheme uses a combination of low-order upwind and high-order limited terms to compute
26  !! face concentrations. The high-order term is constructed using a gradient-based virtual node
27  !! value, following the approach described by Darwish et al. An additional TVD clamp is applied
28  !! to the virtual node value to enforce TVD compliance, especially on grids where the original
29  !! method may not guarantee monotonicity.
30  !!
31  !! - Supports both structured and unstructured grids via polymorphic discretization and gradient objects.
32  !! - The limiter can be selected via the `limiter_id` member (default is van Leer).
33  !! - The method `find_local_extrema` finds the minimum and maximum values among the current cell and its neighbors,
34  !! which is used to enforce the TVD condition.
35  !! - The `compute` method calculates the face coefficients for the transport equation.
36  !<
38  private
39  class(disbasetype), pointer :: dis
40  type(tspfmitype), pointer :: fmi
41  class(igradienttype), pointer :: gradient
42 
43  real(dp), dimension(:), pointer :: phi
44  type(localcellextrematype), allocatable :: min_max_phi ! local minimum values at nodes
45  integer(I4B) :: limiter_id = 2 ! default to van Leer limiter
46  real(dp), dimension(:, :), allocatable :: cached_node_distance ! distance vectors
47  contains
48  procedure :: compute
49  procedure :: set_field
50  final :: destructor
51 
52  procedure, private :: limiter
53  procedure, private :: compute_node_distance
54  end type utvdschemetype
55 
56  interface utvdschemetype
57  module procedure constructor
58  end interface utvdschemetype
59 
60 contains
61 
62  function constructor(dis, fmi, gradient) &
63  result(interpolation_scheme)
64  ! -- return
65  type(utvdschemetype) :: interpolation_scheme
66  ! -- dummy
67  class(disbasetype), pointer, intent(in) :: dis
68  type(tspfmitype), pointer, intent(in) :: fmi
69  class(igradienttype), allocatable, intent(in), target :: gradient
70 
71  interpolation_scheme%dis => dis
72  interpolation_scheme%fmi => fmi
73 
74  interpolation_scheme%gradient => gradient
75  interpolation_scheme%min_max_phi = localcellextrematype(dis)
76 
77  allocate (interpolation_scheme%cached_node_distance(dis%njas, 3))
78  call compute_node_distance(interpolation_scheme)
79 
80  end function constructor
81 
82  subroutine destructor(this)
83  ! -- dummy
84  type(utvdschemetype), intent(inout) :: this
85 
86  deallocate (this%cached_node_distance)
87  end subroutine destructor
88 
89  !> @brief Set the scalar field for which interpolation will be computed
90  !!
91  !! This method establishes a pointer to the scalar field data and updates
92  !! any dependent cached data (gradients and local extrema) to ensure
93  !! subsequent interpolation computations use the current field values.
94  !!
95  !<
96  subroutine set_field(this, phi)
97  ! -- dummy
98  class(utvdschemetype), target :: this
99  real(DP), intent(in), dimension(:), pointer :: phi
100 
101  this%phi => phi
102  call this%gradient%set_field(phi)
103  call this%min_max_phi%set_field(phi)
104  end subroutine set_field
105 
106  function compute(this, n, m, iposnm) result(phi_face)
107  !-- return
108  type(coefficientstype), target :: phi_face ! Output: coefficients for the face between cells n and m
109  ! -- dummy
110  class(utvdschemetype), target :: this
111  integer(I4B), intent(in) :: n ! Index of the first cell
112  integer(I4B), intent(in) :: m ! Index of the second cell
113  integer(I4B), intent(in) :: iposnm ! Position in the connectivity array for the n-m connection
114  ! -- local
115  integer(I4B) :: iup, idn, isympos ! Indices for upwind, downwind, and symmetric position in connectivity
116  real(dp) :: qnm ! Flow rate across the face between n and m
117  real(dp), pointer :: coef_up, coef_dn ! Pointers to upwind and downwind coefficients in phi_face
118  real(dp), dimension(3) :: grad_c ! gradient at upwind cell
119  real(dp), dimension(3) :: dnm ! vector from upwind to downwind cell
120  real(dp) :: smooth ! Smoothness indicator for limiter i.e. ratio of gradients
121  real(dp) :: alimiter ! Value of the TVD limiter
122  real(dp) :: cl1, cl2 ! Connection lengths from upwind and downwind cells to the face
123  real(dp) :: relative_distance ! Relative distance factor for high-order term
124  real(dp) :: c_virtual ! Virtual node concentration (Darwish method)
125  real(dp), pointer :: min_phi, max_phi ! Local minimum and maximum among cell and neighbors
126 
127  isympos = this%dis%con%jas(iposnm)
128  qnm = this%fmi%gwfflowja(iposnm)
129  !
130  ! -- Find upstream node
131  if (qnm > dzero) then
132  ! -- positive flow into n means m is upstream
133  iup = m
134  idn = n
135 
136  cl1 = this%dis%con%cl2(isympos)
137  cl2 = this%dis%con%cl1(isympos)
138 
139  coef_up => phi_face%c_m
140  coef_dn => phi_face%c_n
141  else
142  iup = n
143  idn = m
144 
145  cl1 = this%dis%con%cl1(isympos)
146  cl2 = this%dis%con%cl2(isympos)
147 
148  coef_up => phi_face%c_n
149  coef_dn => phi_face%c_m
150  end if
151  !
152  ! Determine direction of distance vector from upwind to downwind cell
153  ! The cached_node_distance always stores vector from lower-numbered node to higher-numbered node.
154  ! Since we need dnm to point from upwind (iup) to downwind (idn), we must adjust the sign:
155  ! - If iup > idn: the cached vector points from idn to iup, so we negate it to get iup to idn
156  ! - If iup < idn: the cached vector already points from iup to idn, so use it as-is
157  if (iup > idn) then
158  dnm = -this%cached_node_distance(isympos, :)
159  else
160  dnm = this%cached_node_distance(isympos, :)
161  end if
162  !
163  ! -- Add low order terms
164  coef_up = done
165  !
166  ! -- Add high order terms
167  !
168  ! -- Return if straddled cells have same value
169  if (abs(this%phi(idn) - this%phi(iup)) < dsame) return
170  !
171  ! -- Compute cell concentration gradient
172  grad_c = this%gradient%get(iup)
173  !
174  ! Darwish's method to compute virtual node concentration
175  c_virtual = this%phi(idn) - 2.0_dp * (dot_product(grad_c, dnm))
176  !
177  ! Enforce local TVD condition.
178  ! This is done by limiting the virtual concentration to the range of
179  ! the max and min concentration of the neighbouring cells.
180  min_phi => this%min_max_phi%get_min(iup)
181  max_phi => this%min_max_phi%get_max(iup)
182 
183  if (c_virtual > max_phi) then
184  c_virtual = max_phi
185  end if
186 
187  if (c_virtual < max(min_phi, dzero)) then
188  c_virtual = max(min_phi, dzero)
189  end if
190  !
191  ! -- Compute smoothness factor
192  smooth = (this%phi(iup) - c_virtual) / (this%phi(idn) - this%phi(iup))
193  !
194  ! -- Compute limiter
195  alimiter = this%limiter(smooth)
196 
197  ! High order term is:
198  relative_distance = cl1 / (cl1 + cl2)
199  phi_face%rhs = -relative_distance * alimiter * (this%phi(idn) - this%phi(iup))
200 
201  ! Alternative way of writing the high order term by adding it to the
202  ! coefficients matrix. The equation to be added is:
203  ! high_order = cl1 / (cl1 + cl2) * alimiter * qnm * (phi(idn) - phi(iup))
204  ! This is split into two parts:
205  ! coef_up = coef_up - relative_distance * alimiter
206  ! coef_dn = coef_dn + relative_distance * alimiter
207 
208  end function compute
209 
210  function limiter(this, r) result(theta)
211  ! -- return
212  real(dp) :: theta ! limited slope
213  ! -- dummy
214  class(utvdschemetype) :: this
215  real(dp) :: r ! ratio of successive gradients
216 
217  select case (this%limiter_id)
218  case (2) ! van Leer
219  theta = max(0.0_dp, min((r + dabs(r)) / (1.0_dp + dabs(r)), 2.0_dp))
220  case (3) ! Koren
221  theta = max(0.0_dp, min(2.0_dp * r, &
222  1.0_dp / 3.0_dp + 2.0_dp / 3.0_dp * r, 2.0_dp))
223  case (4) ! Superbee
224  theta = max(0.0_dp, min(2.0_dp * r, 1.0_dp), min(r, 2.0_dp))
225  case (5) ! van Albada
226  theta = max(0.0_dp, (r * r + r) / (r * r + 1.0_dp))
227  case (6) ! Koren modified
228  theta = max(0.0_dp, min(4.0_dp * r * r + r, &
229  1.0_dp / 3.0_dp + 2.0_dp / 3.0_dp * r, 2.0_dp))
230  case default
231  theta = dzero
232  end select
233  end function
234 
235  subroutine compute_node_distance(this)
236  ! -- dummy
237  class(utvdschemetype), target :: this
238  ! -- local
239  integer(I4B) :: n, m, ipos, isympos
240 
241  this%cached_node_distance = 0.0_dp
242  do n = 1, this%dis%nodes
243  do ipos = this%dis%con%ia(n) + 1, this%dis%con%ia(n + 1) - 1
244  m = this%dis%con%ja(ipos)
245  if (m <= n) cycle
246 
247  isympos = this%dis%con%jas(ipos)
248  this%cached_node_distance(isympos, :) = node_distance(this%dis, n, m)
249  end do
250  end do
251 
252  end subroutine compute_node_distance
253 
254 end module utvdschememodule
This module contains simulation constants.
Definition: Constants.f90:9
real(dp), parameter dsame
real constant for values that are considered the same based on machine precision
Definition: Constants.f90:122
real(dp), parameter dhalf
real constant 1/2
Definition: Constants.f90:68
real(dp), parameter dzero
real constant zero
Definition: Constants.f90:65
real(dp), parameter done
real constant 1
Definition: Constants.f90:76
real(dp) function, dimension(3), public node_distance(dis, n, m)
Returns the vector distance from cell n to cell m.
Definition: DisUtils.f90:39
This module defines variable data types.
Definition: kind.f90:8
type(coefficientstype) function, target compute(this, n, m, iposnm)
Definition: UTVDScheme.f90:107
subroutine set_field(this, phi)
Set the scalar field for which interpolation will be computed.
Definition: UTVDScheme.f90:97
subroutine destructor(this)
Definition: UTVDScheme.f90:83
type(utvdschemetype) function constructor(dis, fmi, gradient)
Definition: UTVDScheme.f90:64
subroutine compute_node_distance(this)
Definition: UTVDScheme.f90:236
real(dp) function limiter(this, r)
Definition: UTVDScheme.f90:211
Abstract interface for cell-based gradient computation.
Definition: IGradient.f90:15
Computes and caches local extrema for each cell and its connected neighbors.
Total Variation Diminishing (TVD) interpolation scheme.
Definition: UTVDScheme.f90:37