Skip to content

Commit a11dad5

Browse files
committed
add attributes to simvals and x, add test
1 parent 97312c1 commit a11dad5

3 files changed

Lines changed: 93 additions & 2 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Test that the memory manager's readonly/output variable attributes are
3+
enforced uniformly through the BMI, not just by convention:
4+
5+
- get_input_var_names()/get_output_var_names() are filtered on these
6+
attributes rather than dumping the whole memory store.
7+
- a readonly variable (WEL's SIMVALS) cannot be written via set_value(),
8+
regardless of which BMI entry point is used.
9+
- a variable that is both writable and output (the model's X/head array)
10+
can legitimately appear in both lists and be written.
11+
"""
12+
13+
import flopy
14+
import numpy as np
15+
import pytest
16+
from modflow_devtools.markers import requires_pkg
17+
18+
name = "bmiattrs"
19+
nlay, nrow, ncol = 1, 1, 5
20+
delr = delc = 1.0
21+
top = 1.0
22+
botm = [0.0]
23+
k = 1.0
24+
strt = 10.0
25+
chd_head = 10.0
26+
wellq = -1.0
27+
28+
29+
@pytest.fixture
30+
def simple_sim(tmp_path):
31+
"""A trivial, linear, steady-state 1D column: CHD on one end, WEL on the other."""
32+
sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=str(tmp_path))
33+
flopy.mf6.ModflowTdis(sim, nper=1, perioddata=[(1.0, 1, 1.0)])
34+
flopy.mf6.ModflowIms(sim, outer_dvclose=1e-8, inner_dvclose=1e-8)
35+
gwf = flopy.mf6.ModflowGwf(sim, modelname=name)
36+
flopy.mf6.ModflowGwfdis(
37+
gwf, nlay=nlay, nrow=nrow, ncol=ncol, delr=delr, delc=delc, top=top, botm=botm
38+
)
39+
flopy.mf6.ModflowGwfic(gwf, strt=strt)
40+
flopy.mf6.ModflowGwfnpf(gwf, icelltype=0, k=k)
41+
flopy.mf6.ModflowGwfchd(gwf, stress_period_data=[[(0, 0, 0), chd_head]])
42+
flopy.mf6.ModflowGwfwel(
43+
gwf, pname="WEL_0", stress_period_data=[[(0, 0, ncol - 1), wellq]]
44+
)
45+
sim.write_simulation()
46+
return sim
47+
48+
49+
@requires_pkg("xmipy")
50+
def test_var_attributes(simple_sim, targets):
51+
from xmipy import XmiWrapper
52+
from xmipy.errors import XMIError
53+
54+
sim = simple_sim
55+
mf6 = XmiWrapper(lib_path=targets["libmf6"], working_directory=sim.sim_path)
56+
mf6.initialize()
57+
58+
simvals_addr = mf6.get_var_address("SIMVALS", name, "WEL_0")
59+
x_addr = mf6.get_var_address("X", name)
60+
61+
input_vars = mf6.get_input_var_names()
62+
output_vars = mf6.get_output_var_names()
63+
64+
# SIMVALS is readonly + output: input-excluded, output-included
65+
assert simvals_addr not in input_vars
66+
assert simvals_addr in output_vars
67+
68+
# X is writable + output: appears in both
69+
assert x_addr in input_vars
70+
assert x_addr in output_vars
71+
72+
# writing SIMVALS should be rejected uniformly, regardless of entry point
73+
simvals = mf6.get_value_ptr(simvals_addr)
74+
with pytest.raises(XMIError, match="read-only"):
75+
mf6.set_value(simvals_addr, np.zeros_like(simvals))
76+
77+
# writing X should succeed, since it's not readonly
78+
x = mf6.get_value_ptr(x_addr)
79+
new_x = np.full_like(x, 5.0)
80+
mf6.set_value(x_addr, new_x)
81+
np.testing.assert_array_equal(mf6.get_value_ptr(x_addr), new_x)
82+
83+
mf6.finalize()

src/Model/ModelUtilities/BoundaryPackage.f90

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,7 @@ end subroutine allocate_scalars
10041004
!<
10051005
subroutine allocate_arrays(this, nodelist, auxvar)
10061006
! -- modules
1007-
use MemoryManagerModule, only: mem_allocate, mem_setptr
1007+
use MemoryManagerModule, only: mem_allocate, mem_setptr, mem_set_attributes
10081008
! -- dummy
10091009
class(BndType) :: this !< BndType object
10101010
integer(I4B), dimension(:), pointer, contiguous, optional :: nodelist !< package nodelist
@@ -1044,6 +1044,10 @@ subroutine allocate_arrays(this, nodelist, auxvar)
10441044
!
10451045
! -- Allocate the simvals array
10461046
call mem_allocate(this%simvals, this%maxbound, 'SIMVALS', this%memoryPath)
1047+
! -- simvals is a computed, per-timestep budget term: not user-writable,
1048+
! and it's the canonical example of a pure output variable
1049+
call mem_set_attributes('SIMVALS', this%memoryPath, readonly=.true., &
1050+
output=.true.)
10471051
if (this%imover == 1) then
10481052
call mem_allocate(this%simtomvr, this%maxbound, 'SIMTOMVR', &
10491053
this%memoryPath)

src/Model/NumericalModel.f90

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ subroutine allocate_arrays(this)
294294
end subroutine allocate_arrays
295295

296296
subroutine set_xptr(this, xsln, sln_offset, varNameTgt, memPathTgt)
297-
use MemoryManagerModule, only: mem_checkin
297+
use MemoryManagerModule, only: mem_checkin, mem_set_attributes
298298
! dummy
299299
class(NumericalModelType) :: this
300300
real(DP), dimension(:), pointer, contiguous, intent(in) :: xsln
@@ -307,6 +307,10 @@ subroutine set_xptr(this, xsln, sln_offset, varNameTgt, memPathTgt)
307307
offset = this%moffset - sln_offset
308308
this%x => xsln(offset + 1:offset + this%neq)
309309
call mem_checkin(this%x, 'X', this%memoryPath, varNameTgt, memPathTgt)
310+
! X is the model's primary dependent-variable output, but unlike SIMVALS
311+
! it's also a legitimate write target (e.g. coupled-model workflows write
312+
! it directly) - output, but not readonly
313+
call mem_set_attributes('X', this%memoryPath, output=.true.)
310314
end subroutine set_xptr
311315

312316
subroutine set_rhsptr(this, rhssln, sln_offset, varNameTgt, memPathTgt)

0 commit comments

Comments
 (0)