Skip to content

Commit 0eba4ba

Browse files
authored
fix(binaryfile): fix head/budget file reversal (#2483)
Following up on #2475. Fixes #2473. #2480 was extracted from this changeset for tidiness. Up to now the reversal methods have worked for our backwards particle tracking examples, which have very simple time discretizations. But they didn't work in the general case — only for "symmetric" tdis with no time step multiplier. Fix several issues with binary output file reversal. Clean up and expand tests to include dis/disv/disu with both trivial and a couple non-trivial time discretizations. Make a small (but consequential) fix for #2481: when reversing we need to take the reciprocal of tsmult. Fix an issue with the ModelTime class perioddata property and add a pertim property like the totim property.
1 parent 48f46fb commit 0eba4ba

File tree

7 files changed

+362
-281
lines changed

7 files changed

+362
-281
lines changed

autotest/test_binaryfile.py

+2-212
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@
44
"""
55

66
from itertools import repeat
7-
from pathlib import Path
8-
from pprint import pformat
97

108
import numpy as np
119
import pandas as pd
1210
import pytest
1311
from matplotlib import pyplot as plt
1412
from matplotlib.axes import Axes
15-
from modflow_devtools.markers import requires_exe, requires_pkg
13+
from modflow_devtools.markers import requires_exe
1614

1715
import flopy
18-
from flopy.discretization.modeltime import ModelTime
19-
from flopy.modflow import Modflow
2016
from flopy.utils import (
2117
BinaryHeader,
2218
CellBudgetFile,
@@ -25,10 +21,7 @@
2521
UcnFile,
2622
Util2d,
2723
)
28-
from flopy.utils.binaryfile import (
29-
get_headfile_precision,
30-
)
31-
from flopy.utils.gridutil import get_disv_kwargs, uniform_flow_field
24+
from flopy.utils.binaryfile import get_headfile_precision
3225

3326

3427
@pytest.fixture
@@ -429,209 +422,6 @@ def test_binaryfile_read_context(freyberg_model_path):
429422
assert str(e.value) == "seek of closed file", str(e.value)
430423

431424

432-
def test_binaryfile_reverse_mf6_dis_symmetric(function_tmpdir):
433-
name = "reverse_dis"
434-
sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=function_tmpdir, exe_name="mf6")
435-
tdis_rc = [(1, 1, 1.0), (1, 1, 1.0), (1, 1, 1.0)]
436-
nper = len(tdis_rc)
437-
tdis = flopy.mf6.ModflowTdis(sim, nper=nper, perioddata=tdis_rc)
438-
ims = flopy.mf6.ModflowIms(sim)
439-
gwf = flopy.mf6.ModflowGwf(sim, modelname=name, save_flows=True)
440-
dis = flopy.mf6.ModflowGwfdis(gwf, nrow=10, ncol=10)
441-
dis = gwf.get_package("DIS")
442-
nlay = 2
443-
botm = [1 - (k + 1) for k in range(nlay)]
444-
botm_data = np.array([list(repeat(b, 10 * 10)) for b in botm]).reshape(
445-
(nlay, 10, 10)
446-
)
447-
dis.nlay = nlay
448-
dis.botm.set_data(botm_data)
449-
ic = flopy.mf6.ModflowGwfic(gwf)
450-
npf = flopy.mf6.ModflowGwfnpf(gwf, save_specific_discharge=True)
451-
chd = flopy.mf6.ModflowGwfchd(
452-
gwf, stress_period_data=[[(0, 0, 0), 1.0], [(0, 9, 9), 0.0]]
453-
)
454-
budget_file = name + ".bud"
455-
head_file = name + ".hds"
456-
oc = flopy.mf6.ModflowGwfoc(
457-
gwf,
458-
budget_filerecord=budget_file,
459-
head_filerecord=head_file,
460-
saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")],
461-
)
462-
463-
sim.write_simulation(silent=True)
464-
success, buff = sim.run_simulation(silent=True, report=True)
465-
assert success, pformat(buff)
466-
467-
head_file = flopy.utils.HeadFile(function_tmpdir / head_file)
468-
orig_heads = head_file.get_alldata()
469-
orig_times = head_file.get_times()
470-
orig_kstpkper = head_file.get_kstpkper()
471-
head_file.reverse()
472-
rev_heads = head_file.get_alldata()
473-
rev_times = head_file.get_times()
474-
rev_kstpkper = head_file.get_kstpkper()
475-
exp_head_shape = (nper, 2, 10, 10)
476-
assert orig_times == rev_times
477-
assert orig_heads.shape == exp_head_shape
478-
assert rev_heads.shape == (nper, 2, 10, 10)
479-
assert orig_kstpkper == rev_kstpkper
480-
481-
budget_file_rev_path = function_tmpdir / f"{budget_file}_rev"
482-
budget_file = flopy.utils.CellBudgetFile(function_tmpdir / budget_file)
483-
budget_file.reverse(budget_file_rev_path)
484-
budget_file_rev = flopy.utils.CellBudgetFile(budget_file_rev_path)
485-
assert budget_file.get_kstpkper() == orig_kstpkper
486-
assert budget_file_rev.get_kstpkper() == rev_kstpkper
487-
488-
budtxt = "FLOW-JA-FACE"
489-
tmax = orig_times[-1]
490-
perlen = tdis.perioddata.get_data().perlen
491-
492-
for (_, kper), t in zip(orig_kstpkper, orig_times):
493-
assert np.allclose(orig_heads[kper], rev_heads[-(kper + 1)])
494-
budget = budget_file.get_data(text=budtxt, totim=t)[0]
495-
budget_rev = budget_file_rev.get_data(
496-
text=budtxt, totim=tmax - t + perlen[kper]
497-
)[0]
498-
assert budget.shape == budget_rev.shape
499-
assert np.allclose(budget, -budget_rev)
500-
501-
502-
@requires_pkg("shapely")
503-
def test_binaryfile_reverse_mf6_disv_symmetric(function_tmpdir):
504-
name = "reverse_disv"
505-
sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=function_tmpdir, exe_name="mf6")
506-
tdis_rc = [(1, 1, 1.0), (1, 1, 1.0), (1, 1, 1.0)]
507-
nper = len(tdis_rc)
508-
tdis = flopy.mf6.ModflowTdis(sim, nper=nper, perioddata=tdis_rc)
509-
ims = flopy.mf6.ModflowIms(sim)
510-
gwf = flopy.mf6.ModflowGwf(sim, modelname=name, save_flows=True)
511-
dis = flopy.mf6.ModflowGwfdisv(
512-
gwf, **get_disv_kwargs(2, 10, 10, 1.0, 1.0, 25.0, [20.0, 15.0])
513-
)
514-
ic = flopy.mf6.ModflowGwfic(gwf)
515-
npf = flopy.mf6.ModflowGwfnpf(gwf, save_specific_discharge=True)
516-
chd = flopy.mf6.ModflowGwfchd(
517-
gwf, stress_period_data=[[(0, 0, 0), 1.0], [(0, 9, 9), 0.0]]
518-
)
519-
budget_file = name + ".bud"
520-
head_file = name + ".hds"
521-
oc = flopy.mf6.ModflowGwfoc(
522-
gwf,
523-
budget_filerecord=budget_file,
524-
head_filerecord=head_file,
525-
saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")],
526-
)
527-
528-
sim.write_simulation(silent=True)
529-
success, buff = sim.run_simulation(silent=True)
530-
assert success, pformat(buff)
531-
532-
head_file = flopy.utils.HeadFile(function_tmpdir / head_file)
533-
orig_heads = head_file.get_alldata()
534-
orig_times = head_file.get_times()
535-
orig_kstpkper = head_file.get_kstpkper()
536-
head_file.reverse()
537-
rev_heads = head_file.get_alldata()
538-
rev_times = head_file.get_times()
539-
rev_kstpkper = head_file.get_kstpkper()
540-
exp_shape = (nper, 2, 1, 100)
541-
assert orig_times == rev_times
542-
assert orig_heads.shape == exp_shape
543-
assert rev_heads.shape == exp_shape
544-
assert orig_kstpkper == rev_kstpkper
545-
546-
budget_file_rev_path = function_tmpdir / f"{budget_file}_rev"
547-
budget_file = flopy.utils.CellBudgetFile(function_tmpdir / budget_file)
548-
budget_file.reverse(budget_file_rev_path)
549-
budget_file_rev = flopy.utils.CellBudgetFile(budget_file_rev_path, tdis=tdis)
550-
assert budget_file.get_kstpkper() == orig_kstpkper
551-
assert budget_file_rev.get_kstpkper() == rev_kstpkper
552-
553-
budtxt = "FLOW-JA-FACE"
554-
tmax = orig_times[-1]
555-
perlen = tdis.perioddata.get_data().perlen
556-
557-
for (_, kper), t in zip(orig_kstpkper, budget_file.get_times()):
558-
assert np.allclose(orig_heads[kper], rev_heads[-(kper + 1)])
559-
budget = budget_file.get_data(text=budtxt, totim=t)[0]
560-
budget_rev = budget_file_rev.get_data(
561-
text=budtxt, totim=tmax - t + perlen[kper]
562-
)[0]
563-
assert budget.shape == budget_rev.shape
564-
assert np.allclose(budget, -budget_rev)
565-
566-
567-
def test_binaryfile_reverse_mf6_disu_symmetric(example_data_path, function_tmpdir):
568-
sim_name = "test006_gwf3"
569-
sim = flopy.mf6.MFSimulation.load(
570-
sim_name=sim_name, sim_ws=example_data_path / "mf6" / sim_name
571-
)
572-
tdis_rc = [(1, 1, 1.0), (1, 1, 1.0), (1, 1, 1.0)]
573-
nper = len(tdis_rc)
574-
tdis = flopy.mf6.ModflowTdis(sim, time_units="DAYS", nper=nper, perioddata=tdis_rc)
575-
sim.set_sim_path(function_tmpdir)
576-
sim.write_simulation()
577-
sim.run_simulation()
578-
579-
file_path = function_tmpdir / "flow.hds"
580-
head_file = HeadFile(file_path, tdis=tdis)
581-
orig_heads = head_file.get_alldata()
582-
orig_times = head_file.get_times()
583-
orig_kstpkper = head_file.get_kstpkper()
584-
585-
head_file_rev_path = function_tmpdir / "flow_rev.hds"
586-
head_file.reverse(filename=head_file_rev_path)
587-
head_file_rev = HeadFile(head_file_rev_path)
588-
rev_heads = head_file_rev.get_alldata()
589-
rev_times = head_file_rev.get_times()
590-
rev_kstpkper = head_file_rev.get_kstpkper()
591-
assert orig_times == rev_times
592-
assert orig_kstpkper == rev_kstpkper
593-
594-
file_path = function_tmpdir / "flow.cbc"
595-
budget_file = CellBudgetFile(file_path, tdis=tdis)
596-
budget_file_rev_path = function_tmpdir / "flow_rev.cbc"
597-
budget_file.reverse(filename=budget_file_rev_path)
598-
budget_file_rev = CellBudgetFile(budget_file_rev_path)
599-
assert budget_file.get_kstpkper() == orig_kstpkper
600-
assert budget_file_rev.get_kstpkper() == rev_kstpkper
601-
assert head_file.get_alldata().shape == (nper, 1, 1, 121)
602-
assert head_file_rev.get_alldata().shape == (nper, 1, 1, 121)
603-
assert len(head_file) == nper
604-
assert len(head_file_rev) == nper
605-
assert len(budget_file) == nper * 2
606-
assert len(budget_file_rev) == nper * 2
607-
608-
nrecords = len(head_file)
609-
for idx in range(nrecords - 1, -1, -1):
610-
f_data = head_file.get_data(idx=idx)[0]
611-
rf_data = head_file_rev.get_data(idx=nrecords - idx - 1)[0]
612-
assert f_data.shape == rf_data.shape
613-
if f_data.ndim == 1:
614-
for row in range(len(f_data)):
615-
f_datum = f_data[row]
616-
rf_datum = rf_data[row]
617-
assert f_datum == rf_datum
618-
else:
619-
assert np.array_equal(f_data[0][0], rf_data[0][0])
620-
621-
budtxt = "FLOW-JA-FACE"
622-
tmax = orig_times[-1]
623-
perlen = tdis.perioddata.get_data().perlen
624-
625-
for (_, kper), t in zip(orig_kstpkper, budget_file.get_times()):
626-
assert np.allclose(orig_heads[kper], rev_heads[-(kper + 1)])
627-
budget = budget_file.get_data(text=budtxt, totim=t)[0]
628-
budget_rev = budget_file_rev.get_data(
629-
text=budtxt, totim=tmax - t + perlen[kper]
630-
)[0]
631-
assert budget.shape == budget_rev.shape
632-
assert np.allclose(budget, -budget_rev)
633-
634-
635425
@pytest.fixture
636426
@pytest.mark.mf6
637427
@requires_exe("mf6")

0 commit comments

Comments
 (0)