Skip to content

fix(prt): support exchange coupling to ATS flow models#2753

Draft
wpbonelli wants to merge 31 commits into
MODFLOW-ORG:developfrom
wpbonelli:prt-ats
Draft

fix(prt): support exchange coupling to ATS flow models#2753
wpbonelli wants to merge 31 commits into
MODFLOW-ORG:developfrom
wpbonelli:prt-ats

Conversation

@wpbonelli
Copy link
Copy Markdown
Member

@wpbonelli wpbonelli commented Apr 3, 2026

Support exchange-coupling PRT to GWF models that use adaptive time stepping (ATS). This would previously produce incorrect results, as PRT advance/solve routines are generally stateful where the other models are idempotent.

Make these routines idempotent by "staging" tracking state during each solve and only "committing" it on successful time steps.

This also corrects PRT's behavior when it's solved in the same solution group with a flow model with mxiter > 1. Flopy puts models in the same solution group by default, but we didn't hit this before now because mxiter = 1 by default.

This required fixes in mem_reallocate() routines to correctly handle the array shrinking case (e.g. particles are released in a time step that then fails, so need to be "unreleased"). Existing uses must have been limited to expanding.

Also required buffering track records in memory during the time step, and reporting them when the model OT hook is called for a successful time step. The buffer size starts at 64, doubles lazily as needed, and never shrinks, just overwritten on retried steps. (The starting size seems like an arbitrary choice; the release point count seemed like a reasonable guess, but there is no guaranteed relationship between that and the number of particles ultimately released, so I just made it a constant).

This is a departure from the pattern up to now, which was to write particle events immediately, a deliberate choice motivated by MP7 suffering OOM errors when consolidating internal scratch files before writing the final output file for large models. But since just one time step is buffered at once the max usage should typically still be lower than MP7.


Checklist of items for pull request

  • Replaced section above with description of pull request
  • Added new test or modified an existing test
  • Ran ruff on new and modified python scripts in .doc, autotests, doc, distribution, pymake, and utils subdirectories.
  • Formatted new and modified Fortran source files with fprettify
  • Added doxygen comments to new and modified procedures
  • Updated develop.toml with a plain-language description of the bug fix, change, feature; required for changes that may affect users
  • Removed checklist items not relevant to this pull request

@wpbonelli wpbonelli added this to the 6.7.1 milestone Apr 3, 2026
@wpbonelli wpbonelli added the bug label Apr 3, 2026
@wpbonelli wpbonelli marked this pull request as ready for review April 14, 2026 23:52
@wpbonelli wpbonelli marked this pull request as draft April 15, 2026 00:30
@wpbonelli wpbonelli force-pushed the prt-ats branch 2 times, most recently from d24b5ec to 4278c86 Compare April 16, 2026 02:41
@wpbonelli wpbonelli marked this pull request as ready for review April 16, 2026 02:42
@wpbonelli wpbonelli requested a review from aprovost-usgs April 16, 2026 02:43
Comment thread src/Solution/ParticleTracker/Particle/ParticleTracks.f90 Outdated
Comment thread doc/ReleaseNotes/develop.toml Outdated
[[items]]
section = "fixes"
subsection = "model"
description = "A PRT model could not previously be coupled via an exchange to a GWF model that uses adaptive time stepping (ATS). This is now supported."
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.

is the issue that it could not be coupled, or that when coupled to a GWF-ATS model it did not work properly (in that it did not reset the particles)?

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.

Good point, it could be coupled but the results would be incorrect. Will improve the phrasing.

do i = 1, this%ntrackfiles
file = this%files(i)
if (file%iun > 0 .and. &
(file%iprp == -1 .or. file%iprp == rec%iprp)) &
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.

It looks like the ParticleTracks object is in charge of determining where the buffered records are written. To do this, it needs to be aware of the kinds of things that can have an associated output file -- currently, the model (iprp == -1 ?) and the prp's. just wondering whether there's a (reasonable) way to arrange this so that ParticleTracks doesn't need to be aware of that, and whether that would be desirable. I.e., the model and the prp's (and whatever other objects creating track output there might hypothetically be in the future -- advanced packages?) decide for themselves which records they want in their output. Maybe each even has its own buffer? Could be something to chat about at some point.

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.

Nice idea.. Currently the model owns a single ParticleTracksType which knows about model- and PRP-scoped output files, which is awkward. Would make more sense for each to manage its own output. It would be really nice if model and package could select events independently. Need to consider how to configure that, since event selection is in OC.

Comment thread src/Model/ParticleTracking/prt-prp.f90 Outdated
! members
type(PrtFmiType), pointer :: fmi => null() !< flow model interface
type(ParticleStoreType), pointer :: particles => null() !< particle store
type(ParticleStoreType), pointer :: particles_old => null() !< old particle state (for ATS)
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.

From the point of view of anything within PRT, does it matter why the tracking is being retried (e.g., because of ATS, or because of Picard)? If not, then maybe most comments (like this one) don't need to refer specifically to ATS, except perhaps in one place just to cite examples of why you might need this retry capability. In other words, maybe the comments can be less ATS-specific overall.

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.

Will remove mention of ATS here. It may not be the only use case for the old state snapshot in the future, in any case.

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.

it does matter why the advance hook is being called though (detail in comment below)

Comment thread src/Model/ParticleTracking/prt.f90 Outdated
! Reset state variable
irestore = 0
if (iFailedStepRetry > 0) irestore = 1
! The model should advance just once per (step, ATS retry) pair.
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.

Mentioning ATS and Picard as specific examples of retries is good here. However, instead of calling it a "(step, ATS retry) pair" up front, you might consider just "(step, retry) pair", and maybe even rename iFailedStepRetry to simply iRetry to keep it generic?

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.

iFailedStepRetry comes from SimVariablesModule, so it'd need a local variable if we want to clean this up (which I'm not against doing)

Re: ATS vs Picard, I tried to clarify the comment. The PR description didn't really nail this either at first, I'll edit that too. I figure it may help to reserve "retry" to mean "ATS retry" and "rerun" or "repeated invocation" or similar for when advance is called more than once. Retry reruns need to run normally, but not Picard-triggered reruns.

Comment thread src/Model/ParticleTracking/prt-prp.f90 Outdated
! Coincident release times are merged to
! a single time by the release scheduler.

! Save or restore particle state for ATS
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.

"for retry"?

Comment thread src/Model/ParticleTracking/prt.f90 Outdated

! Note: particle tracking output is handled elsewhere
! Flush buffered track events to disk. prt_ot is called once per
! converged time step from Mf6FinalizeTimestep, outside the ATS
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.

"outside the retry loop"?

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.

maybe just not say anything here, and let it be explained elsewhere?

!! only mutable state (positions, status, tracking time, etc) without
!! immutable state (identity, options) saves space and time, but note
!! that it assumes this store already has all the immutable state; if
!! this routine is ever used beyond the context of ATS compatibility,
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.

"retry (e.g., ATS) compatibility"?

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.

updated phrasing

!> @brief Buffer a particle event for deferred write.
!! The buffer can be flushed to disk by flush_buffer,
!! or discarded by discard_buffer (e.g., on a failed
!! ATS retry). The buffer starts at 64 events, then
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 reference to ATS as an example is good i think

end subroutine flush_buffer

!> @brief Discard buffered events.
!! Called from prt_ad at the start of each new ATS retry attempt,
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.

"each new tracking retry"?

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.

updated

Copy link
Copy Markdown
Contributor

@aprovost-usgs aprovost-usgs left a comment

Choose a reason for hiding this comment

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

I have some comments/questions, but don't let them hold up the merge unless you feel there's more discussion needed

@wpbonelli wpbonelli marked this pull request as draft April 24, 2026 19:14
@wpbonelli
Copy link
Copy Markdown
Member Author

#2789 should go before this

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants