Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(prt): rework vertical tracking approach #2066

Merged
merged 49 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9ee5f52
take some notes
wpbonelli Nov 15, 2024
ff98bb6
stationary if dry
wpbonelli Nov 19, 2024
f64fd33
fix 2014
wpbonelli Nov 19, 2024
8cb9419
notes
wpbonelli Nov 20, 2024
3888973
convert example model to test cases, optional termination in dry cells
wpbonelli Nov 21, 2024
f873fa8
tidier prp release logic, notes for revised solution, still need to i…
wpbonelli Nov 22, 2024
9570a88
wip
wpbonelli Nov 23, 2024
2b677f1
closer
wpbonelli Nov 24, 2024
1a44c3b
restore fmi (smaller changeset, cleanup later)
wpbonelli Nov 24, 2024
34c0478
cleanup
wpbonelli Nov 24, 2024
c7af5f3
more cleanup
wpbonelli Nov 24, 2024
63b47ce
working?
wpbonelli Nov 25, 2024
f5edd6f
notes
wpbonelli Nov 25, 2024
c48a56f
fixes
wpbonelli Nov 26, 2024
885bcf3
remove unneded snapshots
wpbonelli Nov 26, 2024
9fbad23
release note
wpbonelli Nov 26, 2024
bdd18ce
fixes
wpbonelli Nov 26, 2024
c94a06a
omit cell id from snapshot comparison
wpbonelli Nov 26, 2024
4062123
fix vertical termination conditions
wpbonelli Dec 1, 2024
6614bb0
cleanup
wpbonelli Dec 3, 2024
b5b5faf
update notes
wpbonelli Dec 3, 2024
fb21b0a
relax comparisons
wpbonelli Dec 3, 2024
b4679fa
seperate release notes, better wording
wpbonelli Dec 3, 2024
3df35b5
report prev cell if we detect backtracking and terminate
wpbonelli Dec 3, 2024
8a4814a
fix notes
wpbonelli Dec 3, 2024
5d587a7
update mf6io
wpbonelli Dec 3, 2024
8900ab9
show snapshot disagreements
wpbonelli Dec 4, 2024
8e62fce
update notes
wpbonelli Dec 4, 2024
cffeb48
restore Sim.f90
wpbonelli Dec 4, 2024
7e08945
fix dry/no exit face check precedence
wpbonelli Dec 4, 2024
de3624d
for real this time?
wpbonelli Dec 4, 2024
8ebc19c
update notes
wpbonelli Dec 4, 2024
7a8dcb8
relax voronoi tests
wpbonelli Dec 4, 2024
d80dc2a
update notes
wpbonelli Dec 4, 2024
22b82cc
notes
wpbonelli Dec 5, 2024
4636c0c
relax dry drop comparison
wpbonelli Dec 5, 2024
f371d75
fix no exit face termination with ifort?
wpbonelli Dec 5, 2024
f2e0fc7
ignore particle for drop case.. stops early with opt=2 and ifort
wpbonelli Dec 5, 2024
0f8c1c5
fix notes, pointer in prp section in mf6io
wpbonelli Dec 6, 2024
021d766
revisions
wpbonelli Dec 6, 2024
6728bf6
method
wpbonelli Dec 6, 2024
5451de0
notes
wpbonelli Dec 6, 2024
e617037
notes
wpbonelli Dec 6, 2024
6ae251b
fix test
wpbonelli Dec 6, 2024
abe8ad3
voronoi test
wpbonelli Dec 6, 2024
fd98950
revisions
wpbonelli Dec 11, 2024
297b082
more revisions
wpbonelli Dec 11, 2024
090d27c
fprettify
wpbonelli Dec 11, 2024
d4109e8
fix comment in test
wpbonelli Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
474 changes: 474 additions & 0 deletions autotest/test_prt_dry.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@
refined cells, instead of the new ternary method
which applies more generally to polygonal cells.

The simulation includes a single stress period
with multiple time steps. This serves to test
whether PRT properly solves trajectories over
"internal" time steps, i.e. within the step's
slice of simulation time, as well as extending
tracking to termination or particle stop times
during the simulation's final time step.
The simulation uses the Newton formulation.

Several cases are provided:
- default: No user-specified tracking times, MP7 in pathline mode.
Expand Down
16 changes: 7 additions & 9 deletions autotest/test_prt_voronoi1.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"""

from pathlib import Path
from platform import processor, system

import flopy
import matplotlib as mpl
Expand Down Expand Up @@ -267,6 +266,7 @@ def plot_output(idx, test):
ax = plt.subplot(1, 1, 1, aspect="equal")
pmv = flopy.plot.PlotMapView(model=gwf, ax=ax)
pmv.plot_grid(alpha=0.25)

pmv.plot_ibound(alpha=0.5)
headmesh = pmv.plot_array(head, alpha=0.25)
cv = pmv.contour_array(head, levels=np.linspace(0, 1, 9), colors="black")
Expand Down Expand Up @@ -331,7 +331,7 @@ def plot_output(idx, test):
plt.savefig(prt_ws / f"{name}.png")


def check_output(idx, test, snapshot):
def check_output(idx, test):
name = test.name
prt_ws = test.workspace / "prt"
prt_name = get_model_name(name, "prt")
Expand All @@ -349,22 +349,20 @@ def check_output(idx, test, snapshot):
pls = pd.read_csv(prt_ws / prt_track_csv_file, na_filter=False)
endpts = pls[pls.ireason == 3] # termination

# compare pathlines with snapshot. particles shouldn't
# have moved vertically. round for cross-platform error.
# skip macos-14 in CI because grid is slightly different
if not (system() == "Darwin" and processor() == "arm"):
assert snapshot == endpts.drop("name", axis=1).round(1).to_records(index=False)
assert np.allclose(endpts.z, 0.5)
assert np.isclose(endpts.y.min(), 1, atol=4)
assert np.isclose(endpts.y.max(), 996, atol=4)


@requires_pkg("syrupy")
@pytest.mark.slow
@pytest.mark.parametrize("idx, name", enumerate(cases))
def test_mf6model(idx, name, function_tmpdir, targets, benchmark, array_snapshot, plot):
def test_mf6model(idx, name, function_tmpdir, targets, benchmark, plot):
test = TestFramework(
name=name,
workspace=function_tmpdir,
build=lambda t: build_models(idx, t),
check=lambda t: check_output(idx, t, array_snapshot),
check=lambda t: check_output(idx, t),
plot=lambda t: plot_output(idx, t) if plot else None,
targets=targets,
compare=None,
Expand Down
5 changes: 3 additions & 2 deletions doc/ReleaseNotes/develop.tex
wpbonelli marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@
\item The number of characters used to represent integers and floating point numbers in MODFLOW input files was restricted to 30. The program was modified to accept any number of characters provided the number is valid. This may be useful for parameter estimation programs that use character substitution to create new input files.
\item A string to character array conversion function in the BMI interface could fail on Apple silicon macOS with recent versions of GNU Fortran, producing array extent errors at runtime. This has been fixed by properly specifying the intent of a dummy argument in the relevant function.
\item The UZF Package will facilitate UZF objects with areas less than the area of the host cell. However, within the GWE model type, the UZE package will not work properly when the area of UZF objects is not equal to the area of the host cell. New code was added to ensure that the area of each UZF object is equal to that of the host grid cell. When this condition is not satisfied, the new code will stop the simulation with an error message indicating which cell is in violation.
% \item xxx

\item With a flow model using the Newton formulation, the PRT model could crash upon a particle's entry into a dry cell. This has been fixed.
wpbonelli marked this conversation as resolved.
Show resolved Hide resolved
\item With a flow model using the Newton formulation, the PRT model could enter an endless loop upon a particle's entry to a dry cell if that cell contains a boundary package (e.g. a pumping well). Where the particle should be captured and terminate, it would instead be passed back and forth between the cell bottom and the top of the cell below. To avoid this, particles are forbidden from backtracking (reentering the previous cell) within the same time step.
wpbonelli marked this conversation as resolved.
Show resolved Hide resolved
\item The PRT model now allows more control over vertical particle motion in dry conditions. In addition to the existing DRAPE option, which controls release-time behavior, the PRP package now provides a DRY\_TRACKING\_METHOD option which configures how dry particles (particles in dry cells, or above the water table in partially saturated cells) behave at tracking time. This option is relevant only when the Newton formulation is used, in which case dry cells remain active; otherwise, dry cells are inactive and particles will terminate. See the MF6IO document for a detailed explanation of DRY\_TRACKING\_METHOD.
\end{itemize}

%\underline{INTERNAL FLOW PACKAGES}
Expand Down
9 changes: 9 additions & 0 deletions doc/mf6io/mf6ivar/dfn/prt-prp.dfn
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,15 @@ optional true
longname drape
description is a text keyword to indicate that if a particle's release point is in a cell that happens to be inactive at release time, the particle is to be moved to the topmost active cell below it, if any. By default, a particle is not released into the simulation if its release point's cell is inactive at release time.

block options
name dry_tracking_method
type string
valid drop stop stay
reader urword
optional true
longname what to do in dry-but-active cells
description is a string indicating how particles should behave in dry-but-active cells (as can occur with the Newton formulation). The value can be ``DROP'', ``STOP'', or ``STAY''. The default is ``DROP'', which passes particles vertically and instantaneously to the water table. ``STOP'' causes particles to terminate. ``STAY'' causes particles to remain stationary but active.

block options
name dev_forceternary
type keyword
Expand Down
2 changes: 2 additions & 0 deletions doc/mf6io/prt/prp.tex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

The PRP Package offers multiple ways to specify particle release times. Particle release times may either be provided explicitly, relative to the simulation start time, or configured relative to the time discretization of stress periods via period block settings. When multiple ways of specifying release times are used together, the resulting set of release times is the union of the times specified by each method.

This package contains options to configure how particles behave in dry conditions. Each of these is described in detail below. Their interaction is summarized in the PRT chapter introduction.

\vspace{5mm}
\subsubsection{Structure of Blocks}
\lstinputlisting[style=blockdefinition]{./mf6ivar/tex/prt-prp-options.dat}
Expand Down
36 changes: 36 additions & 0 deletions doc/mf6io/prt/prt.tex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,42 @@ \subsection{Specifying Cell Face Flows using IFLOWFACE}
\subsection{Particle Mass Budget}
A summary of all inflow (sources) and outflow (sinks) of particle mass is called a mass budget. The particle mass budget is printed to the PRT Model Listing File for selected time steps. In the current implementation, each particle is assigned unit mass, and the numerical value of the flow can be interpreted as particles per time.

\subsection{Vertical Tracking}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should this be here or in the PRP chapter? Also, should it be restated in the supplemental technical info?

..planning to repackage some of this with illustration in an informal migration guide as well, to distribute with the release to testing partners / users

Copy link
Contributor

Choose a reason for hiding this comment

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

My opinion is that it is fine to go here, but it may be worthwhile pointing to it from the PRP chapter.

It probably wouldn't hurt to also summarize this in the supplemental technical information.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree. Though in supptechinfo I'd favor a very generic description that points to mf6io for specifics, since we'll likely be refining the dry tracking options down the road


When a particle is in the flow field, vertical motion can be solved in the same way as lateral motion. Special handling is necessary above the water table.

A dry cell is either an inactive cell or an active but dry cell, as can occur with the Newton formulation.

Normally, an inactive cell might be dry or explicitly disabled (idomain). With Newton, dry cells remain active.

Release-time and tracking-time considerations are described (and implemented) separately.

\subsubsection{Release}

At release time, PRT decides whether to release each particle or to terminate it unreleased.

If the release cell is active, the particle will be released at the user-specified location.

If the release cell is inactive, behavior is determined by the DRAPE option. If the DRAPE option is enabled, the particle will be released from the top-most active cell beneath it, if any. If there is no active cell underneath the particle in any layer, or if DRAPE is not enabled, the particle will terminate unreleased (with status code 8).

Since under the Newton formulation dry cells can remain active, the DRAPE option has no effect when Newton is on (assuming particles are not released into disabled grid regions). Vertical tracking behavior with Newton can be configured using the DRY\_TRACKING\_METHOD option discussed below.

\subsubsection{Tracking}

With the Newton formulation, particles can be released into dry-but-active cells.

Particle trajectories are solved over the same time discretization used by the flow model. A particle may be immersed in the flow field in one time step, and find that the water table has dropped below it in the next.
wpbonelli marked this conversation as resolved.
Show resolved Hide resolved

A particle which finds itself in an inactive cell will terminate with status code 7. This is consistent with MODPATH 7's behavior.

A particle in a dry but active cell, or above the water table in a partially saturated cell, need not terminate. We call such a particle dry. The PRP package provides an option DRY\_TRACKING\_METHOD determining how dry particles should behave. Supported values are DROP (default), STOP, and STAY.

If DROP is selected, or if a DRY\_TRACKING\_METHOD is unspecified, a dry particle is passed vertically and instantaneously to the water table (if the cell is partially saturated) or to the bottom of the cell (if the cell is dry). This repeats (i.e. the particle may drop through multiple cells) until it reaches the water table. Tracking then proceeds as usual. If the vertical column containing the particle is entirely dry, the particle will terminate upon reaching the bottom of the model grid.

If STOP is selected, dry particles will be terminated.
wpbonelli marked this conversation as resolved.
Show resolved Hide resolved

If STAY is selected, a dry particle will remain stationary until a) the water table rises and tracking can continue, b) the particle terminates due to reaching its STOPTIME or STOPTRAVELTIME, or c) the simulation ends.

\subsection{Particle Track Output}

The PRT Model supports both binary and CSV particle track output files. A particle track CSV file contains the output data in tabular format. The first line of the CSV file contains column names. Each subsequent line in the file contains a row of data for a single particle track record, with the following fields:
Expand Down
2 changes: 1 addition & 1 deletion src/Model/ModelUtilities/TrackFile.f90
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module TrackFileModule
!! 1: particle transitioned between cells
!! 2: current time step ended****
!! 3: particle terminated
!! 4: particle exited weak sink
!! 4: particle in weak sink
!! 5: user-specified tracking time
!!
!! Each record has an "istatus" property, which is the tracking status;
Expand Down
120 changes: 81 additions & 39 deletions src/Model/ParticleTracking/prt-prp.f90
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ module PrtPrpModule
integer(I4B), pointer :: istopweaksink => null() !< weak sink option: 0 = no stop, 1 = stop
integer(I4B), pointer :: istopzone => null() !< optional stop zone number: 0 = no stop zone
integer(I4B), pointer :: idrape => null() !< drape option: 0 = do not drape, 1 = drape to topmost active cell
integer(I4B), pointer :: idrymeth => null() !< dry tracking method: 0 = drop, 1 = stop, 2 = stay
integer(I4B), pointer :: itrkout => null() !< binary track file
integer(I4B), pointer :: itrkhdr => null() !< track header file
integer(I4B), pointer :: itrkcsv => null() !< CSV track file
Expand Down Expand Up @@ -89,6 +90,7 @@ module PrtPrpModule
procedure :: release
procedure :: log_release
procedure :: validate_release_point
procedure :: initialize_particle
procedure, public :: bnd_obs_supported => prp_obs_supported
procedure, public :: bnd_df_obs => prp_df_obs
end type PrtPrpType
Expand Down Expand Up @@ -158,6 +160,7 @@ subroutine prp_da(this)
call mem_deallocate(this%istopweaksink)
call mem_deallocate(this%istopzone)
call mem_deallocate(this%idrape)
call mem_deallocate(this%idrymeth)
call mem_deallocate(this%nreleasepoints)
call mem_deallocate(this%nreleasetimes)
call mem_deallocate(this%nparticles)
Expand Down Expand Up @@ -247,6 +250,7 @@ subroutine prp_allocate_scalars(this)
call mem_allocate(this%istopweaksink, 'ISTOPWEAKSINK', this%memoryPath)
call mem_allocate(this%istopzone, 'ISTOPZONE', this%memoryPath)
call mem_allocate(this%idrape, 'IDRAPE', this%memoryPath)
call mem_allocate(this%idrymeth, 'IDRYMETH', this%memoryPath)
call mem_allocate(this%nreleasepoints, 'NRELEASEPOINTS', this%memoryPath)
call mem_allocate(this%nreleasetimes, 'NRELEASETIMES', this%memoryPath)
call mem_allocate(this%nparticles, 'NPARTICLES', this%memoryPath)
Expand All @@ -270,6 +274,7 @@ subroutine prp_allocate_scalars(this)
this%istopweaksink = 0
this%istopzone = 0
this%idrape = 0
this%idrymeth = 0
this%nreleasepoints = 0
this%nreleasetimes = 0
this%nparticles = 0
Expand Down Expand Up @@ -413,47 +418,52 @@ subroutine release(this, ip, trelease)
integer(I4B), intent(in) :: ip !< particle index
real(DP), intent(in) :: trelease !< release time
! local
integer(I4B) :: irow, icol, ilay, icpl
integer(I4B) :: ic, icu
integer(I4B) :: np
real(DP) :: x, y, z
real(DP) :: top, bot, hds
type(ParticleType), pointer :: particle

call this%initialize_particle(particle, ip, trelease)

! Increment cumulative particle count
np = this%nparticles + 1
this%nparticles = np

! Get reduced and user node numbers
ic = this%rptnode(ip)
icu = this%dis%get_nodeuser(ic)
! Save the particle to the store
call this%particles%save_particle(particle, np)
deallocate (particle)

! Load coordinates and transform if needed
x = this%rptx(ip)
y = this%rpty(ip)
if (this%ilocalz > 0) then
top = this%fmi%dis%top(ic)
bot = this%fmi%dis%bot(ic)
hds = this%fmi%gwfhead(ic)
z = bot + this%rptz(ip) * (hds - bot)
else
z = this%rptz(ip)
end if
! Accumulate mass for release point
this%rptm(ip) = this%rptm(ip) + DONE

! Make sure release point is in the grid/cell
call this%validate_release_point(ic, x, y, z)
end subroutine release

subroutine initialize_particle(this, particle, ip, trelease)
wpbonelli marked this conversation as resolved.
Show resolved Hide resolved
class(PrtPrpType), intent(inout) :: this !< this instance
type(ParticleType), pointer, intent(inout) :: particle !< the particle
integer(I4B), intent(in) :: ip !< particle index
real(DP), intent(in) :: trelease !< release time
! local
integer(I4B) :: irow, icol, ilay, icpl
integer(I4B) :: ic, icu, ic_old
real(DP) :: x, y, z
real(DP) :: top, bot, hds

ic = this%rptnode(ip)
icu = this%dis%get_nodeuser(ic)

! Initialize the particle
call create_particle(particle)

if (size(this%boundname) /= 0) then
particle%name = this%boundname(ip)
else
particle%name = ''
end if

particle%irpt = ip
particle%istopweaksink = this%istopweaksink
particle%istopzone = this%istopzone
particle%idrymeth = this%idrymeth
particle%icu = icu

select type (dis => this%dis)
type is (DisType)
call get_ijk(icu, dis%nrow, dis%ncol, dis%nlay, irow, icol, ilay)
Expand All @@ -462,28 +472,49 @@ subroutine release(this, ip, trelease)
end select
particle%ilay = ilay
particle%izone = this%rptzone(ic)

particle%istatus = 0
! Handle inactive cells
! If the cell is inactive, either drape the particle
! to the top-most active cell beneath it if drape is
! enabled, or else terminate permanently unreleased.
if (this%ibound(ic) == 0) then
! If drape option activated, release in highest active
! cell vertically below release point.
if (this%idrape /= 0) &
ic_old = ic
if (this%idrape > 0) then
call this%dis%highest_active(ic, this%ibound)
! If returned cell is inactive, do not release particle
if (this%ibound(ic) == 0) &
particle%istatus = 8 ! permanently unreleased
if (ic == ic_old .or. this%ibound(ic) == 0) &
particle%istatus = 8
else
particle%istatus = 8
end if
end if

! Load coordinates and transform if needed
x = this%rptx(ip)
y = this%rpty(ip)
if (this%ilocalz > 0) then
top = this%fmi%dis%top(ic)
bot = this%fmi%dis%bot(ic)
hds = this%fmi%gwfhead(ic)
z = bot + this%rptz(ip) * (hds - bot)
else
z = this%rptz(ip)
end if

call this%validate_release_point(ic, x, y, z)

particle%x = x
particle%y = y
particle%z = z
particle%trelease = trelease
! Set stopping time to earlier of times specified by STOPTIME and STOPTRAVELTIME

! Set stop time to earlier of STOPTIME and STOPTRAVELTIME
if (this%stoptraveltime == huge(1d0)) then
particle%tstop = this%stoptime
else
particle%tstop = particle%trelease + this%stoptraveltime
if (this%stoptime < particle%tstop) particle%tstop = this%stoptime
end if

particle%ttrack = particle%trelease
particle%idomain(1) = 0
particle%iboundary(1) = 0
Expand All @@ -495,15 +526,7 @@ subroutine release(this, ip, trelease)
particle%iexmeth = this%iexmeth
particle%iextend = this%iextend
particle%extol = this%extol

! Store the particle's state and deallocate it
call this%particles%save_particle(particle, np)
deallocate (particle)

! Accumulate particle mass for release point
this%rptm(ip) = this%rptm(ip) + DONE

end subroutine release
end subroutine initialize_particle

!> @ brief Read and prepare period data for particle input
subroutine prp_rp(this)
Expand Down Expand Up @@ -691,6 +714,25 @@ subroutine prp_options(this, option, found)
case ('DRAPE')
this%idrape = 1
found = .true.
case ('DRY_TRACKING_METHOD')
call this%parser%GetStringCaps(keyword)
select case (keyword)
case ('DROP')
this%idrymeth = 0
case ('STOP')
this%idrymeth = 1
case ('STAY')
this%idrymeth = 2
case default
write (errmsg, '(a, a)') &
'Unknown dry tracking method: ', trim(keyword)
call store_error(errmsg)
write (errmsg, '(a, a)') &
'DRY must be "DROP", "STOP" or "STAY"'
call store_error(errmsg)
call this%parser%StoreErrorUnit()
end select
found = .true.
case ('TRACK')
call this%parser%GetStringCaps(keyword)
if (keyword == 'FILEOUT') then
Expand Down Expand Up @@ -891,7 +933,7 @@ subroutine prp_read_packagedata(this)

if (n < 1 .or. n > this%nreleasepoints) then
write (errmsg, '(a,1x,i0,a)') &
'Release point number must be greater than 0 and less than ', &
'Release point number must be greater than 0 and less than', &
'or equal to', this%nreleasepoints, '.'
call store_error(errmsg)
cycle
Expand Down
Loading
Loading