Skip to content

Let the classic CPRW contract D for multisegment wells - #7282

Open
hnil wants to merge 2 commits into
OPM:masterfrom
hnil:pr/msw-cprw-contract-d
Open

Let the classic CPRW contract D for multisegment wells#7282
hnil wants to merge 2 commits into
OPM:masterfrom
hnil:pr/msw-cprw-contract-d

Conversation

@hnil

@hnil hnil commented Aug 5, 2026

Copy link
Copy Markdown
Member

Standard wells build their coarse CPRW diagonal by contracting D
(StandardWellEquations reads duneD_[0][0]). Multisegment wells instead use
minus the row sum of the contracted reservoir entries, which never reads D and so
drops every segment-to-segment coupling — most of the well once there is one
segment per connection.

preconditioner.well_coarse_diagonal = contract_d makes the multisegment path
contract D as well. Default auto is today's behaviour, so nothing changes unless
asked for. Contracting D is also the Galerkin diagonal for the prolongation the
coarse column already assumes: one coarse value spread over the well's segment
pressures.

The flag is threaded next to use_well_weights. The contraction is
mswellhelpers::contractCprWellDiagonal, unit tested in test_MswCprWellDiagonal;
it returns 1 on exact cancellation, since a zero would make the coarse pressure
system singular.

Full Norne with --convert-to-multisegment-well=per-connection, linear iterations:

linear iterations
cprw 2716
cprw, well_coarse_diagonal = contract_d 2646

Standard wells are byte-identical either way, as they must be.

🤖 Generated with Claude Code

Note on the well_coarse_diagonal config key

This PR's classic path (PressureBhpTransferPolicy) accepts {auto, contract_d} and defaults to auto; #7278's system path accepts {auto, contract_d, row_sum} and defaults to contract_d. Same key name, different valid set and different default — each preserving its own historical behaviour, which is why they differ. Recorded here so that if the two paths are ever unified, the divergence is a known decision rather than a surprise.

@hnil hnil added the manual:enhancement This is an enhancement/improvent that needs to be documented in the manual label Aug 5, 2026
using Scalar = typename DiagMatWell::field_type;
Scalar diag = 0.0;
for (std::size_t row = 0; row < D.N(); ++row) {
for (auto col = D[row].begin(), end = D[row].end(); col != end; ++col) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe not correct but the contraction reads only the first lambda.size() (= numEq) rows of each D block, which is correct and I think consistent with the B-contraction above, but it silently assumes the first numEq rows of D are the conservation equations aligned with lambda. Could we add a cheap guard to catch a future reordering of well primary variables, e.g. assert(lambda.size() <= Block::rows) (or a one-line comment reaffirming the row ordering)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an assert(lambda.size() <= Block::rows) so a future reordering of the well primary variables trips instead of silently contracting the wrong rows.

/// the coarse pressure system singular.
template <class DiagMatWell, class WellWeight>
typename DiagMatWell::field_type
contractCprWellDiagonal(const DiagMatWell& D,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this implements the same contraction as the inline D-loop in SystemCprwPressureStage::assembleCoarseMatrix (#7278), just over Dune BCRSMatrix instead of the merged blocks. I dont know if we need to unify them or no, but we can add // keep in sync with SystemCprwPressureStage-style comment on both sides would help whoever touches one later.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed they are the same contraction. Added a cross-reference comment here rather than unifying - the two operate on different types (Dune BCRSMatrix vs merged blocks) and #7278 is not merged, so a shared helper would have to land there first.

// for every well; "auto" (the default) leaves the multisegment path
// on its historical row-sum diagonal. Standard wells contract D
// either way, so this only changes multisegment wells.
const auto diagonal = prm_.get<std::string>("well_coarse_diagonal", "auto");

@ElyesAhmed ElyesAhmed Aug 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this is fixed somewhere else or in other PRs: but this path accepts {auto, contract_d} and defaults to auto, while #7278's system path (wellCoarseDiagonalFromString) accepts {auto, contract_d, row_sum} and defaults to contract_d. Same config key name, different valid set and default. Each preserving its own historical default is fine, but worth one line in the PR description so if we are tuning both paths we understand that how row_sum is rejected

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real divergence, and deliberate here: row_sum only makes sense for the merged system matrix, so the classic path has no meaningful implementation of it. Noted in the PR description so it is not read as an oversight.

@ElyesAhmed ElyesAhmed left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is Independent of the other two PRs so its better. This one is completely fine

@hnil

hnil commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

All three taken, thanks.

  • Guard added: assert(lambda.size() <= Block::rows), with a comment saying it is there to catch a reordering of the well primary variables that moves something other than the conservation equations into the first rows.
  • Cross-reference added at contractCprWellDiagonal pointing at the equivalent inline loop in SystemCprwPressureStage::assembleCoarseMatrix, so whoever changes one finds the other. Not unifying them yet — the system path is still moving in Add a CPRW pressure stage to the system solver #7278.
  • The config-key divergence is now in the PR description: classic accepts {auto, contract_d} defaulting to auto, system accepts {auto, contract_d, row_sum} defaulting to contract_d. Each keeps its own historical default; written down so a future unification treats it as a known decision.

Unrelated, found while testing this: SPE1CASE2_GASWATER_MSW trips a pre-existing assert at StandardWell_impl.hpp:67 (num_conservation_quantities_ == numWellConservationEq) in an assert-enabled build. It fires without this branch too, and CI does not see it because CI builds with NDEBUG.

@akva2

akva2 commented Aug 10, 2026

Copy link
Copy Markdown
Member

lies.

@hnil
hnil force-pushed the pr/msw-cprw-contract-d branch from 330e23f to 8b12de9 Compare August 11, 2026 08:23
@bska

bska commented Aug 11, 2026

Copy link
Copy Markdown
Member

jenkins build this please

@bska

bska commented Aug 11, 2026

Copy link
Copy Markdown
Member

jenkins build this failure_report please

hnil and others added 2 commits August 11, 2026 14:06
preconditioner.well_coarse_diagonal = contract_d takes a well's coarse
diagonal from lambda' D(:,p) instead of minus the row sum of its contracted
reservoir entries. Default "auto" keeps today's behaviour.

Standard wells have always contracted D (StandardWellEquations reads
duneD_[0][0]); only the multisegment path used the row sum, which never reads
D and therefore throws away all segment-to-segment coupling. With one segment
per connection that is most of the well. Contracting D is also the Galerkin
diagonal for the prolongation the coarse column already assumes -- one coarse
value spread over all of the well's segment pressures.

The flag is threaded next to use_well_weights, from PressureBhpTransferPolicy
down to MultisegmentWellEquations::extractCPRPressureMatrix. The contraction
itself is mswellhelpers::contractCprWellDiagonal, unit tested in
test_MswCprWellDiagonal; it returns 1 on exact cancellation, since a zero
would make the coarse pressure system singular.

Norne with --convert-to-multisegment-well=per-connection: every coarse matrix
differs from the default, i.e. the flag reaches the assembly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 2e00bc3)
- assert(lambda.size() <= Block::rows): the weights index the
  conservation-equation rows of a D block, so a future reordering of the
  well primary variables that moves something else into the first rows
  is caught rather than silently contracting the wrong entries.
- Note pointing at the equivalent inline loop in
  SystemCprwPressureStage::assembleCoarseMatrix, so whoever changes one
  finds the other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hnil
hnil force-pushed the pr/msw-cprw-contract-d branch from 8b12de9 to 8b01b92 Compare August 11, 2026 12:06
@bska

bska commented Aug 11, 2026

Copy link
Copy Markdown
Member

jenkins build this failure_report please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual:enhancement This is an enhancement/improvent that needs to be documented in the manual

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants