opm-simulators
tpsalinearizer.hpp
1 // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 // vi: set et ts=4 sw=4 sts=4:
3 /*
4  Copyright 2025 NORCE AS
5 
6  This file is part of the Open Porous Media project (OPM).
7 
8  OPM is free software: you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation, either version 2 of the License, or
11  (at your option) any later version.
12 
13  OPM is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with OPM. If not, see <http://www.gnu.org/licenses/>.
20 
21  Consult the COPYING file in the top-level source directory of this
22  module for the precise wording of the license and the list of
23  copyright holders.
24 */
25 #ifndef TPSA_LINEARIZER_HPP
26 #define TPSA_LINEARIZER_HPP
27 
28 #include <dune/common/fvector.hh>
29 
30 #include <opm/common/TimingMacros.hpp>
31 
32 #include <opm/grid/utility/SparseTable.hpp>
33 
34 #include <opm/input/eclipse/Schedule/BCProp.hpp>
35 
36 #include <opm/material/materialstates/MaterialStateTPSA.hpp>
37 
39 #include <opm/models/tpsa/tpsabaseproperties.hpp>
40 
41 #include <cassert>
42 #include <set>
43 #include <vector>
44 
45 
46 namespace Opm {
47 
51 template<class TypeTag>
53 {
54  using Constraints = GetPropType<TypeTag, Properties::Constraints>; // TODO: make TPSA constraints
66 
67  using MaterialState = MaterialStateTPSA<Evaluation>;
68 
69  enum { numEq = getPropValue<TypeTag, Properties::NumEqTPSA>() };
70 
71  using ADVectorBlock = Dune::FieldVector<Evaluation, numEq>;
72  using MatrixBlock = typename SparseMatrixAdapter::MatrixBlock;
73  using VectorBlock = Dune::FieldVector<Scalar, numEq>;
74 
75  using StressInfoVector = Dune::FieldVector<Scalar, 3>;
76 
77 public:
78  // ///
79  // Public functions
80  // ///
85  {
86  simulatorPtr_ = nullptr;
87  }
88 
97  void init(Simulator& simulator)
98  {
99  simulatorPtr_ = &simulator;
100  eraseMatrix();
101  }
102 
109  void eraseMatrix()
110  {
111  jacobian_.reset();
112  }
113 
117  void finalize()
118  {
119  jacobian_->finalize();
120  }
121 
127  template <class SubDomainType>
128  void resetSystem_(const SubDomainType& domain)
129  {
130  if (!jacobian_) {
131  initFirstIteration_();
132  }
133  for (int globI : domain.cells) {
134  residual_[globI] = 0.0;
135  jacobian_->clearRow(globI, 0.0);
136  }
137  }
138 
142  void linearize()
143  {
144  linearizeDomain();
146  }
147 
155  {
156  int succeeded;
157  try {
158  linearizeDomain(fullDomain_);
159  succeeded = 1;
160  }
161  catch (const std::exception& e) {
162  std::cout << "rank " << simulator_().gridView().comm().rank()
163  << " caught an exception while linearizing TPSA system:" << e.what()
164  << "\n" << std::flush;
165  succeeded = 0;
166  }
167  catch (...) {
168  std::cout << "rank " << simulator_().gridView().comm().rank()
169  << " caught an exception while linearizing TPSA system"
170  << "\n" << std::flush;
171  succeeded = 0;
172  }
173  succeeded = simulator_().gridView().comm().min(succeeded);
174 
175  if (!succeeded) {
176  throw NumericalProblem("A process did not succeed in linearizing the TPSA system");
177  }
178  }
179 
188  template <class SubDomainType>
189  void linearizeDomain(const SubDomainType& domain)
190  {
191  OPM_TIMEBLOCK(linearizeDomain);
192 
193  // We defer the initialization of the Jacobian matrix until here because the auxiliary modules usually assume
194  // the problem, model and grid to be fully initialized'
195  if (!jacobian_) {
196  initFirstIteration_();
197  }
198 
199  if (domain.cells.size() == flowModel_().numTotalDof()) {
200  // We are on the full domain.
201  resetSystem_();
202  }
203  else {
204  resetSystem_(domain);
205  }
206 
207  linearize_(domain);
208  }
209 
214  { }
215 
223  void setResAndJacobi(VectorBlock& res, MatrixBlock& bMat, const ADVectorBlock& resid) const
224  {
225  // Scalar local residual
226  for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) {
227  res[eqIdx] = resid[eqIdx].value();
228  }
229 
230  // A[dofIdx][focusDofIdx][eqIdx][pvIdx] is the partial derivative of the residual function 'eqIdx' for the
231  // degree of freedom 'dofIdx' with regard to the focus variable 'pvIdx' of the degree of freedom 'focusDofIdx'
232  for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) {
233  for (unsigned pvIdx = 0; pvIdx < numEq; ++pvIdx) {
234  bMat[eqIdx][pvIdx] = resid[eqIdx].derivative(pvIdx);
235  }
236  }
237  }
238 
243  {
244  for (auto& bdyInfo : boundaryInfo_) {
245  // Get boundary information from problem
246  const auto [type, displacementAD] = problem_().mechBoundaryCondition(bdyInfo.cell, bdyInfo.dir);
247 
248  // Strip the unnecessary (and zero anyway) derivatives off displacement
249  std::vector<double> displacement(3, 0.0);
250  for (std::size_t ii = 0; ii < displacement.size(); ++ii) {
251  displacement[ii] = displacementAD[ii].value();
252  }
253 
254  // Update boundary information
255  bdyInfo.bcdata.type = type;
256  bdyInfo.bcdata.displacement = displacement;
257  }
258  }
259 
260  // ///
261  // Public get and set functions
262  // ///
268  const SparseMatrixAdapter& jacobian() const
269  { return *jacobian_; }
270 
276  SparseMatrixAdapter& jacobian()
277  { return *jacobian_; }
278 
284  const GlobalEqVector& residual() const
285  { return residual_; }
286 
292  GlobalEqVector& residual()
293  { return residual_; }
294 
304  { return linearizationType_; }
305 
311  const auto& getStressInfo() const
312  {
313  return stressInfo_;
314  }
315 
323  std::map<unsigned, Constraints> constraintsMap() const
324  { return {}; }
325 
331  void setLinearizationType(LinearizationType linearizationType)
332  { linearizationType_ = linearizationType; }
333 
334 private:
335  // ///
336  // Private functions
337  // ///
344  void createMatrix_()
345  {
346  OPM_TIMEBLOCK(createMatrixTPSA);
347 
348  // If the Jacobian has been initialize before, we jump out
349  if (!neighborInfo_.empty()) {
350  return;
351  }
352 
353  // Init. the stencil
354  const auto& flowModel = flowModel_();
355  Stencil stencil(gridView_(), flowModel.dofMapper());
356 
357  // Build up sparsity patterns and neighboring information for Jacobian and linearization
358  std::vector<std::set<unsigned>> sparsityPattern(flowModel.numTotalDof());
359  unsigned numCells = flowModel.numTotalDof();
360  neighborInfo_.reserve(numCells, 6 * numCells);
361  std::vector<NeighborInfo> loc_nbinfo;
362 
363  stressInfo_.reserve(numCells, 6 * numCells);
364  std::vector<StressInfo> loc_stressinfo;
365  StressInfoVector nullTraction(0.0);
366  StressInfoVector nullFaceNormal(0.0);
367  for (const auto& elem : elements(gridView_())) {
368  // Loop over primary dofs in the element
369  stencil.update(elem);
370 
371  for (unsigned primaryDofIdx = 0; primaryDofIdx < stencil.numPrimaryDof(); ++primaryDofIdx) {
372  // Build up neighboring information for curret primary dof
373  const unsigned myIdx = stencil.globalSpaceIndex(primaryDofIdx);
374  loc_nbinfo.resize(stencil.numDof() - 1);
375 
376  // Build information for stress output
377  const int numFaces = stencil.numBoundaryFaces() + stencil.numInteriorFaces();
378  loc_stressinfo.resize(numFaces);
379 
380  for (unsigned dofIdx = 0; dofIdx < stencil.numDof(); ++dofIdx) {
381  // NOTE: NeighborInfo could/should be expanded with cell face parameters located in problem_()
382  // needed when computing face terms in LocalResidual
383  const unsigned neighborIdx = stencil.globalSpaceIndex(dofIdx);
384  sparsityPattern[myIdx].insert(neighborIdx);
385  if (dofIdx > 0) {
386  const auto scvfIdx = dofIdx - 1;
387  const auto& scvf = stencil.interiorFace(scvfIdx);
388  const Scalar area = scvf.area();
389  loc_nbinfo[dofIdx - 1] = NeighborInfo{ neighborIdx, area, nullptr };
390 
391  int faceId = scvf.dirId();
392  loc_stressinfo[dofIdx - 1] =
393  StressInfo{faceId, nullTraction, nullFaceNormal, area};
394  }
395  }
396 
397  // Insert local neighbor info
398  neighborInfo_.appendRow(loc_nbinfo.begin(), loc_nbinfo.end());
399 
400  // Boundary condition information
401  unsigned bfIndex = 0;
402  for (const auto& intersection : intersections(gridView_(), elem)) {
403  if (intersection.boundary()) {
404  // Get boundary face direction
405  const auto& bf = stencil.boundaryFace(bfIndex);
406  const int dir_id = bf.dirId();
407  const auto bfArea = bf.area();
408 
409  // Initialize boundary information for stress output container
410  // OBS: NNCs are not implemented with TPSA, hence traction and face
411  // normal vector will stay zero for NNC connections!
412  loc_stressinfo[stencil.numInteriorFaces() + bfIndex] =
413  StressInfo{dir_id, nullTraction, nullFaceNormal, bfArea};
414 
415  // Skip NNCs
416  if (dir_id < 0) {
417  continue;
418  }
419 
420  // Get boundary information from problem()
421  const auto [type, displacementAD] = problem_().mechBoundaryCondition(myIdx, dir_id);
422 
423  // Strip the unnecessary (and zero anyway) derivatives off displacement
424  std::vector<double> displacement(3, 0.0);
425  for (std::size_t ii = 0; ii < displacement.size(); ++ii) {
426  displacement[ii] = displacementAD[ii].value();
427  }
428 
429  // Insert boundary condition data in container
430  BoundaryConditionData bcdata { type, displacement, bfIndex, bfArea };
431  boundaryInfo_.push_back( { myIdx, dir_id, bfIndex, bcdata } );
432  ++bfIndex;
433  continue;
434  }
435  if (!intersection.neighbor()) {
436  ++bfIndex;
437  continue;
438  }
439  }
440  stressInfo_.appendRow(loc_stressinfo.begin(), loc_stressinfo.end());
441  }
442  }
443 
444  // Allocate Jacobian matrix and pointers to its sub-blocks
445  jacobian_ = std::make_unique<SparseMatrixAdapter>(simulator_());
446  diagMatAddress_.resize(numCells);
447  jacobian_->reserve(sparsityPattern);
448  for (unsigned globI = 0; globI < numCells; globI++) {
449  const auto& nbInfos = neighborInfo_[globI];
450  diagMatAddress_[globI] = jacobian_->blockAddress(globI, globI);
451  for (auto& nbInfo : nbInfos) {
452  nbInfo.matBlockAddress = jacobian_->blockAddress(nbInfo.neighbor, globI);
453  }
454  }
455 
456  // Create full domain
457  fullDomain_.cells.resize(numCells);
458  std::iota(fullDomain_.cells.begin(), fullDomain_.cells.end(), 0);
459  }
460 
466  template <class SubDomainType>
467  void linearize_(const SubDomainType& domain)
468  {
469  OPM_TIMEBLOCK(linearizeTPSA);
470 
471  // Extract misc. variables used in linearization
472  const auto& flowModel = flowModel_();
473  const auto& geoMechModel = geoMechModel_();
474  auto& problem = problem_();
475  const unsigned int numCells = domain.cells.size();
476 
477 #ifdef _OPENMP
478 #pragma omp parallel for
479 #endif
480  // Loop over cells in the domain and compute local residual and jacobian
481  for (unsigned ii = 0; ii < numCells; ++ii) {
482  OPM_TIMEBLOCK_LOCAL(linearizationForEachCellTPSA, Subsystem::Assembly);
483 
484  const unsigned globI = domain.cells[ii];
485  const auto& nbInfos = neighborInfo_[globI];
486  VectorBlock res(0.0);
487  MatrixBlock bMat(0.0);
488  ADVectorBlock adres(0.0);
489  const MaterialState& materialStateIn = geoMechModel.materialState(globI, /*timeIdx=*/0);
490 
491  // ///
492  // Face term
493  // ///
494  {
495  OPM_TIMEBLOCK_LOCAL(faceCalculationForEachCellTPSA, Subsystem::Assembly);
496 
497  // Loop over neighboring cells
498  short loc = 0;
499  for (const auto& nbInfo : nbInfos) {
500  OPM_TIMEBLOCK_LOCAL(calculationForEachFaceTPSA, Subsystem::Assembly);
501 
502  // Reset local residual and Jacobian
503  res = 0.0;
504  bMat = 0.0;
505  adres = 0.0;
506 
507  // Neighbor information
508  const unsigned globJ = nbInfo.neighbor;
509  assert(globJ != globI);
510  const MaterialState& materialStateEx = geoMechModel.materialState(globJ, /*timeIdx=*/0);
511 
512  // Compute local face term
513  LocalResidual::computeFaceTerm(adres,
514  materialStateIn,
515  materialStateEx,
516  problem,
517  globI,
518  globJ);
519  adres *= nbInfo.faceArea;
520 
521  // Extract residual and sub-block Jacobian entries from computed AD residual
522  setResAndJacobi(res, bMat, adres);
523 
524  // Insert into global residual
525  residual_[globI] += res;
526 
527  // Insert contribution to (globI, globI) sub-block
528  // SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat);
529  *diagMatAddress_[globI] += bMat;
530 
531  // Insert contribution to (globJ, globI) sub-block
532  // Note: since LocalResidual::computeFaceTerm have Evaluation on globI primary variables, it is
533  // natural to insert Jacobian entries for (globJ, globI) here, since we only need to flip the signs
534  // in the calculated face terms
535  // SparseAdapter syntax: jacobian_->addToBlock(globJ, globI, bMat);
536  bMat *= -1.0;
537  *nbInfo.matBlockAddress += bMat;
538 
539  // Insert interior traction vector
540  // OBS: Assume traction vector is the three first entries in residual!
541  const auto& faceNormal = problem.cellFaceNormal(globI, globJ);
542  stressInfo_[globI][loc].faceNormal = faceNormal;
543  stressInfo_[globI][loc].faceArea = nbInfo.faceArea;
544  for (unsigned tractionIdx = 0; tractionIdx < 3; ++tractionIdx) {
545  stressInfo_[globI][loc].traction[tractionIdx] = res[tractionIdx];
546  }
547  ++loc;
548  }
549  }
550 
551  // ///
552  // Volume term
553  // //
554  adres = 0.0;
555  {
556  OPM_TIMEBLOCK_LOCAL(computeVolumeTerm, Subsystem::Assembly);
557 
558  // Compute local volume term
559  LocalResidual::computeVolumeTerm(adres,
560  materialStateIn,
561  problem,
562  globI);
563  }
564  const double volume = flowModel.dofTotalVolume(globI);
565  adres *= volume;
566 
567  // Extract residual and sub-block Jacobian entries from computed AD residual
568  setResAndJacobi(res, bMat, adres);
569 
570  // Insert in global residual
571  residual_[globI] += res;
572 
573  // Insert contribution to (globI, globI) sub-block
574  // SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat);
575  *diagMatAddress_[globI] += bMat;
576 
577  // ///
578  // Source term
579  // ///
580  res = 0.0;
581  bMat = 0.0;
582  adres = 0.0;
583 
584  // Compute local source term
585  LocalResidual::computeSourceTerm(adres,
586  problem,
587  globI,
588  0);
589  adres *= -volume;
590 
591  // Extract residual and sub-block Jacobian entries from computed AD residual
592  setResAndJacobi(res, bMat, adres);
593 
594  // Insert into global residual
595  residual_[globI] += res;
596 
597  // Insert contribution to (globI, globI) sub-block
598  // SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat);
599  *diagMatAddress_[globI] += bMat;
600  } // globI loop
601 
602  // ///
603  // Boundary term
604  // ///
605  for (const auto& bdyInfo : boundaryInfo_) {
606  VectorBlock res(0.0);
607  MatrixBlock bMat(0.0);
608  ADVectorBlock adres(0.0);
609  const unsigned globI = bdyInfo.cell;
610  const MaterialState& materialStateIn = geoMechModel.materialState(globI, /*timeIdx=*/0);
611 
612  // Compute local boundary condition
613  LocalResidual::computeBoundaryTerm(adres,
614  materialStateIn,
615  bdyInfo.bcdata,
616  problem,
617  globI);
618  adres *= bdyInfo.bcdata.faceArea;
619 
620  // Extract residual and sub-block Jacobian entries from computed AD residual
621  setResAndJacobi(res, bMat, adres);
622 
623  // Insert in global residual
624  residual_[globI] += res;
625 
626  // Insert contribution to (globI, globI) sub-block
627  // SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat);
628  *diagMatAddress_[globI] += bMat;
629 
630  // Insert boundary traction vector
631  // OBS: Assume traction vector is the three first entries in residual!
632  const auto& nbInfos = neighborInfo_[globI];
633  short loc = nbInfos.size() + bdyInfo.bfIndex;
634  const auto& bndyNormal = problem.cellFaceNormalBoundary(globI, bdyInfo.bfIndex);
635  stressInfo_[globI][loc].faceNormal = bndyNormal;
636  stressInfo_[globI][loc].faceArea = bdyInfo.bcdata.faceArea;
637  for (unsigned tractionIdx = 0; tractionIdx < 3; ++tractionIdx) {
638  stressInfo_[globI][loc].traction[tractionIdx] = res[tractionIdx];
639  }
640  }
641  }
642 
646  void initFirstIteration_()
647  {
648  // initialize the BCRS matrix for the Jacobian of the residual function
649  createMatrix_();
650 
651  // initialize the Jacobian matrix and the vector for the residual function
652  residual_.resize(flowModel_().numTotalDof());
653  resetSystem_();
654  }
655 
659  void resetSystem_()
660  {
661  // Set residual vector entries to zero
662  residual_ = 0.0;
663 
664  // Set all Jacobian matrix entries to zero
665  jacobian_->clear();
666  }
667 
668  // ///
669  // Private get functions
670  // ///
676  Simulator& simulator_()
677  { return *simulatorPtr_; }
678 
684  const Simulator& simulator_() const
685  { return *simulatorPtr_; }
686 
692  Problem& problem_()
693  { return simulator_().problem(); }
694 
700  const Problem& problem_() const
701  { return simulator_().problem(); }
702 
708  FlowModel& flowModel_()
709  { return simulator_().model(); }
710 
716  const FlowModel& flowModel_() const
717  { return simulator_().model(); }
718 
724  GeomechModel& geoMechModel_()
725  { return problem_().geoMechModel(); }
726 
732  const GeomechModel& geoMechModel_() const
733  { return problem_().geoMechModel(); }
734 
740  const GridView& gridView_() const
741  { return problem_().gridView(); }
742 
743  Simulator* simulatorPtr_{};
744  LinearizationType linearizationType_{};
745 
746  std::vector<MatrixBlock*> diagMatAddress_{};
747  std::unique_ptr<SparseMatrixAdapter> jacobian_{};
748  GlobalEqVector residual_;
749 
750  //
751  // Helper structs
752  //
756  struct NeighborInfo
757  {
758  unsigned int neighbor;
759  double faceArea;
760  MatrixBlock* matBlockAddress;
761  };
762  SparseTable<NeighborInfo> neighborInfo_{};
763 
767  struct StressInfo
768  {
769  int faceId;
770  StressInfoVector traction;
771  StressInfoVector faceNormal;
772  double faceArea;
773  };
774 
775  SparseTable<StressInfo> stressInfo_{};
776 
780  struct BoundaryConditionData
781  {
782  BCMECHType type;
783  std::vector<double> displacement;
784  unsigned boundaryFaceIndex;
785  double faceArea;
786  };
787 
791  struct BoundaryInfo
792  {
793  unsigned int cell;
794  int dir;
795  unsigned int bfIndex;
796  BoundaryConditionData bcdata;
797  };
798  std::vector<BoundaryInfo> boundaryInfo_;
799 
803  struct FullDomain
804  {
805  std::vector<int> cells;
806  std::vector<bool> interior;
807  };
808  FullDomain fullDomain_;
809 }; // class TpsaLinearizer
810 
811 } // namespace Opm
812 
813 #endif
std::map< unsigned, Constraints > constraintsMap() const
Get constraints map.
Definition: tpsalinearizer.hpp:323
void linearize()
Linearize the non-linear system.
Definition: tpsalinearizer.hpp:142
typename Properties::Detail::GetPropImpl< TypeTag, Property >::type::type GetPropType
get the type alias defined in the property (equivalent to old macro GET_PROP_TYPE(...))
Definition: propertysystem.hh:233
void finalize()
Finalize creation of Jacobian matrix and make ready for linear solver.
Definition: tpsalinearizer.hpp:117
void updateBoundaryConditionData()
Update boundary condition information.
Definition: tpsalinearizer.hpp:242
void linearizeAuxiliaryEquations()
Linearize auxillary equation.
Definition: tpsalinearizer.hpp:213
const auto & getStressInfo() const
Container with information to compute stress output.
Definition: tpsalinearizer.hpp:311
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45
void eraseMatrix()
Causes the Jacobian matrix to be recreated from scratch before the next iteration.
Definition: tpsalinearizer.hpp:109
GlobalEqVector & residual()
Get residual vector.
Definition: tpsalinearizer.hpp:292
SparseMatrixAdapter & jacobian()
Get Jacobian matrix.
Definition: tpsalinearizer.hpp:276
Linearizes TPSA equations and generates system matrix and residual for linear solver.
Definition: tpsalinearizer.hpp:52
void linearizeDomain()
Linearize the non-linear system for the spatial domain.
Definition: tpsalinearizer.hpp:154
Definition: linearizationtype.hh:33
void init(Simulator &simulator)
Initialize the linearizer.
Definition: tpsalinearizer.hpp:97
The common code for the linearizers of non-linear systems of equations.
const SparseMatrixAdapter & jacobian() const
Get Jacobian matrix.
Definition: tpsalinearizer.hpp:268
void setLinearizationType(LinearizationType linearizationType)
Set linearization type.
Definition: tpsalinearizer.hpp:331
const LinearizationType & getLinearizationType() const
Get linearization type.
Definition: tpsalinearizer.hpp:303
void resetSystem_(const SubDomainType &domain)
Initializing and/or reset residual and Jacobian.
Definition: tpsalinearizer.hpp:128
void linearizeDomain(const SubDomainType &domain)
Linearize the non-linear system for the spatial domain.
Definition: tpsalinearizer.hpp:189
void setResAndJacobi(VectorBlock &res, MatrixBlock &bMat, const ADVectorBlock &resid) const
Extract local residuals and sub-block Jacobians from locally computed AD residual.
Definition: tpsalinearizer.hpp:223
const GlobalEqVector & residual() const
Get residual vector.
Definition: tpsalinearizer.hpp:284
TpsaLinearizer()
Constructor.
Definition: tpsalinearizer.hpp:84