tpsalinearizer.hpp
Go to the documentation of this file.
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
40
41#include <cassert>
42#include <set>
43#include <vector>
44
45
46namespace Opm {
47
51template<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
77public:
78 // ///
79 // Public functions
80 // ///
85 {
86 simulatorPtr_ = nullptr;
87 }
88
97 void init(Simulator& simulator)
98 {
99 simulatorPtr_ = &simulator;
100 eraseMatrix();
101 }
102
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
143 {
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
332 { linearizationType_ = linearizationType; }
333
334private:
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
Linearizes TPSA equations and generates system matrix and residual for linear solver.
Definition: tpsalinearizer.hpp:53
GlobalEqVector & residual()
Get residual vector.
Definition: tpsalinearizer.hpp:292
const SparseMatrixAdapter & jacobian() const
Get Jacobian matrix.
Definition: tpsalinearizer.hpp:268
const GlobalEqVector & residual() const
Get residual vector.
Definition: tpsalinearizer.hpp:284
void eraseMatrix()
Causes the Jacobian matrix to be recreated from scratch before the next iteration.
Definition: tpsalinearizer.hpp:109
void resetSystem_(const SubDomainType &domain)
Initializing and/or reset residual and Jacobian.
Definition: tpsalinearizer.hpp:128
std::map< unsigned, Constraints > constraintsMap() const
Get constraints map.
Definition: tpsalinearizer.hpp:323
void updateBoundaryConditionData()
Update boundary condition information.
Definition: tpsalinearizer.hpp:242
SparseMatrixAdapter & jacobian()
Get Jacobian matrix.
Definition: tpsalinearizer.hpp:276
TpsaLinearizer()
Constructor.
Definition: tpsalinearizer.hpp:84
void linearizeDomain(const SubDomainType &domain)
Linearize the non-linear system for the spatial domain.
Definition: tpsalinearizer.hpp:189
void linearizeDomain()
Linearize the non-linear system for the spatial domain.
Definition: tpsalinearizer.hpp:154
void finalize()
Finalize creation of Jacobian matrix and make ready for linear solver.
Definition: tpsalinearizer.hpp:117
const auto & getStressInfo() const
Definition: tpsalinearizer.hpp:311
void setLinearizationType(LinearizationType linearizationType)
Set linearization type.
Definition: tpsalinearizer.hpp:331
const LinearizationType & getLinearizationType() const
Get linearization type.
Definition: tpsalinearizer.hpp:303
void linearizeAuxiliaryEquations()
Linearize auxillary equation.
Definition: tpsalinearizer.hpp:213
void init(Simulator &simulator)
Initialize the linearizer.
Definition: tpsalinearizer.hpp:97
void linearize()
Linearize the non-linear system.
Definition: tpsalinearizer.hpp:142
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
Definition: blackoilbioeffectsmodules.hh:45
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
Definition: linearizationtype.hh:34