GEOS
DirichletFluxComputeKernel_impl.hpp
Go to the documentation of this file.
1 /*
2  * ------------------------------------------------------------------------------------------------------------
3  * SPDX-License-Identifier: LGPL-2.1-only
4  *
5  * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC
6  * Copyright (c) 2018-2024 TotalEnergies
7  * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University
8  * Copyright (c) 2023-2024 Chevron
9  * Copyright (c) 2019- GEOS/GEOSX Contributors
10  * All rights reserved
11  *
12  * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
13  * ------------------------------------------------------------------------------------------------------------
14  */
15 
20 #ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONAL_DIRICHLETFLUXCOMPUTEKERNEL_IMPL_HPP
21 #define GEOS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONAL_DIRICHLETFLUXCOMPUTEKERNEL_IMPL_HPP
22 
24 
25 namespace geos
26 {
27 namespace isothermalCompositionalMultiphaseFVMKernels
28 {
29 
30 template< typename FLUIDWRAPPER, typename POLICY, integer NUM_COMP, integer NUM_DOF >
32 DirichletFluxComputeKernel( integer const numPhases,
33  globalIndex const rankOffset,
34  FaceManager const & faceManager,
35  BoundaryStencilWrapper const & stencilWrapper,
36  FLUIDWRAPPER const & fluidWrapper,
37  DofNumberAccessor const & dofNumberAccessor,
38  CompFlowAccessors const & compFlowAccessors,
39  MultiFluidAccessors const & multiFluidAccessors,
40  CapPressureAccessors const & capPressureAccessors,
41  PermeabilityAccessors const & permeabilityAccessors,
42  real64 const dt,
44  arrayView1d< real64 > const & localRhs,
45  BitFlags< KernelFlags > kernelFlags )
46  : Base( numPhases,
47  rankOffset,
48  stencilWrapper,
49  dofNumberAccessor,
50  compFlowAccessors,
51  multiFluidAccessors,
52  capPressureAccessors,
53  permeabilityAccessors,
54  dt,
55  localMatrix,
56  localRhs,
57  kernelFlags ),
58  m_facePres( faceManager.getField< fields::flow::facePressure >() ),
59  m_faceTemp( faceManager.getField< fields::flow::faceTemperature >() ),
60  m_faceCompFrac( faceManager.getField< fields::flow::faceGlobalCompFraction >() ),
61  m_faceGravCoef( faceManager.getField< fields::flow::gravityCoefficient >() ),
62  m_fluidWrapper( fluidWrapper )
63 {}
64 
65 template< typename FLUIDWRAPPER, typename POLICY, integer NUM_COMP, integer NUM_DOF >
67 launchKernel( localIndex const numConnections )
68 {
69  this->template launch< POLICY >( numConnections, *this );
70 }
71 
72 template< typename FLUIDWRAPPER, typename POLICY, integer NUM_COMP, integer NUM_DOF >
75 setup( localIndex const iconn,
76  StackVariables & stack ) const
77 {
78  globalIndex const offset =
79  m_dofNumber[m_seri( iconn, BoundaryStencil::Order::ELEM )][m_sesri( iconn, BoundaryStencil::Order::ELEM )][m_sei( iconn, BoundaryStencil::Order::ELEM )];
80 
81  for( integer jdof = 0; jdof < numDof; ++jdof )
82  {
83  stack.dofColIndices[jdof] = offset + jdof;
84  }
85 }
86 
87 template< typename FLUIDWRAPPER, typename POLICY, integer NUM_COMP, integer NUM_DOF >
88 template< typename FUNC >
91 computeFlux( localIndex const iconn,
92  StackVariables & stack,
93  FUNC && compFluxKernelOp ) const
94 {
95  using Deriv = constitutive::multifluid::DerivativeOffset;
96  using Order = BoundaryStencil::Order;
97 
98  localIndex const er = m_seri( iconn, Order::ELEM );
99  localIndex const esr = m_sesri( iconn, Order::ELEM );
100  localIndex const ei = m_sei( iconn, Order::ELEM );
101  localIndex const kf = m_sei( iconn, Order::FACE );
102 
103  // Step 1: compute the transmissibility at the boundary face
104 
105  real64 dTrans_dPerm[3]{};
106  m_stencilWrapper.computeWeights( iconn,
107  m_permeability,
108  stack.transmissibility,
109  dTrans_dPerm );
110  real64 const dTrans_dPres = LvArray::tensorOps::AiBi< 3 >( dTrans_dPerm, m_dPerm_dPres[er][esr][ei][0] );
111 
112  // Step 2: compute the fluid properties on the face
113  // This is needed to get the phase mass density and the phase comp fraction at the face
114  // Because we approximate the face mobility using the total element mobility
115 
122  StackArray< real64, 4, constitutive::MultiFluidBase::MAX_NUM_PHASES * NUM_COMP,
123  constitutive::multifluid::LAYOUT_PHASE_COMP > facePhaseCompFrac( 1, 1, m_numPhases, NUM_COMP );
124  real64 faceTotalDens = 0.0;
125 
126  constitutive::MultiFluidBase::KernelWrapper::computeValues( m_fluidWrapper,
127  m_facePres[kf],
128  m_faceTemp[kf],
129  m_faceCompFrac[kf],
130  facePhaseFrac[0][0],
131  facePhaseDens[0][0],
132  facePhaseMassDens[0][0],
133  facePhaseVisc[0][0],
134  facePhaseEnthalpy[0][0],
135  facePhaseInternalEnergy[0][0],
136  facePhaseCompFrac[0][0],
137  faceTotalDens );
138 
139  // Step 3: loop over phases, compute and upwind phase flux and sum contributions to each component's flux
140 
141  for( integer ip = 0; ip < m_numPhases; ++ip )
142  {
143 
144  // working variables
145  real64 dDensMean_dC[numComp]{};
146  real64 dF_dC[numComp]{};
147  real64 dProp_dC[numComp]{};
148 
149  real64 phaseFlux = 0.0; // for the lambda
150  real64 dPhaseFlux_dP = 0.0;
151  real64 dPhaseFlux_dC[numComp]{};
152 
153 
154  // Step 3.1: compute the average phase mass density at the face
155 
156  applyChainRule( numComp,
157  m_dCompFrac_dCompDens[er][esr][ei],
158  m_dPhaseMassDens[er][esr][ei][0][ip],
159  dProp_dC,
160  Deriv::dC );
161 
162  // average density and derivatives
163  real64 const densMean = 0.5 * ( m_phaseMassDens[er][esr][ei][0][ip] + facePhaseMassDens[0][0][ip] );
164  real64 const dDensMean_dP = 0.5 * m_dPhaseMassDens[er][esr][ei][0][ip][Deriv::dP];
165  for( integer jc = 0; jc < numComp; ++jc )
166  {
167  dDensMean_dC[jc] = 0.5 * dProp_dC[jc];
168  }
169 
170 
171  // Step 3.2: compute the (TPFA) potential difference at the face
172 
173  real64 const gravTimesDz = m_gravCoef[er][esr][ei] - m_faceGravCoef[kf];
174  real64 const potDif = m_pres[er][esr][ei] - m_facePres[kf] - densMean * gravTimesDz;
175  real64 const f = stack.transmissibility * potDif;
176  real64 const dF_dP = stack.transmissibility * ( 1.0 - dDensMean_dP * gravTimesDz ) + dTrans_dPres * potDif;
177  for( integer jc = 0; jc < numComp; ++jc )
178  {
179  dF_dC[jc] = -stack.transmissibility * dDensMean_dC[jc] * gravTimesDz;
180  }
181 
182  // Step 3.3: computation of the mobility
183  // We do that before the if/else statement to be able to pass it to the compFluxOpKernel
184 
185  // recomputing the exact mobility at the face would be quite complex, as it would require:
186  // 1) computing the saturation
187  // 2) computing the relperm
188  // 3) computing the mobility as \lambda_p = \rho_p kr_p( S_p ) / \mu_p
189  // the second step in particular would require yet another dispatch to get the relperm model
190  // so, for simplicity, we approximate the face mobility as
191  // \lambda^approx_p = \rho_p S_p / \mu_p
192  // = \rho_p ( (nu_p / rho_p) * rho_t ) / \mu_p (plugging the expression of saturation)
193  // = \nu_p * rho_t / \mu_p
194  // fortunately, we don't need the derivatives
195  real64 const facePhaseMob = ( facePhaseFrac[0][0][ip] > 0.0 )
196  ? facePhaseFrac[0][0][ip] * faceTotalDens / facePhaseVisc[0][0][ip]
197  : 0.0;
198 
199  // *** upwinding ***
200  // Step 3.4: upwinding based on the sign of the phase potential gradient
201  // It is easier to hard-code the if/else because it is difficult to address elem and face variables in a uniform way
202 
203  if( potDif >= 0 ) // the element is upstream
204  {
205 
206  // compute the phase flux and derivatives using the element mobility
207  phaseFlux = m_phaseMob[er][esr][ei][ip] * f;
208  dPhaseFlux_dP = m_phaseMob[er][esr][ei][ip] * dF_dP + m_dPhaseMob[er][esr][ei][ip][Deriv::dP] * f;
209  for( integer jc = 0; jc < numComp; ++jc )
210  {
211  dPhaseFlux_dC[jc] =
212  m_phaseMob[er][esr][ei][ip] * dF_dC[jc] + m_dPhaseMob[er][esr][ei][ip][Deriv::dC+jc] * f;
213  }
214 
215  // slice some constitutive arrays to avoid too much indexing in component loop
216  arraySlice1d< real64 const, constitutive::multifluid::USD_PHASE_COMP-3 > phaseCompFracSub =
217  m_phaseCompFrac[er][esr][ei][0][ip];
218  arraySlice2d< real64 const, constitutive::multifluid::USD_PHASE_COMP_DC-3 > dPhaseCompFracSub =
219  m_dPhaseCompFrac[er][esr][ei][0][ip];
220 
221  // compute component fluxes and derivatives using element composition
222  for( integer ic = 0; ic < numComp; ++ic )
223  {
224  real64 const ycp = phaseCompFracSub[ic];
225  stack.compFlux[ic] += phaseFlux * ycp;
226  stack.dCompFlux_dP[ic] += dPhaseFlux_dP * ycp + phaseFlux * dPhaseCompFracSub[ic][Deriv::dP];
227 
228  applyChainRule( numComp,
229  m_dCompFrac_dCompDens[er][esr][ei],
230  dPhaseCompFracSub[ic],
231  dProp_dC,
232  Deriv::dC );
233  for( integer jc = 0; jc < numComp; ++jc )
234  {
235  stack.dCompFlux_dC[ic][jc] += dPhaseFlux_dC[jc] * ycp + phaseFlux * dProp_dC[jc];
236  }
237  }
238 
239  }
240  else // the face is upstream
241  {
242 
243  // compute the phase flux and derivatives using the approximated face mobility
244  // we only have to take derivatives of the potential gradient in this case
245  phaseFlux = facePhaseMob * f;
246  dPhaseFlux_dP = facePhaseMob * dF_dP;
247  for( integer jc = 0; jc < numComp; ++jc )
248  {
249  dPhaseFlux_dC[jc] = facePhaseMob * dF_dC[jc];
250  }
251 
252  // compute component fluxes and derivatives using the face composition
253  for( integer ic = 0; ic < numComp; ++ic )
254  {
255  real64 const ycp = facePhaseCompFrac[0][0][ip][ic];
256  stack.compFlux[ic] += phaseFlux * ycp;
257  stack.dCompFlux_dP[ic] += dPhaseFlux_dP * ycp;
258  for( integer jc = 0; jc < numComp; ++jc )
259  {
260  stack.dCompFlux_dC[ic][jc] += dPhaseFlux_dC[jc] * ycp;
261  }
262  }
263  }
264 
265  // call the lambda in the phase loop to allow the reuse of the phase fluxes and their derivatives
266  // possible use: assemble the derivatives wrt temperature, and the flux term of the energy equation for this phase
267  compFluxKernelOp( ip, er, esr, ei, kf, f,
268  facePhaseMob, facePhaseEnthalpy[0][0], facePhaseCompFrac[0][0],
269  phaseFlux, dPhaseFlux_dP, dPhaseFlux_dC );
270 
271  }
272 
273  // *** end of upwinding
274 
275  // Step 4: populate local flux vector and derivatives
276  for( integer ic = 0; ic < numComp; ++ic )
277  {
278  stack.localFlux[ic] = m_dt * stack.compFlux[ic];
279  stack.localFluxJacobian[ic][0] = m_dt * stack.dCompFlux_dP[ic];
280  for( integer jc = 0; jc < numComp; ++jc )
281  {
282  stack.localFluxJacobian[ic][jc+1] = m_dt * stack.dCompFlux_dC[ic][jc];
283  }
284  }
285 }
286 
287 template< typename FLUIDWRAPPER, typename POLICY, integer NUM_COMP, integer NUM_DOF >
288 template< typename FUNC >
291 complete( localIndex const iconn,
292  StackVariables & stack,
293  FUNC && assemblyKernelOp ) const
294 {
295  using namespace compositionalMultiphaseUtilities;
296  using Order = BoundaryStencil::Order;
297 
298  if( AbstractBase::m_kernelFlags.isSet( KernelFlags::TotalMassEquation ) )
299  {
300  // Apply equation/variable change transformation(s)
301  real64 work[numDof]{};
302  shiftRowsAheadByOneAndReplaceFirstRowWithColumnSum( numComp, numDof, stack.localFluxJacobian, work );
303  shiftElementsAheadByOneAndReplaceFirstElementWithSum( numComp, stack.localFlux );
304  }
305 
306  // add contribution to residual and jacobian into:
307  // - the component mass balance equations (i = 0 to i = numComp-1)
308  // note that numDof includes derivatives wrt temperature if this class is derived in ThermalKernels
309  if( m_ghostRank[m_seri( iconn, Order::ELEM )][m_sesri( iconn, Order::ELEM )][m_sei( iconn, Order::ELEM )] < 0 )
310  {
311  globalIndex const globalRow = m_dofNumber[m_seri( iconn, Order::ELEM )][m_sesri( iconn, Order::ELEM )][m_sei( iconn, Order::ELEM )];
312  localIndex const localRow = LvArray::integerConversion< localIndex >( globalRow - m_rankOffset );
313  GEOS_ASSERT_GE( localRow, 0 );
314  GEOS_ASSERT_GT( AbstractBase::m_localMatrix.numRows(), localRow + numComp );
315 
316  for( integer ic = 0; ic < numComp; ++ic )
317  {
318  RAJA::atomicAdd( parallelDeviceAtomic{}, &AbstractBase::m_localRhs[localRow + ic], stack.localFlux[ic] );
319  AbstractBase::m_localMatrix.addToRowBinarySearchUnsorted< parallelDeviceAtomic >
320  ( localRow + ic,
321  stack.dofColIndices,
322  stack.localFluxJacobian[ic],
323  numDof );
324  }
325 
326  // call the lambda to assemble additional terms, such as thermal terms
327  assemblyKernelOp( localRow );
328  }
329 }
330 
331 } // namespace isothermalCompositionalMultiphaseFVMKernels
332 } // namespace geos
333 
334 #endif // GEOS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONAL_DIRICHLETFLUXCOMPUTEKERNEL_IMPL_HPP
#define GEOS_HOST_DEVICE
Marks a host-device function.
Definition: GeosxMacros.hpp:49
#define GEOS_ASSERT_GT(lhs, rhs)
Assert that one value compares greater than the other in debug builds.
Definition: Logger.hpp:919
#define GEOS_ASSERT_GE(lhs, rhs)
Assert that one value compares greater than or equal to the other in debug builds.
Definition: Logger.hpp:936
The FaceManager class provides an interface to ObjectManagerBase in order to manage face data.
Definition: FaceManager.hpp:44
A struct to automatically construct and store element view accessors.
A struct to automatically construct and store element view accessors.
GEOS_HOST_DEVICE void computeFlux(localIndex const iconn, StackVariables &stack, FUNC &&compFluxKernelOp=NoOpFunc{}) const
Compute the local Dirichlet face flux contributions to the residual and Jacobian.
GEOS_HOST_DEVICE void complete(localIndex const iconn, StackVariables &stack, FUNC &&assemblyKernelOp=NoOpFunc{}) const
Performs the complete phase for the kernel.
DirichletFluxComputeKernel(integer const numPhases, globalIndex const rankOffset, FaceManager const &faceManager, BoundaryStencilWrapper const &stencilWrapper, FLUIDWRAPPER const &fluidWrapper, DofNumberAccessor const &dofNumberAccessor, CompFlowAccessors const &compFlowAccessors, MultiFluidAccessors const &multiFluidAccessors, CapPressureAccessors const &capPressureAccessors, PermeabilityAccessors const &permeabilityAccessors, real64 const dt, CRSMatrixView< real64, globalIndex const > const &localMatrix, arrayView1d< real64 > const &localRhs, BitFlags< KernelFlags > kernelFlags)
Constructor for the kernel interface.
void launchKernel(localIndex const numConnections)
Launch the kernel for a given number of connections.
GEOS_HOST_DEVICE void setup(localIndex const iconn, StackVariables &stack) const
Performs the setup phase for the kernel.
ArrayView< T, 1 > arrayView1d
Alias for 1D array view.
Definition: DataTypes.hpp:179
GEOS_GLOBALINDEX_TYPE globalIndex
Global index type (for indexing objects across MPI partitions).
Definition: DataTypes.hpp:87
LvArray::StackArray< T, NDIM, PERMUTATION, localIndex, MAXSIZE > StackArray
Multidimensional stack-based array type. See LvArray:StackArray for details.
Definition: DataTypes.hpp:155
ArraySlice< T, 2, USD > arraySlice2d
Alias for 2D array slice.
Definition: DataTypes.hpp:199
double real64
64-bit floating point type.
Definition: DataTypes.hpp:98
GEOS_LOCALINDEX_TYPE localIndex
Local index type (for indexing objects within an MPI partition).
Definition: DataTypes.hpp:84
ArraySlice< T, 1, USD > arraySlice1d
Alias for 1D array slice.
Definition: DataTypes.hpp:183
LvArray::CRSMatrixView< T, COL_INDEX, INDEX_TYPE const, LvArray::ChaiBuffer > CRSMatrixView
Alias for CRS Matrix View.
Definition: DataTypes.hpp:309
int integer
Signed integer type.
Definition: DataTypes.hpp:81
Defines the order of element/face in the stencil.
static constexpr localIndex ELEM
Order of element index in stencil.
Kernel variables (dof numbers, jacobian and residual) located on the stack.
real64 dCompFlux_dC[numComp][numComp]
Derivatives of component fluxes wrt component densities.
globalIndex dofColIndices[numDof]
Indices of the matrix rows/columns corresponding to the dofs in this face.