COMPS, NCOMPS, WELLSTRE and WINJGAS are all consumed: COMPS and NCOMPS
set the component count, and WELLSTRE/WINJGAS supply the injection
stream composition used by the compositional well model. Listing them
as unsupported made every flow_comp run warn about its own input.
Record the WINJGAS items the handler really does ignore, MAKEUPGAS and
STAGE, as partially supported instead, so that signal is not lost.
- One preconditioner for whole TPSA system; three sub-block preconditioner can be set by user
- Allows for Hypre, which is default if linked; else ilu0
The auto-choke group pressure is imposed on its wells through a path of
its own. Apply the same eligibility rule as for the extended network:
a well without a VFP table cannot be put under THP control, so no THP
limit is imposed on it, while its rates still contribute to the group
flow.
No result changes: wellHasTHPConstraints() already neutralized the
imposed limit for such wells, so the guarded path was inert.
The network cannot put a well without a VFP table under THP control,
so it should neither impose a dynamic THP limit on such a well nor
record a retained network_thp_limit for it. Previously only
wellHasTHPConstraints() neutralized the imposed limit; guarding the
imposition sites as well keeps the retained state meaningful and
removes the reliance on every getTHPConstraint() caller checking
wellHasTHPConstraints() first. No result changes: the guarded paths
were inert for wells without a VFP table.
No restart array carries the retained limit yet, so it is reconstructed
for a detached THP-controlled producer whose restart THP differs from
the schedule THP limit (a well on its own, possibly UDA-driven, limit
is left untouched). A retained limit that is not active at the restart
time is lost; this limitation is documented in the code.
Report-step events do not reach the simulator for ACTIONX updates, so
the SimulatorUpdate::thp_respecified_wells set of the companion opm-common
branch is used instead.
The limit is stored in SingleWellState and stays in force, also across
shut periods, until the deck re-specifies the well THP limit or VFP
table (the WELL_THP_UPDATE event of the companion opm-common branch).
Previously a detached well silently reverted to the static THP limit
from the deck.
The preceding commit removed the only reader of this flag, leaving
write-only state that was still computed, serialized and compared.
Wells with a trivial group rate target are recognised on demand by
stoppedOrZeroRateTarget() and wellUnderZeroRateTarget().
The else branch of the rate scaling only existed to set the flag and
goes with it; the scaling itself is unchanged.
activeProductionConstraint() skipped the switch to THP control for
GRUP-controlled wells when ws.trivial_group_target was set. That flag
is only rewritten in updateWellStateWithTarget() when a well switches
control, so it can go stale - it is set at the initial ORAT->GRUP
switch while a sibling well still covers the whole group target - and
the veto then blocks the very switch that would refresh it. Network
wells could therefore keep flowing with a THP below the nodal pressure
of the node they feed, which Eclipse never does.
Both call paths into checkIndividualConstraints() already return early
for wells whose freshly evaluated group target is zero
(stoppedOrZeroRateTarget() and wellUnderZeroRateTarget()), so the veto
could only ever act on stale information and is removed.
Without a VFP table a THP limit cannot be converted to a BHP, so the
well cannot operate under a THP constraint. Previously, a network well
with WCONPROD items 10 and 11 defaulted aborted the simulation with
"Nonexistent VFP table 0 referenced". The well now stays on its
remaining controls while its rates still enter the network pressure
calculation.
A slave whose schedule ran out before the master's deadlocked the whole
coupled run. The slave called MPI_Comm_disconnect(), which blocks until
the master joins the collective close, while the master blocked in
MPI_Recv() waiting for a next report date that the slave would never
send. Neither side could proceed, and the remaining slaves were starved
behind the stuck master.
Two things were wrong. The message protocol had no way for a slave to
say that its run had ended. And receiveTerminateAndDisconnect()
disconnected without checking the signal value it had just received, so
it mistook the master's routine "keep going" for a shutdown order and
also logged a misleading "Received terminate signal from master
process".
A slave that runs out of report steps now answers on the
next-report-date channel with a negative sentinel value. A genuine
offset is measured from the slave's own start and is therefore never
negative, so no new message tag is needed, and the master learns about
the ended slave at exactly the point where it used to block. The master
acknowledges, disconnects that one intercommunicator, reports
Slave run RES-1 has ended.
Master run will continue with no flow from this slave.
and carries on without that slave.
Add slaveHasEnded() and slaveIsCoupled() alongside the existing
slaveIsActivated(). slaveIsActivated() keeps its literal meaning and
stays monotonic, while slaveIsCoupled() means "activated and not ended"
- the question every gate actually wants to ask before exchanging
messages with a slave, or before letting its master groups contribute
rates and guide rates. Switching those gates is most of the diff.
Little more was needed, because the master already had a complete
zero-flow path for a slave that has not activated yet:
receiveProductionDataFromSlaves() and receiveInjectionDataFromSlaves()
already substituted zeros, and
excludeInactiveSlaveMasterGroupsFromDistribution_() already forced the
effective group-controlled-wells count to zero so those master groups
drop out of guide-rate distribution. An ended slave reuses all of it.
Verified on a two-slave case with the first slave's last report step
removed: the run now completes, and from the moment that slave ends the
field oil production rate equals the remaining slave's group rate
exactly. With the unmodified deck, results are bit-identical to a run
without this change at every shared report point.
Making the master stop instead of continuing, via item 8 of GECON, is a
separate option that remains unimplemented.
A fracture model reads its seeds out of the deck during the run rather than
only at setup, so those keywords must survive. Gated on runspec().frac()
rather than on mechanics as a whole: a TPSA run has no such need, and
svenn-t asked not to enable it for every geomechanics simulator.
Decided inside readDeck because keepKeywords is an input to deck reading
while frac() is an output of it - the caller cannot know yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
recordNewDynamicWellConns() passes fracture-created connections (well name
+ zero-based Cartesian cell ids) to the output layer on the I/O rank, so
the summary engine can instantiate connection-level vectors for them
(opm-common already provides the receiving API).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The correction applies only while the wells of this sync step have not
been solved, and only to wells the schedule flags as NEW_WELL. That it
fires exactly once per newly opened well depends on the NEW_WELL flag
being cleared after the first time step of the report step, which is a
property of the live event object and not of the report-step snapshot
BlackoilWellModel keeps beside it.
Nothing in the code said so, and the snapshot is the more obvious source
to reach for. Switching to it would reintroduce the very problem the
correction exists to avoid, on every sync step after the first. Write
that down.
Prompted by a review comment on #7366.
The correction for a newly opened, not yet solved well reproduced only
part of what sumWellPhaseRates() does per well: it applied the network
efficiency factor but not the well's efficiency_scaling_factor, and it
filtered on nothing but the NEW_WELL event. Two consequences. A newly
opened injector would have had its rate subtracted from the group's
*production*, because getWellRateVector() reports positive magnitudes
for producers and injectors alike; the model this was developed on only
escapes that because its new injectors are GRUP-controlled and start
from zero rather than from a target. And a producer whose rates are
dynamically scaled was only partially removed. Two further mismatches:
shut wells are skipped by the sum but were not skipped here, and
getWellRateVector() reads the nupcol well state where the sum reads the
live one.
Build the correction from wellRateContributionToGroup() instead, the
same per-well step the sums use, so it removes precisely what they
added. That makes it rank-local, like the sums: the network rates now
apply it before their reduction, and the surface rates reduce it before
subtracting it from the already-reduced group state value.
Spotted by Copilot in review of #7366.
sumWellPhaseRates() decides per well whether it belongs in the sum at
all -- present in this rank's well state, owned by this rank, of the
right type, not shut -- and with what efficiency factor and sign it
enters. Callers that need to add or remove a single well's share of
such a sum have to reproduce all of that, and get it wrong when they
reproduce only part.
Move the body of the well loop into wellRateContributionToGroup() and
call it from the loop. No functional change.
so both StandardWell and MultisegmentWell can use them. The temperature and
salt concentration of the first perforated cell are kept in a small struct
with named members instead of a std::tuple.
restoreMasterGroupControlsFromSchedule_() lost both its only caller and
its definition in #7203; the declaration in RescoupConstraintsCalculator
was left behind.
A master group under a GCONINJE mode that turns production into an
injection target -- REIN, SALE or VREP -- gets that target from the
production the master has been told about. The target is computed once
per sync step, in beginTimeStep(), from the group rates the slaves report
before they solve their wells for that step, so it is a target for the
*previous* step's production. The targets are never revised, even though
the master keeps receiving fresh slave rates all through the step.
A step in which slave production changes therefore injects the wrong
amount for its whole length. With a field under GCONINJE ... REIN, the
gas not injected leaves the balance as sales gas, so the sales rate
jumps for one step and then returns.
Recompute the injection targets after each non-final slave-rate receive
inside the master's network iteration and send them to the slaves, which
now expect that message where they previously only sent. Production
constraints are not resent: the slaves have already solved their wells
against them, and they are what produced the rates the recomputation is
based on.
The recomputation is unconditional, so it also re-sends targets that
cannot have changed -- a RATE target is a deck constant, and a RESV
target moves only with the other phases' reservoir injection. A TODO on
the new entry point records what a filter would have to check: not the
group's own GCONINJE record, since the target comes from the controlling
ancestor and that ancestor's mode changes during a run.
The injection half of calculateSlaveGroupConstraints_() is split out as
calculateSlaveGroupInjectionTargets_() so both entry points share it.
A reservoir-coupling slave ships its group rates to the master twice per
sync step: once in beginTimeStep() before it solves its wells for the
step, and once after. A well that opens in the report step is put on its
WCONPROD target by SingleWellState::update_producer_targets() before that
solve runs, as an initial guess, so on the first send the slave reports a
target as achieved production.
The master folds those rates into the field's reinjection base. With the
field under GCONINJE ... REIN, a multi-million sm3/day gas target arriving
that way pushes the reinjection target past the GCONINJE maximum, which
switches the field's gas injection control to RATE. In RATE mode the
target is the maximum itself, independent of production, so the correct
values that arrive later in the same sync step cannot bring it back down:
the injectors sit on their cap for the whole step.
Track in ReservoirCouplingSlaveReportStep whether the wells have been
solved this sync step -- alongside the other per-sync-step flags kept
there -- and while they have not, leave a well that opens in this report
step out of the group rates the slave sends. The event alone cannot be
used as the condition: it stays set for the whole report step and so
cannot tell the two sends apart.
The exclusion covers both the surface production rates and the network
surface rates -- the master uses the latter as the flow of its network's
leaf nodes -- and descends into child groups applying their efficiency
factors, since a group named in GRUPSLAV need not hold wells directly.
The gas reinjection rate the slave reports is derived the same way and has
the same defect, but the master stores that field without ever reading it,
so it is left alone.
computeSegmentFluidProperties() allocated and value-initialized a vector of
segment PVT values on every call. Keep it as a member alongside the segment
fluid states it is filled from, so the assembly path only refills it.
The segment property calculations were templated on the fluid state type, which
depends on the TypeTag that MultisegmentWellSegments does not have, so they had
to live in the header.
They only read pressure, temperature, salt concentration, the inverse formation
volume factors and Rs/Rv from the fluid state, and those are all EvalWell, which
is concrete for <FluidSystem, Indices>. Pass them in a small struct instead, so
computeFluidProperties(), computeVolumeRatio() and calculatePhaseState() are no
longer templates and are instantiated with the class in the .cpp file.
Before: updateSegmentFluidState() cached the volume ratio of every segment and
getSegmentSurfaceVolume() read that cache; on the assembly path
computeFluidProperties() overwrote it immediately, so the work was discarded.
Now: updateSegmentFluidState() only builds the fluid states, the ratio is
computed where it is consumed and passed to getSegmentSurfaceVolume(), and
volume_ratios_ has a single writer.
It initializes mass inventory and, when enabled, energy inventory in the
same per-segment loop; the separate computeInitialSegmentEnergy() helper was removed.
The per-segment fluid state becomes the single source of truth for the
segment fluid properties and surface volume, for thermal and non-thermal
runs alike. It is rebuilt from the current primary variables at the point
of consumption (computeSegmentFluidProperties), and its stored inverse
formation volume factors and Rs/Rv are reused whenever they coincide with
the values the property calculation needs (both oil and gas present, the
common case). This removes the duplicated from-scratch PVT calculations
in computeFluidProperties()/getSurfaceVolume() and the scattered fluid
state refresh calls, while reproducing the previous results exactly.
- Old 7x7 block structure is broken up to several sub-matrices, purpose-made for preconditioners like Hypre.
- New structure made to interface 7x7 structure in linearizer without changes.