Skip to content

CPR: compute the true-IMPES weights per degree of freedom - #7276

Draft
hnil wants to merge 3 commits into
OPM:masterfrom
hnil:pr/cpr-dof-weights
Draft

CPR: compute the true-IMPES weights per degree of freedom#7276
hnil wants to merge 3 commits into
OPM:masterfrom
hnil:pr/cpr-dof-weights

Conversation

@hnil

@hnil hnil commented Aug 4, 2026

Copy link
Copy Markdown
Member

Both true-IMPES weight formulas depend only on a degree of freedom's own intensive
quantities -- and, for the non-analytic one, its own volume. No neighbours, no geometry.
Walking the grid's elements to evaluate them is therefore not necessary, and it is the
only thing tying these weights to the grid.

The choice between the two walks is made on two facts a model can now be asked for
directly:

  • formsStorageFromIntensiveQuantities on the residual -- BlackOilLocalResidualTPFA
    has the computeStorage() overload taking intensive quantities alone, the
    element-by-element residual does not.
  • intensiveQuantityCacheEnabled() on the model -- the indexed lookup is valid only
    when the cache is on. storeIntensiveQuantities() does not answer that, being true
    also when only the thermodynamic hints are kept.

The element walk is kept and taken whenever either is missing, so
flow_blackoil_legacyassembly and anything else on the element-based residual is
unaffected.

dofTotalVolume() is the same quantity the element walk reads off the stencil --
accumulated once per degree of freedom and completed over process boundaries by the sum
handle -- minus the extrusion factor, which is applied explicitly.

Verified on SIMPLE_MECH_NX_11_NY_11_NZ_25_FRAC_SEQ: 252 summary vectors identical to
1e-12 under cpr_trueimpes, and a 13-test local regression set unchanged under
cpr_trueimpesanalytic. Draft: I have not run the full opm-tests suite.

Motivation is work on flow degrees of freedom that have no grid element (numerical
aquifers and embedded fractures as auxiliary DOFs), which cannot be given a weight at all
today -- but the change stands on its own.

🤖 Generated with Claude Code

hnil and others added 3 commits August 4, 2026 14:31
Two facts that code outside a model has no way to ask for, and that it must know
before it can walk the degrees of freedom instead of the elements:

  - whether the residual's computeStorage() can be given a degree of freedom's
    intensive quantities directly.  BlackOilLocalResidualTPFA has that overload;
    the element-by-element residual does not, so the base states false and the TPFA
    one overrides it.  The model carries the answer through.

  - whether the intensive quantities can be had by index at all, which is what the
    cache is.  storeIntensiveQuantities() does not answer that, being true also
    when only the thermodynamic hints are kept.

No behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The weight is a function of the degree of freedom's own fluid state and primary
variables -- the formation volume factors and the dissolved and vaporised ratios --
and of nothing else.  No neighbours, no geometry.  Walking the grid's elements to
find it is therefore not necessary, and it is the only thing tying these weights to
the grid: a degree of freedom that has no element cannot be given one at all.

Walk the degrees of freedom when their intensive quantities can be had by index, and
the elements when they cannot.  The per-degree-of-freedom expression is written once
and shared between the two walks so they cannot drift apart.

Both walks cover the same degrees of freedom -- the element chunks are built over
Partitions::all -- and read the same fluid states, so the weights are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…esidual allows

Same argument as for the analytic variant, with one requirement more.  The
accumulation term of a degree of freedom is a function of its own intensive
quantities and its own volume; nothing about it needs an element.  Forming it
without one takes a storage term computed from those quantities alone, which the
blackoil TPFA residual has and the element-by-element residual does not -- so the
choice is made on that, and the element walk is taken whenever it or the indexed
lookup is unavailable.

The volume is the one substitution that is not a rename: dofTotalVolume() is the
same quantity the element walk reads off the stencil -- accumulated once per degree
of freedom and completed over process boundaries by the sum handle -- but without
the extrusion factor, which is applied here explicitly.

The two walks share the expression that turns an accumulation term into a weight, so
they cannot drift apart.  On a deck run under cpr_trueimpes the 252 summary vectors
are identical to 1e-12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hnil
hnil force-pushed the pr/cpr-dof-weights branch from 180de88 to c9ede47 Compare August 4, 2026 12:37
@hnil
hnil requested a review from atgeirr August 4, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant