Skip to content

Commit f68bd45

Browse files
committed
Implemented spring boundary conditions
- Spring bc is zero robin bc, following Boon et al., Computat. Geosci. 2026 and Nordbotten & Keilegavlen, Comput. Math. Appl. 2025
1 parent 22cff7c commit f68bd45

4 files changed

Lines changed: 237 additions & 48 deletions

File tree

opm/models/discretization/common/tpsalinearizer.hpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,17 +244,19 @@ class TpsaLinearizer
244244
{
245245
for (auto& bdyInfo : boundaryInfo_) {
246246
// Get boundary information from problem
247-
const auto [type, displacementAD] = problem_().mechBoundaryCondition(bdyInfo.cell, bdyInfo.dir);
247+
const auto& mechBC = problem_().mechBoundaryCondition(bdyInfo.cell, bdyInfo.dir);
248248

249249
// Strip the unnecessary (and zero anyway) derivatives off displacement
250250
std::vector<double> displacement(3, 0.0);
251251
for (std::size_t ii = 0; ii < displacement.size(); ++ii) {
252-
displacement[ii] = displacementAD[ii].value();
252+
displacement[ii] = mechBC.displacement[ii].value();
253253
}
254254

255255
// Update boundary information
256-
bdyInfo.bcdata.type = type;
256+
bdyInfo.bcdata.type = mechBC.type;
257257
bdyInfo.bcdata.displacement = displacement;
258+
bdyInfo.bcdata.shearModulus = mechBC.shearModulus;
259+
bdyInfo.bcdata.distance = mechBC.distance;
258260
}
259261
}
260262

@@ -419,16 +421,21 @@ class TpsaLinearizer
419421
}
420422

421423
// Get boundary information from problem()
422-
const auto [type, displacementAD] = problem_().mechBoundaryCondition(myIdx, dir_id);
424+
const auto& mechBC = problem_().mechBoundaryCondition(myIdx, dir_id);
423425

424426
// Strip the unnecessary (and zero anyway) derivatives off displacement
425427
std::vector<double> displacement(3, 0.0);
426428
for (std::size_t ii = 0; ii < displacement.size(); ++ii) {
427-
displacement[ii] = displacementAD[ii].value();
429+
displacement[ii] = mechBC.displacement[ii].value();
428430
}
429431

430432
// Insert boundary condition data in container
431-
BoundaryConditionData bcdata { type, displacement, bfIndex, bfArea };
433+
BoundaryConditionData bcdata{mechBC.type,
434+
displacement,
435+
mechBC.shearModulus,
436+
mechBC.distance,
437+
bfIndex,
438+
bfArea};
432439
boundaryInfo_.push_back( { myIdx, dir_id, bfIndex, bcdata } );
433440
++bfIndex;
434441
continue;
@@ -782,6 +789,8 @@ class TpsaLinearizer
782789
{
783790
BCMECHType type;
784791
std::vector<double> displacement;
792+
Scalar shearModulus;
793+
Scalar distance;
785794
unsigned boundaryFaceIndex;
786795
double faceArea;
787796
};

opm/models/tpsa/elasticitylocalresidualtpsa.hpp

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@
4343

4444
namespace Opm {
4545

46+
namespace detail {
47+
48+
/*! \brief Computes modulo 3 of possibly negative integers to get indices in cross product.
49+
*
50+
* If i = x-dir(=0), we want y-dir(=1) and z-dir(=2), hence -1 mod 3 must equal 2 and not -1
51+
*/
52+
inline int modNeg(int i)
53+
{
54+
return ((i % 3) + 3) % 3;
55+
}
56+
57+
} // namespace detail
58+
4659
/*!
4760
* \brief Calculation of (linear) elasticity model terms for the residual
4861
*
@@ -167,15 +180,11 @@ class ElasticityLocalResidual
167180
// ///
168181
// Displacement, rotation and solid pressure (directional-dependent) equations
169182
// ///
170-
// Lambda function for computing modulo 3 of possibly negative integers to get indices in cross product.
171-
// E.g. if i = x-dir(=0), we want y-dir(=1) and z-dir(=2), hence -1 mod 3 must equal 2 and not -1
172-
auto modNeg = [](int i) { return ((i % 3) + 3) % 3; };
173-
174183
// Loop over x-, y- and z-dir (corresponding to dirIdx = 0, 1, 2)
175184
for (int dirIdx = 0; dirIdx < 3; ++dirIdx) {
176185
// Direction indices in cross-product
177-
unsigned dirIdxNeg = modNeg(dirIdx - 1);
178-
unsigned dirIdxPos = modNeg(dirIdx + 1);
186+
unsigned dirIdxNeg = detail::modNeg(dirIdx - 1);
187+
unsigned dirIdxPos = detail::modNeg(dirIdx + 1);
179188

180189
// Displacement equation
181190
const Scalar faceNormalDir = faceNormal[dirIdx];
@@ -247,6 +256,13 @@ class ElasticityLocalResidual
247256
problem,
248257
globalIndex);
249258
break;
259+
case BCMECHType::SPRING:
260+
computeBoundaryTermSpring(bndryTerm,
261+
materialState,
262+
bdyInfo,
263+
problem,
264+
globalIndex);
265+
break;
250266
default:
251267
throw std::logic_error("Unknown boundary condition type " +
252268
std::to_string(static_cast<int>(bdyInfo.type)) +
@@ -295,15 +311,11 @@ class ElasticityLocalResidual
295311
// ///
296312
// Displacement equation
297313
// ///
298-
// Lambda function for computing modulo 3 of possibly negative integers to get indices in cross product.
299-
// E.g. if i = x-dir(=0), we want y-dir(=1) and z-dir(=2), hence -1 mod 3 must equal 2 and not -1
300-
auto modNeg = [](int i) { return ((i % 3) + 3) % 3; };
301-
302314
// Loop over x-, y- and z-dir (corresponding to dirIdx = 0, 1, 2)
303315
for (int dirIdx = 0; dirIdx < 3; ++dirIdx) {
304316
// Direction indices in cross-product
305-
unsigned dirIdxNeg = modNeg(dirIdx - 1);
306-
unsigned dirIdxPos = modNeg(dirIdx + 1);
317+
unsigned dirIdxNeg = detail::modNeg(dirIdx - 1);
318+
unsigned dirIdxPos = detail::modNeg(dirIdx + 1);
307319

308320
// Displacement equation
309321
const Scalar faceNormalDir = faceNormal[dirIdx];
@@ -362,10 +374,6 @@ class ElasticityLocalResidual
362374
// ///
363375
// Rotation and solid pressure (directional-dependent) equations
364376
// ///
365-
// Lambda function for computing modulo 3 of possibly negative integers to get indices in cross product.
366-
// E.g. if i = x-dir(=0), we want y-dir(=1) and z-dir(=2), hence -1 mod 3 must equal 2 and not -1
367-
auto modNeg = [](int i) { return ((i % 3) + 3) % 3; };
368-
369377
// Pre-compute dot product for rotation equation
370378
Evaluation dotProd = 0;
371379
for (int dirIdx = 0; dirIdx < 3; ++dirIdx) {
@@ -375,8 +383,8 @@ class ElasticityLocalResidual
375383
// Loop over x-, y- and z-dir (corresponding to dirIdx = 0, 1, 2)
376384
for (int dirIdx = 0; dirIdx < 3; ++dirIdx) {
377385
// Direction indices in cross-product
378-
unsigned dirIdxNeg = modNeg(dirIdx - 1);
379-
unsigned dirIdxPos = modNeg(dirIdx + 1);
386+
unsigned dirIdxNeg = detail::modNeg(dirIdx - 1);
387+
unsigned dirIdxPos = detail::modNeg(dirIdx + 1);
380388

381389
// Rotation equation
382390
const Scalar faceNormalDir = faceNormal[dirIdx];
@@ -400,6 +408,98 @@ class ElasticityLocalResidual
400408
}
401409
}
402410

411+
/*!
412+
* \brief Calculate spring boundary condition in TPSA formulation
413+
*
414+
* \param bndryTerm Boundary term vector
415+
* \param materialState Material state container
416+
* \param bdyInfo Boundary condition info container
417+
* \param problem Flow problem
418+
* \param globalIndex Cell index
419+
*
420+
* \note Spring boundary condition sets a shear modulus at fictitious point outside a certain
421+
* distance outside the boundary
422+
*/
423+
template <class BoundaryConditionData>
424+
static void computeBoundaryTermSpring(Dune::FieldVector<Evaluation, numEq>& bndryTerm,
425+
const MaterialState& materialState,
426+
const BoundaryConditionData& bdyInfo,
427+
Problem& problem,
428+
unsigned globalIndex)
429+
{
430+
// Reset bondary term
431+
bndryTerm = 0.0;
432+
433+
// Extract cell and boundary information
434+
const unsigned bfIdx = bdyInfo.boundaryFaceIndex;
435+
const auto& faceNormal = problem.cellFaceNormalBoundary(globalIndex, bfIdx);
436+
const Scalar distIn = problem.normalDistanceBoundary(globalIndex, bfIdx);
437+
const Scalar distEx = bdyInfo.distance;
438+
const Scalar sModulusIn = problem.shearModulus(globalIndex);
439+
const Scalar sModulusEx = bdyInfo.shearModulus;
440+
441+
// Calculate face properties
442+
const Scalar weightIn = distIn / sModulusIn;
443+
const Scalar weightEx = distEx / sModulusEx;
444+
const Scalar weightAvgIn = weightIn / (weightIn + weightEx);
445+
const Scalar weightAvgEx = 1.0 - weightAvgIn;
446+
const Scalar weightProd = weightIn * weightEx;
447+
const Scalar normDist = distIn + distEx;
448+
449+
// Effective shear modulus
450+
const Scalar eff_sModulus = weightAvgIn * sModulusIn + weightAvgEx * sModulusEx;
451+
452+
// Distance ratio
453+
const Scalar distRatio = 0.5 * weightProd / normDist;
454+
455+
// Solid pressure equation (direction-independent equation)
456+
const Evaluation& solidP = materialState.solidPressure();
457+
bndryTerm[contiSolidPresEqIdx] +=
458+
distRatio * eff_sModulus * solidP;
459+
460+
// Pre-compute dot product for rotation equation
461+
Evaluation dotProd = 0;
462+
for (int dirIdx = 0; dirIdx < 3; ++dirIdx) {
463+
dotProd += faceNormal[dirIdx] * materialState.rotation(dirIdx);
464+
}
465+
466+
// Loop over x-, y- and z-dir (corresponding to dirIdx = 0, 1, 2)
467+
for (int dirIdx = 0; dirIdx < 3; ++dirIdx) {
468+
// Direction indices in cross-product
469+
unsigned dirIdxNeg = detail::modNeg(dirIdx - 1);
470+
unsigned dirIdxPos = detail::modNeg(dirIdx + 1);
471+
472+
// Displacement equation
473+
const Scalar faceNormalDir = faceNormal[dirIdx];
474+
const Scalar faceNormalNeg = faceNormal[dirIdxNeg];
475+
const Scalar faceNormalPos = faceNormal[dirIdxPos];
476+
477+
const Evaluation& disp = materialState.displacement(dirIdx);
478+
479+
const Evaluation& rotNeg = materialState.rotation(dirIdxNeg);
480+
const Evaluation& rotPos = materialState.rotation(dirIdxPos);
481+
482+
bndryTerm[conti0EqIdx + dirIdx] +=
483+
2.0 * (eff_sModulus / normDist) * disp
484+
- weightAvgIn * (faceNormalNeg * rotPos - faceNormalPos * rotNeg)
485+
- faceNormalDir * weightAvgIn * solidP;
486+
487+
// Rotation equation
488+
const Evaluation& dispNeg = materialState.displacement(dirIdxNeg);
489+
const Evaluation& dispPos = materialState.displacement(dirIdxPos);
490+
491+
const Evaluation& rot = materialState.rotation(dirIdx);
492+
493+
bndryTerm[contiRotEqIdx + dirIdx] +=
494+
- weightAvgEx * (faceNormalNeg * dispPos - faceNormalPos * dispNeg)
495+
+ distRatio * eff_sModulus * (dotProd * faceNormalDir - rot);
496+
497+
// Solid pressure (directional-dependent) equation
498+
bndryTerm[contiSolidPresEqIdx] +=
499+
- faceNormalDir * weightAvgEx * disp;
500+
}
501+
}
502+
403503
/*!
404504
* \brief Calculate source term in TPSA formulation
405505
*

opm/simulators/flow/FlowProblemTPSA.hpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,20 @@ class FlowProblemTPSA : public FlowProblemBlackoil<TypeTag>
8888

8989
using CartesianIndexMapper = Dune::CartesianIndexMapper<Grid>;
9090
using DimVector = Dune::FieldVector<Scalar, dimWorld>;
91+
using EvalDimVector = Dune::FieldVector<Evaluation, dimWorld>;
9192
using FaceProperties = FacePropertiesTPSA<Grid, GridView, ElementMapper, CartesianIndexMapper, Scalar>;
9293
using InitialMaterialState = MaterialStateTPSA<Scalar>;
9394
using Toolbox = MathToolbox<Evaluation>;
9495

96+
// Boundary condition helper struct
97+
struct MechBCData
98+
{
99+
BCMECHType type;
100+
EvalDimVector displacement;
101+
Scalar distance;
102+
Scalar shearModulus;
103+
};
104+
95105
// ///
96106
// Public functions
97107
// ///
@@ -277,31 +287,36 @@ class FlowProblemTPSA : public FlowProblemBlackoil<TypeTag>
277287
* \note Only BCMECHTYPE = FREE and NONE implemented. FIXED will/should throw an error when computed in local
278288
* residual!
279289
*/
280-
std::pair<BCMECHType, Dune::FieldVector<Evaluation, 3>>
290+
MechBCData
281291
mechBoundaryCondition(const unsigned int globalSpaceIdx, const int directionId)
282292
{
283293
// Default boundary conditions if BCCON/BCMECH not defined
284294
if (!this->nonTrivialBoundaryConditions()) {
285-
return { BCMECHType::NONE, Dune::FieldVector<Evaluation, 3>{0.0, 0.0, 0.0} };
295+
return {BCMECHType::NONE, EvalDimVector{0.0, 0.0, 0.0}, 0.0, 0.0};
286296
}
287297

288298
// Default for BCMECH index = 0 or no BCMECH defined at current episode
289299
FaceDir::DirEnum dir = FaceDir::FromIntersectionIndex(directionId);
290300
const auto& schedule = this->simulator().vanguard().schedule();
291301
if (this->bcindex_(dir)[globalSpaceIdx] == 0
292302
|| schedule[this->episodeIndex()].bcstate.size() == 0) {
293-
return {BCMECHType::NONE, Dune::FieldVector<Evaluation, 3>{0.0, 0.0, 0.0} };
303+
return {BCMECHType::NONE, EvalDimVector{0.0, 0.0, 0.0}, 0.0, 0.0};
294304
}
295305

296306
// Get current BC
297307
const auto& bc =
298308
schedule[this->episodeIndex()].bcstate[this->bcindex_(dir)[globalSpaceIdx]];
299309
if (bc.bcmechtype == BCMECHType::FREE) {
300-
return { BCMECHType::FREE, Dune::FieldVector<Evaluation, 3>{0.0, 0.0, 0.0} };
310+
return {BCMECHType::FREE, EvalDimVector{0.0, 0.0, 0.0}, 0.0, 0.0};
301311
}
302-
else {
303-
return { bc.bcmechtype, Dune::FieldVector<Evaluation, 3>{0.0, 0.0, 0.0} };
312+
if (bc.bcmechtype == BCMECHType::SPRING) {
313+
const auto& mechbcval = bc.mechbcvalue;
314+
return {BCMECHType::SPRING, EvalDimVector{0.0, 0.0, 0.0},
315+
mechbcval.distance, mechbcval.shearmodulus};
304316
}
317+
318+
// Default return
319+
return {bc.bcmechtype, EvalDimVector{0.0, 0.0, 0.0}, 0.0, 0.0};
305320
}
306321

307322
/*!

0 commit comments

Comments
 (0)