Skip to content

Commit 01b3f4c

Browse files
committed
fix(prt): fix drape behavior, simplify localz calculation
1 parent a6a4984 commit 01b3f4c

5 files changed

Lines changed: 618 additions & 38 deletions

File tree

-560 Bytes
Binary file not shown.
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
▗ ▗ ▖ ▖ Claude Code v2.1.139
2+
Sonnet 4.6 · Claude Pro
3+
▘▘ ▝▝ ~/dev/modflow6/autotest
4+
5+
✻ Conversation compacted (ctrl+o for history)
6+
7+
8+
❯ /compact
9+
⎿ Compacted (ctrl+o to see full summary)
10+
⎿  Read test_prt_subcell_cycle.py (314 lines)
11+
⎿  Read temp/synthetic_valley/run_prt_debug.py (198 lines)
12+
⎿  Read ../src/Solution/ParticleTracker/Method/MethodSubcell.f90 (79 lines)
13+
⎿  Read ../src/Solution/ParticleTracker/Method/MethodCell.f90 (349 lines)
14+
15+
❯ Ok, good. The plot works. But the test still passes. How could this be? What
16+
did we do differently than in the Synthetic Valley example? Would the choice
17+
of WEL instead of SFR matter? I doubt it, but you tell me. I figure we may
18+
need to closely compare the original example with our new test case to see
19+
what we're missing.
20+
21+
⏺ Good question. WEL vs. SFR almost certainly doesn't matter — both are
22+
distributed sinks that PRT treats the same way through the FMI. Let me look at
23+
the ternary method and cell defn code to understand what's really different.
24+
25+
Searched for 2 patterns, read 2 files (ctrl+o to expand)
26+
27+
⏺ Now let me find where faceflow is populated for PRT cells, and what
28+
inoexitface depends on:
29+
30+
Searched for 1 pattern (ctrl+o to expand)
31+
32+
⏺ Bash(find /Users/wbonelli/dev/modflow6/src/Solution/ParticleTracker -name
33+
"*.f90" | xargs grep -l "faceflow\|inoexitface\|distflow" | head -10)
34+
⎿  /Users/wbonelli/dev/modflow6/src/Solution/ParticleTracker/Domain/CellD
35+
efn.f90
36+
/Users/wbonelli/dev/modflow6/src/Solution/ParticleTracker/Domain/CellR
37+
… +15 lines (ctrl+o to expand)
38+
39+
Read 1 file (ctrl+o to expand)
40+
41+
⏺ Now I need to see how inoexitface and faceflow are set for DISV cells:
42+
43+
Read 1 file (ctrl+o to expand)
44+
45+
⏺ Now let me find load_cell_no_exit_face which sets inoexitface:
46+
47+
Searched for 1 pattern, read 1 file (ctrl+o to expand)
48+
49+
⏺ Let me look at PrtFmiModule to understand how BoundaryFlows vs SinkFlows are
50+
assigned:
51+
52+
Searched for 1 pattern (ctrl+o to expand)
53+
54+
⏺ Bash(find /Users/wbonelli/dev/modflow6/src -name "*.f90" | xargs grep -l
55+
"BoundaryFlows\|SinkFlows\|SourceFlows" 2>/dev/null | head -10)
56+
⎿  /Users/wbonelli/dev/modflow6/src/Solution/ParticleTracker/Method/Metho
57+
dDisv.f90
58+
/Users/wbonelli/dev/modflow6/src/Solution/ParticleTracker/Method/Metho
59+
… +2 lines (ctrl+o to expand)
60+
61+
Read 1 file (ctrl+o to expand)
62+
63+
⏺ Now I have the full picture. Let me check if there's a tracking CSV from a
64+
recent run, and also look at the particle starting position logic:
65+
66+
Searched for 1 pattern, read 1 file (ctrl+o to expand)
67+
68+
⏺ The particle terminates immediately (barely moves). Let me check status codes
69+
and look for the actual issue:
70+
71+
Searched for 3 patterns (ctrl+o to expand)
72+
73+
⏺ Now I have the full picture. Let me read the actual face flows from the CBC
74+
file to confirm what the GWF is producing:
75+
76+
Listed 1 directory (ctrl+o to expand)
77+
78+
⏺ Bash(cd "/private/var/folders/r9/yy7jb_8x0_s7zdkp6flb42980000gp/T/pytest-of-wb
79+
onelli/pytest-79/test_mf6model_0-prtscyc_0/" && python3 -c "
80+
import flopy…)
81+
⎿  Error: Exit code 1
82+
Traceback (most recent call last):
83+
File "<string>", line 2, in <module>
84+
ModuleNotFoundError: No module named 'flopy' ⎿  Interrupted · What should Claude do instead?
85+
86+
❯ Use the pixi `rtd` env
87+
88+
⏺ Bash(cd "/private/var/folders/r9/yy7jb_8x0_s7zdkp6flb42980000gp/T/pytest-of-wb
89+
onelli/pytest-79/test_mf6model_0-prtscyc_0/" && pixi run -e rtd python3 -c
90+
"
91+
import flo…)
92+
⎿  Error: Exit code 1
93+
Error: × could not find pixi.toml or pyproject.toml with tool.pixi at
94+
directory /
95+
│ private/var/folders/r9/yy7jb_8x0_s7zdkp6flb42980000gp/T/pytest-of-
96+
│ wbonelli/pytest-79/test_mf6model_0-prtscyc_0
97+
⎿  Interrupted · What should Claude do instead?
98+
99+
❯ you'll have to specify --manifest-path if you're running it from the temp dir
100+
101+
⏺ Bash(cd "/private/var/folders/r9/yy7jb_8x0_s7zdkp6flb42980000gp/T/pytest-of-wb
102+
onelli/pytest-79/test_mf6model_0-prtscyc_0/" && pixi run --manifest-path
103+
/Users/wbonell…)
104+
⎿  Cell 0 face flows (positive=inflow to cell 0):
105+
neighbor cell 1: q = 0.02370000 m3/day
106+
neighbor cell 2: q = -0.00100000 m3/day
107+
… +6 lines (ctrl+o to expand)
108+
⎿  Shell cwd was reset to /Users/wbonelli/dev/modflow6
109+
110+
⏺ Good — the face flows exactly match FACE_Q. So inoexitface = 0, the ternary
111+
method IS invoked. The problem is now clear from the tracking output.
112+
113+
The particle terminates with istatus=9 = TERM_NO_EXITS_SUB — that's a
114+
subcell-level no-exit, not a cell-level one. The particle is released at (0,
115+
0), which is the geometric centroid — vertex 2 of every ternary subcell
116+
simultaneously. The code assigns it to subcell 1 (the first one satisfying the
117+
barycentric condition). But it starts there at exactly gamma=1 (the centroid
118+
vertex), which is a degenerate position: the ternary subcell solver can't
119+
compute a valid exit direction from a corner vertex, so it immediately fires
120+
TERM_NO_EXITS_SUB. The particle never gets near subcell 2 — the one with the
121+
tiny outflow face — at all.
122+
123+
WEL vs SFR is not the issue. Confirmed in code: WEL (no IFLOWFACE) goes to
124+
SinkFlows → distflow, which is stored on CellDefnType but is never used in
125+
vertvelo(). divcell is computed purely from faceflow (lateral GWF-GWF flows),
126+
so the convergent field from the five inflow faces is fully in play. That part
127+
of the setup is correct.
128+
129+
What differs from Synthetic Valley: particles there enter cell 798 through an
130+
inflow face from a neighboring cell — a non-degenerate interior point. They
131+
then travel through the cell before hitting the oscillation zone near face 1.
132+
Our release at the centroid vertex short-circuits all of that.
133+
134+
Fix: release the particle inside subcell 2 (VERTS[1]→VERTS[2]→centroid), the
135+
one whose exterior is the tiny outflow face. The centroid of that triangle is
136+
a safe, non-degenerate starting point:
137+
138+
Read 1 file (ctrl+o to expand)
139+
140+
⏺ Update(test_prt_subcell_cycle.py)
141+
⎿  Added 7 lines, removed 4 lines
142+
183 )
143+
184 flopy.mf6.ModflowPrtmip(prt, pname="mip", porosity=POROSIT
144+
Y)
145+
185
146+
186 - # Release at the centroid of the central cell. The converg
147+
-ent lateral
148+
187 - # velocity field drives the particle toward the short-face
149+
- subcells
150+
188 - # where the oscillation occurs.
151+
186 + # Release inside subcell 1 (VERTS[1]->VERTS[2]->centroid),
152+
+ the subcell
153+
187 + # whose exterior is the tiny outflow face. Starting at the
154+
+ cell centroid
155+
188 + # (shared vertex of all subcells) is degenerate for the te
156+
+rnary solver
157+
189 + # and causes immediate TERM_NO_EXITS_SUB. Use the triangle
158+
+ centroid instead.
159+
190 z = 0.5 * (TOP + BOTM)
160+
190 - prpdata = [(0, (0, 0), 0.0, 0.0, z)]
161+
191 + sc1_cx = (VERTS[1][0] + VERTS[2][0] + 0.0) / 3.0
162+
192 + sc1_cy = (VERTS[1][1] + VERTS[2][1] + 0.0) / 3.0
163+
193 + prpdata = [(0, (0, 0), sc1_cx, sc1_cy, z)]
164+
194 prt_csv = f"{prtname}.trk.csv"
165+
195 flopy.mf6.ModflowPrtprp(
166+
196 prt,
167+
168+
⏺ Now let's run the test and watch for the cycle detection message:
169+
170+
⏺ Bash(cd /Users/wbonelli/dev/modflow6/autotest && pixi run --manifest-path
171+
/Users/wbonelli/dev/modflow6/pixi.toml -e rtd pytest
172+
test_prt_subcell_cycle.py -v -s 2>&1 |…)
173+
⎿  by the USGS or the U.S. Government as to the functionality of the
174+
software and related material nor shall the fact of release
175+
constitute any such warranty. The software is provided on the
176+
… +23 lines (ctrl+o to expand)
177+
⎿  (timeout 2m)
178+
179+
⏺ Still passing cleanly. Let me check the new tracking CSV to see what subcells
180+
the particle visits:
181+
182+
⏺ Bash(LATEST=$(find /private/var/folders/r9/-name "prtscyc_prt.trk.csv" -newer
183+
/private/var/folders/r9/yy7jb_8x0_s7zdkp6flb42980000gp/T/pytest-of-wbonell
184+
i/pytest-79/t…)
185+
⎿  Error: Exit code 1
186+
187+
cat: : No such file or directory
188+
189+
Searched for 1 pattern, read 1 file (ctrl+o to expand)
190+
191+
⏺ Progress — the particle now travels for 323 days before terminating with
192+
TERM_NO_EXITS_SUB inside subcell 2, near face 1. But with FACE_Q[1] = -0.001,
193+
the outflow there is so tiny (70× smaller than the SV-scaled value) that the
194+
convergent field overwhelms it, creating a velocity sink inside the subcell
195+
instead of a spoke-exit oscillation.
196+
197+
Let me restore the original SV-extracted value and keep the new release point:
198+
199+
⏺ Update(test_prt_subcell_cycle.py)
200+
⎿  Added 1 line, removed 1 line
201+
46 # synthetic_valley model (layer 0), scaled by 0.1 for this mini
202+
-model.
203+
47 # Face indices correspond to edges VERTS[i]→VERTS[(i+1)%6].
204+
48 # Face 1 (v1→v2, short upper-right edge) is the only exit (outf
205+
low).
206+
49 -FACE_Q = np.array([0.0237, -0.001, 0.1280, 1.3583, 0.0768, 0.96
207+
-73])
208+
49 +FACE_Q = np.array([0.0237, -0.0703, 0.1280, 1.3583, 0.0768, 0.9
209+
+673])
210+
50
211+
51
212+
52 def _outward_normal(vi, vj):
213+
214+
⏺ Bash(pixi run --manifest-path /Users/wbonelli/dev/modflow6/pixi.toml -e rtd
215+
pytest test_prt_subcell_cycle.py -v -s 2>&1 | grep -E
216+
"PASSED|FAILED|ERROR|Cyclic|subcell…)
217+
⎿  test_prt_subcell_cycle.py::test_mf6model[0-prtscyc] writing simulation
218+
...
219+
Normal termination of simulation.
220+
… +3 lines (ctrl+o to expand)
221+
222+
⏺ Bash(find /private/var/folders/r9/yy7jb_8x0_s7zdkp6flb42980000gp/T/pytest-of-w
223+
bonelli/ -name "prtscyc_prt.trk.csv" | sort -t- -k2 -n | tail -1 | xargs
224+
cat)
225+
⎿  kper,kstp,imdl,iprp,irpt,ilay,icell,izone,istatus,ireason,trelease,t,x
226+
,y,z,name
227+
1,1,1,1,1,1,1,0,1,0,0.0000000000000000,0.0000000000000000,13.835446670
228+
… +7 lines (ctrl+o to expand)
229+
230+
⏺ Good progress. The particle now travels for 172 days and exits cleanly through
231+
face 1. The oscillation sweet spot requires FACE_Q[1] to be small enough that
232+
the spoke velocity wins over the face-1 exit velocity, but not so small it
233+
creates a velocity sink. Let me try a few intermediate values to locate that
234+
regime:
235+
236+
⏺ Update(test_prt_subcell_cycle.py)
237+
⎿  (No output)
238+
⎿  Interrupted · What should Claude do instead?

autotest/test_prt_drape.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,8 @@ def check_output(idx, test):
276276
)
277277

278278
if drape:
279-
assert mf6_pls.shape[0] == 36
279+
assert mf6_pls.shape[0] == 27
280+
assert mf6_pls[mf6_pls.ireason == 6].empty
280281
else:
281282
# expect no movement without drape
282283
assert mf6_pls.shape[0] == 9

0 commit comments

Comments
 (0)