NonlinearSystemNldd.hpp
Go to the documentation of this file.
1/*
2 Copyright 2013, 2015 SINTEF ICT, Applied Mathematics.
3 Copyright 2014, 2015 Dr. Blatt - HPC-Simulation-Software & Services
4 Copyright 2014, 2015 Statoil ASA.
5 Copyright 2015 NTNU
6 Copyright 2015, 2016, 2017 IRIS AS
7
8 This file is part of the Open Porous Media project (OPM).
9
10 OPM is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 OPM is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with OPM. If not, see <http://www.gnu.org/licenses/>.
22*/
23
24#ifndef OPM_NONLINEAR_SYSTEM_NLDD_HEADER_INCLUDED
25#define OPM_NONLINEAR_SYSTEM_NLDD_HEADER_INCLUDED
26
27#include <dune/common/fvector.hh>
28#include <dune/common/timer.hh>
29#include <dune/istl/bvector.hh>
30#include <dune/istl/istlexception.hh>
31
32#include <opm/common/Exceptions.hpp>
33
34#include <opm/grid/common/SubGridPart.hpp>
35
37
44
46
47#if COMPILE_GPU_BRIDGE
49#else
51#endif
53
57
60
62
63#include <fmt/format.h>
64
65#include <algorithm>
66#include <array>
67#include <cassert>
68#include <cmath>
69#include <cstddef>
70#include <filesystem>
71#include <memory>
72#include <numeric>
73#include <set>
74#include <sstream>
75#include <string>
76#include <type_traits>
77#include <utility>
78#include <vector>
79
80namespace Opm {
81
82template<class TypeTag> class NonlinearSystemBlackOilReservoir;
83
84namespace detail {
85
99template <class Grid, class ElementMapper, class EntitySeed>
100std::vector<int> findOverlapCells(const Grid& grid,
101 const ElementMapper& elementMapper,
102 const std::vector<int>& owned_cells,
103 std::vector<EntitySeed>& seeds,
104 const int num_layers,
105 const int mark,
106 std::vector<int>& in_domain)
107{
108 const auto& gridView = grid.leafGridView();
109 // The owned cells are in the subdomain from the start.
110 for (const int cell : owned_cells) {
111 in_domain[cell] = mark;
112 }
113
114 std::vector<int> overlap;
115 // seeds[layer_begin, layer_end) are the cells added by the previous layer,
116 // initially the owned cells.
117 std::size_t layer_begin = 0;
118 for (int layer = 0; layer < num_layers; ++layer) {
119 // seeds grows inside the loop; the new entries form the next layer.
120 const std::size_t layer_end = seeds.size();
121 for (std::size_t i = layer_begin; i < layer_end; ++i) {
122 const auto element = grid.entity(seeds[i]);
123 for (const auto& intersection : intersections(gridView, element)) {
124 // Skip faces without a neighbour cell (domain boundary).
125 if (!intersection.neighbor()) {
126 continue;
127 }
128 // Skip cells owned by another MPI rank.
129 const auto outside = intersection.outside();
130 if (outside.partitionType() != Dune::InteriorEntity) {
131 continue;
132 }
133 // Skip cells that are already in the subdomain.
134 const int cell = elementMapper.index(outside);
135 if (in_domain[cell] == mark) {
136 continue;
137 }
138 // A new overlap cell: it is also a seed for the next layer.
139 in_domain[cell] = mark;
140 overlap.push_back(cell);
141 seeds.push_back(outside.seed());
142 }
143 }
144 layer_begin = layer_end;
145 }
146 return overlap;
147}
148
149} // namespace detail
150
152template <class TypeTag>
154{
155public:
163
168
169 static constexpr int numEq = Indices::numEq;
170
174 : model_(model)
175 , wellModel_(model.wellModel())
176 , rank_(model_.simulator().vanguard().grid().comm().rank())
177 {
178 // Create partitions.
179 const auto& [partition_vector_initial, num_domains_initial] = this->partitionCells();
180
181 int num_domains = num_domains_initial;
182 std::vector<int> partition_vector = partition_vector_initial;
183
184 // Fix-up for an extreme case: Interior cells who do not have any on-rank
185 // neighbours. Move all such cells into a single domain on this rank,
186 // and mark the domain for skipping. For what it's worth, we've seen this
187 // case occur in practice when testing on field cases.
188 bool isolated_cells = false;
189 for (auto& domainId : partition_vector) {
190 if (domainId < 0) {
191 domainId = num_domains;
192 isolated_cells = true;
193 }
194 }
195 if (isolated_cells) {
196 num_domains++;
197 }
198
199 // Set nldd handler in main well model
200 model.wellModel().setNlddAdapter(&wellModel_);
201
202 // Scan through partitioning to get correct size for each.
203 std::vector<int> sizes(num_domains, 0);
204 for (const auto& p : partition_vector) {
205 ++sizes[p];
206 }
207
208 // Set up correctly sized vectors of entity seeds and of indices for each partition.
209 using EntitySeed = typename Grid::template Codim<0>::EntitySeed;
210 std::vector<std::vector<EntitySeed>> seeds(num_domains);
211 std::vector<std::vector<int>> partitions(num_domains);
212 for (int domain = 0; domain < num_domains; ++domain) {
213 seeds[domain].resize(sizes[domain]);
214 partitions[domain].resize(sizes[domain]);
215 }
216
217 // Iterate through grid once, setting the seeds of all partitions.
218 // Note: owned cells only!
219 const auto& grid = model_.simulator().vanguard().grid();
220
221 std::vector<int> count(num_domains, 0);
222 const auto& gridView = grid.leafGridView();
223 const auto beg = gridView.template begin<0, Dune::Interior_Partition>();
224 const auto end = gridView.template end<0, Dune::Interior_Partition>();
225 int cell = 0;
226 for (auto it = beg; it != end; ++it, ++cell) {
227 const int p = partition_vector[cell];
228 seeds[p][count[p]] = it->seed();
229 partitions[p][count[p]] = cell;
230 ++count[p];
231 }
232 assert(count == sizes);
233
234 // Create the domains.
235 const int num_overlap_layers = model_.param().nldd_num_overlap_layers_;
236 const auto& elementMapper = model_.simulator().model().elementMapper();
237 // Scratch space for detail::findOverlapCells, shared by all domains.
238 std::vector<int> in_domain(num_overlap_layers > 0 ? partition_vector.size() : 0, -1);
239 for (int index = 0; index < num_domains; ++index) {
240 std::vector<bool> interior(partition_vector.size(), false);
241 for (int ix : partitions[index]) {
242 interior[ix] = true;
243 }
244
245 // Mark the last domain for skipping if it contains isolated cells
246 const bool skip = isolated_cells && (index == num_domains - 1);
247
248 std::vector<int> overlap_cells;
249 if (num_overlap_layers > 0 && !skip) {
250 overlap_cells = detail::findOverlapCells(grid, elementMapper, partitions[index],
251 seeds[index], num_overlap_layers,
252 index, in_domain);
253 }
254
255 // The view holds exactly the cells of the local problem, and
256 // reports all of them, overlap cells included, as interior since
257 // all of them are solved for. The one-layer neighbour overlap
258 // that SubGridPart adds by default is not used, so it is left out.
259 Dune::SubGridPart<Grid> view{grid, std::move(seeds[index]), /*overlap=*/false};
260
261 this->domains_.emplace_back(index,
262 std::move(partitions[index]),
263 std::move(overlap_cells),
264 std::move(interior),
265 std::move(view),
266 skip);
267 }
268
269 // Initialize storage for previous mobilities in a single flat vector
270 const auto numCells = grid.size(0);
271 previousMobilities_.resize(numCells * FluidSystem::numActivePhases(), 0.0);
272 for (const auto& domain : domains_) {
273 updateMobilities(domain);
274 }
275
276 // Initialize domain_needs_solving_ to true for all domains
277 domain_needs_solving_.resize(num_domains, true);
278
279 // Set up container for the local system matrices.
280 domain_matrices_.resize(num_domains);
281
282 // Set up container for the local linear solvers.
283 for (int index = 0; index < num_domains; ++index) {
284 // TODO: The ISTLSolver constructor will make
285 // parallel structures appropriate for the full grid
286 // only. This must be addressed before going parallel.
287 const auto& eclState = model_.simulator().vanguard().eclState();
289 loc_param.is_nldd_local_solver_ = true;
290 loc_param.init(eclState.getSimulationConfig().useCPR());
291 // Override solver type with umfpack if small domain.
292 if (domains_[index].cells.size() < 200) {
293 loc_param.linsolver_ = "umfpack";
294 }
295 loc_param.linear_solver_print_json_definition_ = false;
296 const bool force_serial = true;
297 domain_linsolvers_.emplace_back(model_.simulator(), loc_param, force_serial);
298 domain_linsolvers_.back().setDomainIndex(index);
299 }
300
301 assert(int(domains_.size()) == num_domains);
302
303 domain_reports_accumulated_.resize(num_domains);
304
305 // Print domain distribution summary
307 partition_vector,
308 domains_,
309 local_reports_accumulated_,
310 domain_reports_accumulated_,
311 grid,
312 wellModel_.numLocalWellsEnd());
313 }
314
317 {
318 // Setup domain->well mapping.
319 wellModel_.setupDomains(domains_);
320 }
321
323 template <class NonlinearSolverType>
325 NonlinearSolverType& nonlinear_solver)
326 {
327 // ----------- Set up reports and timer -----------
329 // When nldd_num_initial_newton_iter_ == 0, NLDD starts immediately but wells are still
330 // properly initialized via NewtonIterationContext::needsTimestepInit() which
331 // ensures prepareTimeStep() is called during the initial global linearization.
332 // If nldd_num_initial_newton_iter_ > 0, run global Newton iterations first.
333 // TODO: Investigate whether wells and other subsystems are correctly initialized
334 // for the following NLDD iteration.
335 if (model_.simulator().problem().iterationContext().iteration() < model_.param().nldd_num_initial_newton_iter_) {
336 report = model_.nonlinearIterationNewton(timer, nonlinear_solver);
337 return report;
338 }
339
340 model_.initialLinearization(report, model_.param().newton_min_iter_,
341 model_.param().newton_max_iter_, timer);
342
343 if (report.converged) {
344 return report;
345 }
346
347 // Remove the initial linearization entry; the final Newton step will
348 // record its own, keeping one entry per outer iteration.
349 model_.popLastConvergenceReport();
350
351 // ----------- If not converged, do an NLDD iteration -----------
352 Dune::Timer localSolveTimer;
353 Dune::Timer detailTimer;
354 localSolveTimer.start();
355 detailTimer.start();
356 auto& solution = model_.simulator().model().solution(0);
357 auto initial_solution = solution;
358 auto locally_solved = initial_solution;
359
360 // ----------- Decide on an ordering for the domains -----------
361 const auto domain_order = this->getSubdomainOrder();
362 local_reports_accumulated_.success.pre_post_time += detailTimer.stop();
363
364 // ----------- Solve each domain separately -----------
365 DeferredLogger logger;
366 std::vector<SimulatorReportSingle> domain_reports(domains_.size());
367
369 for (const int domain_index : domain_order) {
370 const auto& domain = domains_[domain_index];
371 SimulatorReportSingle local_report;
372 detailTimer.reset();
373 detailTimer.start();
374
375 domain_needs_solving_[domain_index] = checkIfSubdomainNeedsSolving(domain);
376
377 updateMobilities(domain);
378
379 if (domain.skip || !domain_needs_solving_[domain_index]) {
380 local_report.skipped_domains = true;
381 local_report.converged = true;
382 domain_reports[domain.index] = local_report;
383 continue;
384 }
385 switch (model_.param().local_solve_approach_) {
387 solveDomainJacobi(solution, locally_solved, local_report, logger,
388 timer, domain);
389 break;
390 default:
392 solveDomainGaussSeidel(solution, locally_solved, local_report, logger,
393 timer, domain);
394 break;
395 }
396 // This should have updated the global matrix to be
397 // dR_i/du_j evaluated at new local solutions for
398 // i == j, at old solution for i != j.
399 if (!local_report.converged) {
400 // TODO: more proper treatment, including in parallel.
401 logger.debug(fmt::format("Convergence failure in domain {} on rank {}." , domain.index, rank_));
402 }
403 local_report.solver_time += detailTimer.stop();
404 domain_reports[domain.index] = local_report;
405 }
406 OPM_END_PARALLEL_TRY_CATCH("Unexpected exception in local domain solve: ", model_.simulator().vanguard().grid().comm());
407
408 detailTimer.reset();
409 detailTimer.start();
410 // Communicate and log all messages.
411 auto global_logger = gatherDeferredLogger(logger, model_.simulator().vanguard().grid().comm());
412 global_logger.logMessages();
413
414 // Accumulate local solve data.
415 // Putting the counts in a single array to avoid multiple
416 // comm.sum() calls. Keeping the named vars for readability.
417 std::array<int, 5> counts{ 0, 0, 0, static_cast<int>(domain_reports.size()), 0 };
418 int& num_converged = counts[0];
419 int& num_converged_already = counts[1];
420 int& num_local_newtons = counts[2];
421 int& num_domains = counts[3];
422 int& num_skipped = counts[4];
423 {
424 auto step_newtons = 0;
425 const auto dr_size = domain_reports.size();
426 for (auto i = 0*dr_size; i < dr_size; ++i) {
427 // Reset the needsSolving flag for the next iteration
428 domain_needs_solving_[i] = false;
429 const auto& dr = domain_reports[i];
430 if (dr.converged) {
431 ++num_converged;
432 if (dr.total_newton_iterations == 0) {
433 ++num_converged_already;
434 }
435 else {
436 // If we needed to solve the domain, we also solve in next iteration
437 domain_needs_solving_[i] = true;
438 }
439 }
440 if (dr.skipped_domains) {
441 ++num_skipped;
442 }
443 step_newtons += dr.total_newton_iterations;
444 // Accumulate local reports per domain
445 domain_reports_accumulated_[i] += dr;
446 // Accumulate local reports per rank
447 local_reports_accumulated_ += dr;
448 }
449 num_local_newtons = step_newtons;
450 }
451
452 if (model_.param().local_solve_approach_ == DomainSolveApproach::Jacobi) {
453 solution = locally_solved;
454 model_.simulator().model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0);
455 }
456
457#if HAVE_MPI
458 // Communicate solutions:
459 // With multiple processes, this process' overlap (i.e. not
460 // owned) cells' solution values have been modified by local
461 // solves in the owning processes, and remain unchanged
462 // here. We must therefore receive the updated solution on the
463 // overlap cells and update their intensive quantities before
464 // we move on.
465 const auto& comm = model_.simulator().vanguard().grid().comm();
466 if (comm.size() > 1) {
467 const auto* ccomm = model_.simulator().model().newtonMethod().linearSolver().comm();
468
469 // Copy numerical values from primary vars.
470 ccomm->copyOwnerToAll(solution, solution);
471
472 // Copy flags from primary vars, together with whether the cell switched
473 // primary variables in this time step. The local solves update that only
474 // on owned cells, and the global Newton update must treat a cell and its
475 // copies on other processes alike.
476 auto& newton = model_.simulator().model().newtonMethod();
477 const std::size_t num = solution.size();
478 Dune::BlockVector<Dune::FieldVector<std::size_t, 2>> flags(num);
479 for (std::size_t ii = 0; ii < num; ++ii) {
480 flags[ii][0] = PVUtil::pack(solution[ii]);
481 flags[ii][1] = newton.wasSwitched(ii);
482 }
483 ccomm->copyOwnerToAll(flags, flags);
484 for (std::size_t ii = 0; ii < num; ++ii) {
485 PVUtil::unPack(solution[ii], flags[ii][0]);
486 newton.setWasSwitched(ii, flags[ii][1] != 0);
487 }
488
489 // Update intensive quantities for our overlap values.
490 model_.simulator().model().invalidateAndUpdateIntensiveQuantitiesOverlap(/*timeIdx=*/0);
491
492 // Make total counts of domains converged.
493 comm.sum(counts.data(), counts.size());
494 }
495#endif // HAVE_MPI
496
497 const bool is_iorank = this->rank_ == 0;
498 if (is_iorank) {
499 OpmLog::debug(fmt::format(fmt::runtime("Local solves finished. Converged for {}/{} domains. {} domains were skipped. {} domains did no work. {} total local Newton iterations.\n"),
500 num_converged, num_domains, num_skipped, num_converged_already, num_local_newtons));
501 }
502 auto total_local_solve_time = localSolveTimer.stop();
503 report.local_solve_time += total_local_solve_time;
504 local_reports_accumulated_.success.total_time += total_local_solve_time;
505 local_reports_accumulated_.success.pre_post_time += detailTimer.stop();
506
507 // Finish with a global Newton step.
508 auto rep = model_.nonlinearIterationNewton(timer, nonlinear_solver);
509 report += rep;
510 if (rep.converged) {
511 report.converged = true;
512 }
513 return report;
514 }
515
518 {
519 return local_reports_accumulated_;
520 }
521
523 const std::vector<SimulatorReport>& domainAccumulatedReports() const
524 {
525 // Update the number of wells for each domain that has been added to the well model at this point
526 // this is a mutable operation that updates the well counts in the domain reports.
527 const auto dr_size = domain_reports_accumulated_.size();
528 // Reset well counts before updating
529 for (auto i = 0*dr_size; i < dr_size; ++i) {
530 domain_reports_accumulated_[i].success.num_wells = 0;
531 }
532 // Update the number of wells for each domain
533 for (const auto& [wname, domain] : wellModel_.well_domain()) {
534 domain_reports_accumulated_[domain].success.num_wells++;
535 }
536 return domain_reports_accumulated_;
537 }
538
541 void writePartitions(const std::filesystem::path& odir) const
542 {
543 const auto& grid = this->model_.simulator().vanguard().grid();
544 const auto& elementMapper = this->model_.simulator().model().elementMapper();
545 const auto& cartMapper = this->model_.simulator().vanguard().cartesianIndexMapper();
546
548 odir,
549 domains_,
550 grid,
551 elementMapper,
552 cartMapper);
553 }
554
556 void writeNonlinearIterationsPerCell(const std::filesystem::path& odir) const
557 {
558 const auto& grid = this->model_.simulator().vanguard().grid();
559 const auto& elementMapper = this->model_.simulator().model().elementMapper();
560 const auto& cartMapper = this->model_.simulator().vanguard().cartesianIndexMapper();
561
563 odir,
564 domains_,
565 domain_reports_accumulated_,
566 grid,
567 elementMapper,
568 cartMapper);
569 }
570
571private:
574 solveDomain(const Domain& domain,
575 const SimulatorTimerInterface& timer,
576 SimulatorReportSingle& local_report,
577 DeferredLogger& logger,
578 const bool initial_assembly_required)
579 {
580 auto& modelSimulator = model_.simulator();
581 Dune::Timer detailTimer;
582
583 // RAII guard: local iteration context for this domain, restored on scope exit.
584 LocalContextGuard localCtxGuard(modelSimulator.problem());
585 auto& localCtx = localCtxGuard.context();
586
587 // When called, if assembly has already been performed
588 // with the initial values, we only need to check
589 // for local convergence. Otherwise, we must do a local
590 // assembly.
591 if (initial_assembly_required) {
592 detailTimer.start();
593 // TODO: we should have a beginIterationLocal function()
594 // only handling the well model for now
595 wellModel_.assemble(modelSimulator.timeStepSize(),
596 domain);
597 const double tt0 = detailTimer.stop();
598 local_report.assemble_time += tt0;
599 local_report.assemble_time_well += tt0;
600 detailTimer.reset();
601 detailTimer.start();
602 // Assemble reservoir locally.
603 this->assembleReservoirDomain(domain);
604 local_report.assemble_time += detailTimer.stop();
605 local_report.total_linearizations += 1;
606 }
607 detailTimer.reset();
608 detailTimer.start();
609 std::vector<Scalar> resnorms;
610 auto convreport = this->getDomainConvergence(domain, timer, logger, resnorms);
611 local_report.update_time += detailTimer.stop();
612 if (convreport.converged()) {
613 // TODO: set more info, timing etc.
614 local_report.converged = true;
615 return convreport;
616 }
617
618 // We have already assembled for the first iteration,
619 // but not done the Schur complement for the wells yet.
620 detailTimer.reset();
621 detailTimer.start();
622 model_.wellModel().linearizeDomain(domain,
623 modelSimulator.model().linearizer().jacobian(),
624 modelSimulator.model().linearizer().residual());
625 const double tt1 = detailTimer.stop();
626 local_report.assemble_time += tt1;
627 local_report.assemble_time_well += tt1;
628
629 // Local Newton loop.
630 const int max_iter = model_.param().max_local_solve_iterations_;
631 const auto& grid = modelSimulator.vanguard().grid();
632 double damping_factor = 1.0;
633 std::vector<std::vector<Scalar>> convergence_history;
634 convergence_history.reserve(20);
635 convergence_history.push_back(resnorms);
636 do {
637 // Solve local linear system.
638 // Note that x has full size, we expect it to be nonzero only for in-domain cells.
639 const int nc = grid.size(0);
640 BVector x(nc);
641 detailTimer.reset();
642 detailTimer.start();
643 double setup_time = 0.0;
644 try {
645 this->solveJacobianSystemDomain(domain, x, setup_time);
646 }
647 catch (const NumericalProblem& e) {
648 // Local linear solve failed - treat as domain-level failure, not global timestep cut.
649 logger.debug(fmt::format(
650 "Local linear solver failed in domain {} on rank {}: {}",
651 domain.index, rank_, e.what()));
652 // record statistics and return early
653 local_report.linear_solve_time += detailTimer.stop();
654 local_report.linear_solve_setup_time += setup_time;
655 local_report.total_linear_iterations = domain_linsolvers_[domain.index].iterations();
656 modelSimulator.problem().endIteration();
657 local_report.converged = false;
658 local_report.total_newton_iterations = localCtx.iteration();
659 local_report.total_linearizations += localCtx.iteration();
660 return convreport;
661 }
662 model_.wellModel().postSolveDomain(x, domain);
663 if (damping_factor != 1.0) {
664 x *= damping_factor;
665 }
666 local_report.linear_solve_time += detailTimer.stop();
667 local_report.linear_solve_setup_time += setup_time;
668 local_report.total_linear_iterations = domain_linsolvers_[domain.index].iterations();
669
670 // Update local solution. // TODO: x is still full size, should we optimize it?
671 detailTimer.reset();
672 detailTimer.start();
673 this->updateDomainSolution(domain, x);
674 local_report.update_time += detailTimer.stop();
675
676 // Assemble well and reservoir.
677 detailTimer.reset();
678 detailTimer.start();
679 localCtx.advanceIteration();
680 // TODO: we should have a beginIterationLocal function()
681 // only handling the well model for now
682 // Assemble reservoir locally.
683 wellModel_.assemble(modelSimulator.timeStepSize(),
684 domain);
685 const double tt3 = detailTimer.stop();
686 local_report.assemble_time += tt3;
687 local_report.assemble_time_well += tt3;
688 detailTimer.reset();
689 detailTimer.start();
690 this->assembleReservoirDomain(domain);
691 local_report.assemble_time += detailTimer.stop();
692
693 // Check for local convergence.
694 detailTimer.reset();
695 detailTimer.start();
696 resnorms.clear();
697 convreport = this->getDomainConvergence(domain, timer, logger, resnorms);
698 convergence_history.push_back(resnorms);
699 local_report.update_time += detailTimer.stop();
700
701 // apply the Schur complement of the well model to the
702 // reservoir linearized equations
703 detailTimer.reset();
704 detailTimer.start();
705 model_.wellModel().linearizeDomain(domain,
706 modelSimulator.model().linearizer().jacobian(),
707 modelSimulator.model().linearizer().residual());
708 const double tt2 = detailTimer.stop();
709 local_report.assemble_time += tt2;
710 local_report.assemble_time_well += tt2;
711
712 // Check if we should dampen, also when the wells are not
713 // converged. Waiting for the wells would tie the start of the
714 // damping to the iteration where the well tolerances are relaxed.
715 if (!convreport.converged()) {
716 bool oscillate = false;
717 bool stagnate = false;
718 const auto num_residuals = convergence_history.front().size();
719 detail::detectOscillations(convergence_history, localCtx.iteration(), num_residuals,
720 Scalar{0.2}, 1, oscillate, stagnate);
721 if (oscillate) {
722 damping_factor *= 0.85;
723 logger.debug(fmt::format(fmt::runtime("| Damping factor is now {}"), damping_factor));
724 }
725 }
726 } while (!convreport.converged() && localCtx.iteration() <= max_iter);
727
728 modelSimulator.problem().endIteration();
729
730 local_report.converged = convreport.converged();
731 local_report.total_newton_iterations = localCtx.iteration();
732 local_report.total_linearizations += localCtx.iteration();
733 // TODO: set more info, timing etc.
734 return convreport;
735 }
736
738 void assembleReservoirDomain(const Domain& domain)
739 {
740 OPM_TIMEBLOCK(assembleReservoirDomain);
741 // -------- Mass balance equations --------
742 model_.simulator().model().linearizer().linearizeDomain(domain);
743 }
744
746 void solveJacobianSystemDomain(const Domain& domain, BVector& global_x, double& setup_time)
747 {
748 const auto& modelSimulator = model_.simulator();
749
750 Dune::Timer perfTimer;
751 perfTimer.start();
752
753 const Mat& main_matrix = modelSimulator.model().linearizer().jacobian().istlMatrix();
754 if (domain_matrices_[domain.index]) {
755 Details::copySubMatrix(main_matrix, domain.cells, *domain_matrices_[domain.index]);
756 } else {
757 domain_matrices_[domain.index] = std::make_unique<Mat>(Details::extractMatrix(main_matrix, domain.cells));
758 }
759 auto& jac = *domain_matrices_[domain.index];
760 auto res = Details::extractVector(modelSimulator.model().linearizer().residual(),
761 domain.cells);
762 auto x = res;
763
764 // set initial guess
765 global_x = 0.0;
766 x = 0.0;
767
768 auto& linsolver = domain_linsolvers_[domain.index];
769
770 linsolver.prepare(jac, res);
771 setup_time = perfTimer.stop();
772 linsolver.setResidual(res);
773 linsolver.solve(x);
774
775 Details::setGlobal(x, domain.cells, global_x);
776 }
777
779 void updateDomainSolution(const Domain& domain, const BVector& dx)
780 {
781 OPM_TIMEBLOCK(updateDomainSolution);
782 auto& simulator = model_.simulator();
783 auto& newtonMethod = simulator.model().newtonMethod();
784 SolutionVector& solution = simulator.model().solution(/*timeIdx=*/0);
785
786 newtonMethod.update_(/*nextSolution=*/solution,
787 /*curSolution=*/solution,
788 /*update=*/dx,
789 /*resid=*/dx,
790 domain.cells); // the update routines of the black
791 // oil model do not care about the
792 // residual
793
794 // if the solution is updated, the intensive quantities need to be recalculated
795 simulator.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0, domain);
796 }
797
799 std::pair<Scalar, Scalar> localDomainConvergenceData(const Domain& domain,
800 std::vector<Scalar>& R_sum,
801 std::vector<Scalar>& maxCoeff,
802 std::vector<Scalar>& B_avg,
803 std::vector<int>& maxCoeffCell)
804 {
805 const auto& modelSimulator = model_.simulator();
806
807 Scalar pvSumLocal = 0.0;
808 Scalar numAquiferPvSumLocal = 0.0;
809 const auto& model = modelSimulator.model();
810 const auto& problem = modelSimulator.problem();
811
812 const auto& modelResid = modelSimulator.model().linearizer().residual();
813
814 ElementContext elemCtx(modelSimulator);
815 const auto& gridView = domain.view;
816 const auto& elemEndIt = gridView.template end</*codim=*/0>();
817 IsNumericalAquiferCell isNumericalAquiferCell(gridView.grid());
818 const auto& elementMapper = model.elementMapper();
819
820 for (auto elemIt = gridView.template begin</*codim=*/0>();
821 elemIt != elemEndIt;
822 ++elemIt)
823 {
824 if (elemIt->partitionType() != Dune::InteriorEntity) {
825 continue;
826 }
827 const auto& elem = *elemIt;
828 // Convergence is measured on the owned cells only. Overlap cells
829 // are owned, and checked, by another subdomain.
830 if (domain.hasOverlap() && !domain.interior[elementMapper.index(elem)]) {
831 continue;
832 }
833 elemCtx.updatePrimaryStencil(elem);
834 elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
835
836 const unsigned cell_idx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
837 const auto& intQuants = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0);
838 const auto& fs = intQuants.fluidState();
839
840 const auto pvValue = problem.referencePorosity(cell_idx, /*timeIdx=*/0) *
841 model.dofTotalVolume(cell_idx);
842 pvSumLocal += pvValue;
843
844 if (isNumericalAquiferCell(elem))
845 {
846 numAquiferPvSumLocal += pvValue;
847 }
848
849 model_.getMaxCoeff(cell_idx, intQuants, fs, modelResid, pvValue,
850 B_avg, R_sum, maxCoeff, maxCoeffCell);
851 }
852
853 // compute local average in terms of global number of elements
854 const int bSize = B_avg.size();
855 for ( int i = 0; i<bSize; ++i )
856 {
857 B_avg[ i ] /= Scalar(domain.interior_cells.size());
858 }
859
860 return {pvSumLocal, numAquiferPvSumLocal};
861 }
862
863 ConvergenceReport getDomainReservoirConvergence(const double reportTime,
864 const double dt,
865 const Domain& domain,
866 DeferredLogger& logger,
867 std::vector<Scalar>& B_avg,
868 std::vector<Scalar>& residual_norms)
869 {
870 using Vector = std::vector<Scalar>;
871
872 const auto& iterCtx = model_.simulator().problem().iterationContext();
873
874 const int numComp = numEq;
875 Vector R_sum(numComp, 0.0 );
876 Vector maxCoeff(numComp, std::numeric_limits<Scalar>::lowest() );
877 std::vector<int> maxCoeffCell(numComp, -1);
878 const auto [ pvSum, numAquiferPvSum]
879 = this->localDomainConvergenceData(domain, R_sum, maxCoeff, B_avg, maxCoeffCell);
880
881 auto cnvErrorPvFraction = computeCnvErrorPvLocal(domain, B_avg, dt);
882 cnvErrorPvFraction /= (pvSum - numAquiferPvSum);
883
884 // For each iteration, we need to determine whether to use the relaxed tolerances.
885 // To disable the usage of relaxed tolerances, you can set the relaxed tolerances
886 // equal to the strict tolerances.
887 //
888 // If min_strict_mb_iter_ = -1 (default) we use a relaxed tolerance for the mass
889 // balance only on the last local iteration. For non-negative values we use the
890 // relaxed tolerance after the given number of iterations.
891 const int maxLocalIter = model_.param().max_local_solve_iterations_;
892
893 const bool relax_final_iteration_mb =
894 model_.param().min_strict_mb_iter_ < 0 && iterCtx.iteration() == maxLocalIter;
895
896 const bool relax_iter_mb =
897 model_.param().min_strict_mb_iter_ >= 0 &&
898 iterCtx.shouldRelax(model_.param().min_strict_mb_iter_);
899
900 const bool use_relaxed_mb = relax_final_iteration_mb || relax_iter_mb;
901
902 const Scalar tol_mb = model_.param().local_tolerance_scaling_mb_
903 * (use_relaxed_mb ? model_.param().tolerance_mb_relaxed_ : model_.param().tolerance_mb_);
904
905 // If min_strict_cnv_iter_ = -1 (default) we use a relaxed tolerance for the CNV
906 // only on the last local iteration. For non-negative values we use the relaxed
907 // tolerance after the given number of iterations. We also use relaxed tolerances
908 // for cells with total pore-volume less than relaxed_max_pv_fraction_.
909 const bool relax_final_iteration_cnv =
910 model_.param().min_strict_cnv_iter_ < 0 && iterCtx.iteration() == maxLocalIter;
911
912 const bool relax_iter_cnv =
913 model_.param().min_strict_cnv_iter_ >= 0 &&
914 iterCtx.shouldRelax(model_.param().min_strict_cnv_iter_);
915
916 const bool relax_pv_fraction_cnv =
917 cnvErrorPvFraction < model_.param().relaxed_max_pv_fraction_;
918
919 const bool use_relaxed_cnv = relax_final_iteration_cnv ||
920 relax_iter_cnv ||
921 relax_pv_fraction_cnv;
922
923 // Tighter bound for local convergence should increase the
924 // likelyhood of: local convergence => global convergence
925 const Scalar tol_cnv = model_.param().local_tolerance_scaling_cnv_
926 * (use_relaxed_cnv ? model_.param().tolerance_cnv_relaxed_
927 : model_.param().tolerance_cnv_);
928
929 // TODO: Energy-specific tolerances are not currently applied. A closer analysis
930 // is needed to determine if everything in NLDD is compatible with thermal simulations.
931
932 // Finish computation
933 std::vector<Scalar> CNV(numComp);
934 std::vector<Scalar> mass_balance_residual(numComp);
935 for (int compIdx = 0; compIdx < numComp; ++compIdx )
936 {
937 CNV[compIdx] = B_avg[compIdx] * dt * maxCoeff[compIdx];
938 mass_balance_residual[compIdx] = std::abs(B_avg[compIdx]*R_sum[compIdx]) * dt / pvSum;
939 residual_norms.push_back(CNV[compIdx]);
940 }
941
942 // Create convergence report.
943 ConvergenceReport report{reportTime};
944 using CR = ConvergenceReport;
945 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
946 Scalar res[2] = { mass_balance_residual[compIdx], CNV[compIdx] };
947 CR::ReservoirFailure::Type types[2] = { CR::ReservoirFailure::Type::MassBalance,
948 CR::ReservoirFailure::Type::Cnv };
949 Scalar tol[2] = { tol_mb, tol_cnv };
950 model_.addReservoirConvergenceMetrics(
951 report,
952 compIdx,
953 model_.compNames().name(compIdx),
954 std::span<const Scalar>{res},
955 std::span<const CR::ReservoirFailure::Type>{types},
956 std::span<const Scalar>{tol},
957 model_.param().max_residual_allowed_,
958 [&logger](const std::string& message)
959 {
960 logger.debug(message);
961 });
962 }
963
964 // Output of residuals. If converged at initial state, log nothing.
965 const bool converged_at_initial_state = (report.converged() && iterCtx.iteration() == 0);
966 if (!converged_at_initial_state) {
967 if (iterCtx.iteration() == 0) {
968 // Log header.
969 std::string msg = fmt::format("Domain {} on rank {}, size {} (overlap {}), containing cell {}\n| Iter",
970 domain.index, this->rank_, domain.interior_cells.size(),
971 domain.cells.size() - domain.interior_cells.size(),
972 domain.interior_cells[0]);
973 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
974 msg += " MB(";
975 msg += model_.compNames().name(compIdx)[0];
976 msg += ") ";
977 }
978 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
979 msg += " CNV(";
980 msg += model_.compNames().name(compIdx)[0];
981 msg += ") ";
982 }
983 logger.debug(msg);
984 }
985 // Log convergence data.
986 std::ostringstream ss;
987 ss << "| ";
988 const std::streamsize oprec = ss.precision(3);
989 const std::ios::fmtflags oflags = ss.setf(std::ios::scientific);
990 ss << std::setw(4) << iterCtx.iteration();
991 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
992 ss << std::setw(11) << mass_balance_residual[compIdx];
993 }
994 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
995 ss << std::setw(11) << CNV[compIdx];
996 }
997 ss.precision(oprec);
998 ss.flags(oflags);
999 logger.debug(ss.str());
1000 }
1001
1002 return report;
1003 }
1004
1005 ConvergenceReport getDomainConvergence(const Domain& domain,
1006 const SimulatorTimerInterface& timer,
1007 DeferredLogger& logger,
1008 std::vector<Scalar>& residual_norms)
1009 {
1010 OPM_TIMEBLOCK(getDomainConvergence);
1011 std::vector<Scalar> B_avg(numEq, 0.0);
1012 auto report = this->getDomainReservoirConvergence(timer.simulationTimeElapsed(),
1013 timer.currentStepLength(),
1014 domain,
1015 logger,
1016 B_avg,
1017 residual_norms);
1018 report += wellModel_.getWellConvergence(domain, B_avg, logger);
1019 return report;
1020 }
1021
1023 std::vector<int> getSubdomainOrder()
1024 {
1025 const auto& modelSimulator = model_.simulator();
1026 const auto& solution = modelSimulator.model().solution(0);
1027
1028 std::vector<int> domain_order(domains_.size());
1029 std::iota(domain_order.begin(), domain_order.end(), 0);
1030
1031 if (model_.param().local_solve_approach_ == DomainSolveApproach::Jacobi) {
1032 // Do nothing, 0..n-1 order is fine.
1033 return domain_order;
1034 } else if (model_.param().local_solve_approach_ == DomainSolveApproach::GaussSeidel) {
1035 // Calculate the measure used to order the domains.
1036 std::vector<Scalar> measure_per_domain(domains_.size());
1037 switch (model_.param().local_domains_ordering_) {
1039 // Use average pressures to order domains.
1040 for (const auto& domain : domains_) {
1041 const Scalar press_sum =
1042 std::accumulate(domain.interior_cells.begin(), domain.interior_cells.end(), Scalar{0},
1043 [&solution](const auto acc, const auto c)
1044 { return acc + solution[c][Indices::pressureSwitchIdx]; });
1045 const Scalar avgpress = press_sum / domain.interior_cells.size();
1046 measure_per_domain[domain.index] = avgpress;
1047 }
1048 break;
1049 }
1051 // Use max pressures to order domains.
1052 for (const auto& domain : domains_) {
1053 measure_per_domain[domain.index] =
1054 std::accumulate(domain.interior_cells.begin(), domain.interior_cells.end(), Scalar{0},
1055 [&solution](const auto acc, const auto c)
1056 { return std::max(acc, solution[c][Indices::pressureSwitchIdx]); });
1057 }
1058 break;
1059 }
1061 // Use maximum residual to order domains.
1062 const auto& residual = modelSimulator.model().linearizer().residual();
1063 const int num_vars = residual[0].size();
1064 for (const auto& domain : domains_) {
1065 Scalar maxres = 0.0;
1066 for (const int c : domain.interior_cells) {
1067 for (int ii = 0; ii < num_vars; ++ii) {
1068 maxres = std::max(maxres, std::fabs(residual[c][ii]));
1069 }
1070 }
1071 measure_per_domain[domain.index] = maxres;
1072 }
1073 break;
1074 }
1075 } // end of switch (model_.param().local_domains_ordering_)
1076
1077 // Sort by largest measure, keeping index order if equal.
1078 const auto& m = measure_per_domain;
1079 std::stable_sort(domain_order.begin(), domain_order.end(),
1080 [&m](const int i1, const int i2){ return m[i1] > m[i2]; });
1081 return domain_order;
1082 } else {
1083 throw std::logic_error("Domain solve approach must be Jacobi or Gauss-Seidel");
1084 }
1085 }
1086
1087 template<class GlobalEqVector>
1088 void solveDomainJacobi(GlobalEqVector& solution,
1089 GlobalEqVector& locally_solved,
1090 SimulatorReportSingle& local_report,
1091 DeferredLogger& logger,
1092 const SimulatorTimerInterface& timer,
1093 const Domain& domain)
1094 {
1095 auto initial_local_well_primary_vars = wellModel_.getPrimaryVarsDomain(domain.index);
1096 auto initial_local_solution = Details::extractVector(solution, domain.cells);
1097 auto convrep = solveDomain(domain, timer, local_report, logger, false);
1098 if (local_report.converged) {
1099 // Each cell takes its value from the subdomain that owns it.
1100 auto local_solution = Details::extractVector(solution, domain.interior_cells);
1101 Details::setGlobal(local_solution, domain.interior_cells, locally_solved);
1102 Details::setGlobal(initial_local_solution, domain.cells, solution);
1103 model_.simulator().model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0, domain);
1104 } else {
1105 wellModel_.setPrimaryVarsDomain(domain.index, initial_local_well_primary_vars);
1106 Details::setGlobal(initial_local_solution, domain.cells, solution);
1107 model_.simulator().model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0, domain);
1108 }
1109 }
1110
1111 template<class GlobalEqVector>
1112 void solveDomainGaussSeidel(GlobalEqVector& solution,
1113 GlobalEqVector& locally_solved,
1114 SimulatorReportSingle& local_report,
1115 DeferredLogger& logger,
1116 const SimulatorTimerInterface& timer,
1117 const Domain& domain)
1118 {
1119 auto initial_local_well_primary_vars = wellModel_.getPrimaryVarsDomain(domain.index);
1120 auto initial_local_solution = Details::extractVector(solution, domain.cells);
1121 auto convrep = solveDomain(domain, timer, local_report, logger, true);
1122 if (!local_report.converged) {
1123 // We look at the detailed convergence report to evaluate
1124 // if we should accept the unconverged solution.
1125 // We do not accept a solution if the wells are unconverged.
1126 if (!convrep.wellFailed()) {
1127 // Calculare the sums of the mb and cnv failures.
1128 Scalar mb_sum = 0.0;
1129 Scalar cnv_sum = 0.0;
1130 for (const auto& rc : convrep.reservoirConvergence()) {
1132 mb_sum += rc.value();
1133 } else if (rc.type() == ConvergenceReport::ReservoirFailure::Type::Cnv) {
1134 cnv_sum += rc.value();
1135 }
1136 }
1137 // If not too high, we overrule the convergence failure.
1138 const Scalar acceptable_local_mb_sum = 1e-3;
1139 const Scalar acceptable_local_cnv_sum = 1.0;
1140 if (mb_sum < acceptable_local_mb_sum && cnv_sum < acceptable_local_cnv_sum) {
1141 local_report.converged = true;
1142 local_report.accepted_unconverged_domains += 1;
1143 logger.debug(fmt::format("Accepting solution in unconverged domain {} on rank {}.", domain.index, rank_));
1144 logger.debug(fmt::format("Value of mb_sum: {} cnv_sum: {}", mb_sum, cnv_sum));
1145 } else {
1146 logger.debug("Unconverged local solution.");
1147 }
1148 } else {
1149 logger.debug("Unconverged local solution with well convergence failures:");
1150 for (const auto& wf : convrep.wellFailures()) {
1151 logger.debug(to_string(wf));
1152 }
1153 }
1154 }
1155 if (local_report.converged) {
1156 local_report.converged_domains += 1;
1157 auto local_solution = Details::extractVector(solution, domain.interior_cells);
1158 Details::setGlobal(local_solution, domain.interior_cells, locally_solved);
1159 // The updated overlap values stay in the global solution vector,
1160 // so subdomains solved later in the sweep start from them. An
1161 // overlap cell ends the sweep with the value from the last
1162 // subdomain that solved for it, which need not be its owner.
1163 } else {
1164 local_report.unconverged_domains += 1;
1165 wellModel_.setPrimaryVarsDomain(domain.index, initial_local_well_primary_vars);
1166 Details::setGlobal(initial_local_solution, domain.cells, solution);
1167 model_.simulator().model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0, domain);
1168 }
1169 }
1170
1171 Scalar computeCnvErrorPvLocal(const Domain& domain,
1172 const std::vector<Scalar>& B_avg, double dt) const
1173 {
1174 Scalar errorPV{};
1175 const auto& simulator = model_.simulator();
1176 const auto& model = simulator.model();
1177 const auto& problem = simulator.problem();
1178 const auto& residual = simulator.model().linearizer().residual();
1179
1180 for (const int cell_idx : domain.interior_cells) {
1181 const Scalar pvValue = problem.referencePorosity(cell_idx, /*timeIdx=*/0) *
1182 model.dofTotalVolume(cell_idx);
1183 const auto& cellResidual = residual[cell_idx];
1184 bool cnvViolated = false;
1185
1186 for (unsigned eqIdx = 0; eqIdx < cellResidual.size(); ++eqIdx) {
1187 using std::fabs;
1188 Scalar CNV = cellResidual[eqIdx] * dt * B_avg[eqIdx] / pvValue;
1189 cnvViolated = cnvViolated || (fabs(CNV) > model_.param().tolerance_cnv_);
1190 }
1191
1192 if (cnvViolated) {
1193 errorPV += pvValue;
1194 }
1195 }
1196 return errorPV;
1197 }
1198
1199 decltype(auto) partitionCells() const
1200 {
1201 const auto& grid = this->model_.simulator().vanguard().grid();
1202
1203 using GridView = std::remove_cv_t<std::remove_reference_t<
1204 decltype(grid.leafGridView())>>;
1205
1206 using Element = std::remove_cv_t<std::remove_reference_t<
1207 typename GridView::template Codim<0>::Entity>>;
1208
1209 const auto& param = this->model_.param();
1210
1211 auto zoltan_ctrl = ZoltanPartitioningControl<Element>{};
1212
1213 zoltan_ctrl.domain_imbalance = param.local_domains_partition_imbalance_;
1214
1215 zoltan_ctrl.index =
1216 [elementMapper = &this->model_.simulator().model().elementMapper()]
1217 (const Element& element)
1218 {
1219 return elementMapper->index(element);
1220 };
1221
1222 zoltan_ctrl.local_to_global =
1223 [cartMapper = &this->model_.simulator().vanguard().cartesianIndexMapper()]
1224 (const int elemIdx)
1225 {
1226 return cartMapper->cartesianIndex(elemIdx);
1227 };
1228
1229 // Forming the list of wells is expensive, so do this only if needed.
1230 const auto need_wells = param.local_domains_partition_method_ == "zoltan";
1231
1232 const auto wells = need_wells
1233 ? this->model_.simulator().vanguard().schedule().getWellsatEnd()
1234 : std::vector<Well>{};
1235
1236 const auto& possibleFutureConnectionSet = need_wells
1237 ? this->model_.simulator().vanguard().schedule().getPossibleFutureConnections()
1238 : std::unordered_map<std::string, std::set<int>> {};
1239
1240 // If defaulted parameter for number of domains, choose a reasonable default.
1241 constexpr int default_cells_per_domain = 1000;
1242 const int num_domains = (param.num_local_domains_ > 0)
1243 ? param.num_local_domains_
1244 : detail::countGlobalCells(grid) / default_cells_per_domain;
1245 return ::Opm::partitionCells(param.local_domains_partition_method_,
1246 num_domains, grid.leafGridView(), wells,
1247 possibleFutureConnectionSet, zoltan_ctrl,
1248 param.local_domains_partition_well_neighbor_levels_);
1249 }
1250
1251 void updateMobilities(const Domain& domain)
1252 {
1253 if (domain.skip || model_.param().nldd_relative_mobility_change_tol_ == 0.0) {
1254 return;
1255 }
1256 const auto numActivePhases = FluidSystem::numActivePhases();
1257 for (const auto globalDofIdx : domain.interior_cells) {
1258 const auto& intQuants = model_.simulator().model().intensiveQuantities(globalDofIdx, /* time_idx = */ 0);
1259
1260 for (unsigned activePhaseIdx = 0; activePhaseIdx < numActivePhases; ++activePhaseIdx) {
1261 const auto phaseIdx = FluidSystem::activeToCanonicalPhaseIdx(activePhaseIdx);
1262 const auto mobIdx = globalDofIdx * numActivePhases + activePhaseIdx;
1263 previousMobilities_[mobIdx] = getValue(intQuants.mobility(phaseIdx));
1264 }
1265 }
1266 }
1267
1268 bool checkIfSubdomainNeedsSolving(const Domain& domain)
1269 {
1270 if (domain.skip) {
1271 return false;
1272 }
1273
1274 // If we domain was marked as needing solving in previous iterations,
1275 // we do not need to check again
1276 if (domain_needs_solving_[domain.index]) {
1277 return true;
1278 }
1279
1280 // If we do not check for mobility changes, we need to solve the domain
1281 if (model_.param().nldd_relative_mobility_change_tol_ == 0.0) {
1282 return true;
1283 }
1284
1285 // Skip mobility check on first global iteration (no previous mobilities to compare)
1286 if (model_.simulator().problem().iterationContext().isFirstGlobalIteration()) {
1287 return true;
1288 }
1289
1290 return checkSubdomainChangeRelative(domain);
1291 }
1292
1293 bool checkSubdomainChangeRelative(const Domain& domain)
1294 {
1295 const auto numActivePhases = FluidSystem::numActivePhases();
1296
1297 // Check mobility changes for the owned cells. Overlap cells change
1298 // whenever a neighbouring subdomain is solved, which on its own is no
1299 // reason to solve this subdomain again.
1300 for (const auto globalDofIdx : domain.interior_cells) {
1301 const auto& intQuants = model_.simulator().model().intensiveQuantities(globalDofIdx, /* time_idx = */ 0);
1302
1303 // Calculate average previous mobility for normalization
1304 Scalar cellMob = 0.0;
1305 for (unsigned activePhaseIdx = 0; activePhaseIdx < numActivePhases; ++activePhaseIdx) {
1306 const auto mobIdx = globalDofIdx * numActivePhases + activePhaseIdx;
1307 cellMob += previousMobilities_[mobIdx] / numActivePhases;
1308 }
1309
1310 // Check relative changes for each phase
1311 for (unsigned activePhaseIdx = 0; activePhaseIdx < numActivePhases; ++activePhaseIdx) {
1312 const auto phaseIdx = FluidSystem::activeToCanonicalPhaseIdx(activePhaseIdx);
1313 const auto mobIdx = globalDofIdx * numActivePhases + activePhaseIdx;
1314 const auto mobility = getValue(intQuants.mobility(phaseIdx));
1315 const auto relDiff = std::abs(mobility - previousMobilities_[mobIdx]) / cellMob;
1316 if (relDiff > model_.param().nldd_relative_mobility_change_tol_) {
1317 return true;
1318 }
1319 }
1320 }
1321 return false;
1322 }
1323
1324 NonlinearSystemBlackOilReservoir<TypeTag>& model_;
1325 BlackoilWellModelNldd<TypeTag> wellModel_;
1326 std::vector<Domain> domains_;
1327 std::vector<std::unique_ptr<Mat>> domain_matrices_;
1328 std::vector<ISTLSolverType> domain_linsolvers_;
1329 SimulatorReport local_reports_accumulated_;
1330 // mutable because we need to update the number of wells for each domain in getDomainAccumulatedReports()
1331 mutable std::vector<SimulatorReport> domain_reports_accumulated_;
1332 int rank_ = 0;
1333 // Store previous mobilities to check for changes - single flat vector indexed by (globalCellIdx * numActivePhases + activePhaseIdx)
1334 std::vector<Scalar> previousMobilities_;
1335 // Flag indicating if this domain should be solved in the next iteration
1336 std::vector<bool> domain_needs_solving_;
1337};
1338
1339} // namespace Opm
1340
1341#endif // OPM_NONLINEAR_SYSTEM_NLDD_HEADER_INCLUDED
#define OPM_END_PARALLEL_TRY_CATCH(prefix, comm)
Catch exception and throw in a parallel try-catch clause.
Definition: DeferredLoggingErrorHelpers.hpp:197
#define OPM_BEGIN_PARALLEL_TRY_CATCH()
Macro to setup the try of a parallel try-catch.
Definition: DeferredLoggingErrorHelpers.hpp:160
Definition: ConvergenceReport.hpp:38
Definition: DeferredLogger.hpp:57
void debug(const std::string &tag, const std::string &message)
Definition: ISTLSolver.hpp:152
Definition: NewtonIterationContext.hpp:152
Definition: NonlinearSystemBlackOilReservoir.hpp:63
typename SparseMatrixAdapter::IstlMatrix Mat
Definition: NonlinearSystemBlackOilReservoir.hpp:112
Dune::BlockVector< VectorBlockType > BVector
Definition: NonlinearSystemBlackOilReservoir.hpp:113
A NLDD implementation used by the reservoir nonlinear system.
Definition: NonlinearSystemNldd.hpp:154
void writePartitions(const std::filesystem::path &odir) const
Definition: NonlinearSystemNldd.hpp:541
NonlinearSystemNldd(NonlinearSystemBlackOilReservoir< TypeTag > &model)
The constructor sets up the subdomains.
Definition: NonlinearSystemNldd.hpp:173
GetPropType< TypeTag, Properties::ElementContext > ElementContext
Definition: NonlinearSystemNldd.hpp:156
typename NonlinearSystemBlackOilReservoir< TypeTag >::ModelParameters ModelParameters
Definition: NonlinearSystemNldd.hpp:161
const std::vector< SimulatorReport > & domainAccumulatedReports() const
return the statistics of local solves accumulated for each domain on this rank
Definition: NonlinearSystemNldd.hpp:523
GetPropType< TypeTag, Properties::Scalar > Scalar
Definition: NonlinearSystemNldd.hpp:160
typename NonlinearSystemBlackOilReservoir< TypeTag >::BVector BVector
Definition: NonlinearSystemNldd.hpp:164
GetPropType< TypeTag, Properties::Grid > Grid
Definition: NonlinearSystemNldd.hpp:158
SimulatorReportSingle nonlinearIterationNldd(const SimulatorTimerInterface &timer, NonlinearSolverType &nonlinear_solver)
Do one non-linear NLDD iteration.
Definition: NonlinearSystemNldd.hpp:324
void prepareStep()
Called before starting a time step.
Definition: NonlinearSystemNldd.hpp:316
GetPropType< TypeTag, Properties::FluidSystem > FluidSystem
Definition: NonlinearSystemNldd.hpp:157
typename NonlinearSystemBlackOilReservoir< TypeTag >::Mat Mat
Definition: NonlinearSystemNldd.hpp:167
GetPropType< TypeTag, Properties::SolutionVector > SolutionVector
Definition: NonlinearSystemNldd.hpp:162
static constexpr int numEq
Definition: NonlinearSystemNldd.hpp:169
SubDomain< Grid > Domain
Definition: NonlinearSystemNldd.hpp:165
void writeNonlinearIterationsPerCell(const std::filesystem::path &odir) const
Write the number of nonlinear iterations per cell to a file in ResInsight compatible format.
Definition: NonlinearSystemNldd.hpp:556
const SimulatorReport & localAccumulatedReports() const
return the statistics of local solves accumulated for this rank
Definition: NonlinearSystemNldd.hpp:517
GetPropType< TypeTag, Properties::Indices > Indices
Definition: NonlinearSystemNldd.hpp:159
WellModel & wellModel()
Definition: NonlinearSystem.hpp:87
Interface class for SimulatorTimer objects, to be improved.
Definition: SimulatorTimerInterface.hpp:34
Vector extractVector(const Vector &x, const std::vector< int > &indices)
Definition: extractMatrix.hpp:104
void copySubMatrix(const Matrix &A, const std::vector< int > &indices, Matrix &B)
Definition: extractMatrix.hpp:35
void setGlobal(const Vector &x, const std::vector< int > &indices, Vector &global_x)
Definition: extractMatrix.hpp:115
Matrix extractMatrix(const Matrix &m, const std::vector< int > &indices)
Definition: extractMatrix.hpp:47
void unPack(PV &privar, const std::size_t meanings)
Definition: priVarsPacking.hpp:41
std::size_t pack(const PV &privar)
Definition: priVarsPacking.hpp:31
std::size_t countGlobalCells(const Grid &grid)
Get the number of cells of a global grid.
Definition: countGlobalCells.hpp:80
void detectOscillations(const std::vector< std::vector< Scalar > > &residualHistory, const int it, const int numPhases, const Scalar relaxRelTol, const int minimumOscillatingPhases, bool &oscillate, bool &stagnate)
Detect oscillation or stagnation in a given residual history.
std::vector< int > findOverlapCells(const Grid &grid, const ElementMapper &elementMapper, const std::vector< int > &owned_cells, std::vector< EntitySeed > &seeds, const int num_layers, const int mark, std::vector< int > &in_domain)
Definition: NonlinearSystemNldd.hpp:100
Definition: blackoilbioeffectsmodules.hh:45
Opm::DeferredLogger gatherDeferredLogger(const Opm::DeferredLogger &local_deferredlogger, Parallel::Communication communicator)
Create a global log combining local logs.
std::pair< std::vector< int >, int > partitionCells(const std::string &method, const int num_local_domains, const GridView &grid_view, const std::vector< Well > &wells, const std::unordered_map< std::string, std::set< int > > &possibleFutureConnections, const ZoltanPartitioningControl< Element > &zoltan_ctrl, const int num_neighbor_levels)
void printDomainDistributionSummary(const std::vector< int > &partition_vector, const std::vector< Domain > &domains, SimulatorReport &local_reports_accumulated, std::vector< SimulatorReport > &domain_reports_accumulated, const Grid &grid, int num_wells)
Definition: NlddReporting.hpp:219
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 writePartitions(const std::filesystem::path &odir, const std::vector< Domain > &domains, const Grid &grid, const ElementMapper &elementMapper, const CartMapper &cartMapper)
Definition: NlddReporting.hpp:164
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
void writeNonlinearIterationsPerCell(const std::filesystem::path &odir, const std::vector< Domain > &domains, const std::vector< SimulatorReport > &domain_reports, const Grid &grid, const ElementMapper &elementMapper, const CartMapper &cartMapper)
Definition: NlddReporting.hpp:104
Solver parameters for the NonlinearSystemBlackOilReservoir.
Definition: BlackoilModelParameters.hpp:208
This class carries all parameters for the NewtonIterationBlackoilInterleaved class.
Definition: FlowLinearSolverParameters.hpp:98
bool is_nldd_local_solver_
Definition: FlowLinearSolverParameters.hpp:112
void init(bool cprRequestedInDataFile)
bool linear_solver_print_json_definition_
Definition: FlowLinearSolverParameters.hpp:114
std::string linsolver_
Definition: FlowLinearSolverParameters.hpp:113
Definition: SimulatorReport.hpp:202
SimulatorReportSingle success
Definition: SimulatorReport.hpp:203
A struct for returning timing data from a simulator to its caller.
Definition: SimulatorReport.hpp:34
double linear_solve_time
Definition: SimulatorReport.hpp:43
int skipped_domains
Definition: SimulatorReport.hpp:73
double assemble_time
Definition: SimulatorReport.hpp:39
double assemble_time_well
Definition: SimulatorReport.hpp:41
double solver_time
Definition: SimulatorReport.hpp:38
bool converged
Definition: SimulatorReport.hpp:57
double pre_post_time
Definition: SimulatorReport.hpp:40
double total_time
Definition: SimulatorReport.hpp:37
double linear_solve_setup_time
Definition: SimulatorReport.hpp:42
unsigned int total_newton_iterations
Definition: SimulatorReport.hpp:50
double update_time
Definition: SimulatorReport.hpp:45
double local_solve_time
Definition: SimulatorReport.hpp:44
unsigned int total_linearizations
Definition: SimulatorReport.hpp:49
unsigned int total_linear_iterations
Definition: SimulatorReport.hpp:51
Definition: SubDomain.hpp:124