Skip to content

Commit 1db60ef

Browse files
author
wpbonelli
committed
fix maxbound
1 parent a75bfdf commit 1db60ef

File tree

9 files changed

+228
-157
lines changed

9 files changed

+228
-157
lines changed

flopy4/mf6/converter.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
from pathlib import Path
33
from typing import Any
44

5+
import numpy as np
6+
import xarray as xr
57
import xattree
68
from cattrs import Converter
79

810
from flopy4.mf6.component import Component
11+
from flopy4.mf6.constants import FILL_DNODATA
912
from flopy4.mf6.spec import get_blocks
1013

1114

flopy4/mf6/filters.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@ def field_type(value: Any) -> str:
6666
"""
6767
if isinstance(value, bool):
6868
return "keyword"
69-
if isinstance(value, (int, float)):
70-
return "scalar"
69+
if isinstance(value, int):
70+
return "integer"
71+
if isinstance(value, float):
72+
return "double precision"
7173
if isinstance(value, str):
7274
return "string"
7375
if isinstance(value, (dict, tuple)):

flopy4/mf6/gwf/chd.py

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import ClassVar, Optional
33

44
import numpy as np
5-
from attrs import Converter
5+
from attrs import Converter, setters
66
from numpy.typing import NDArray
77
from xattree import xattree
88

@@ -12,6 +12,43 @@
1212
from flopy4.mf6.spec import array, field
1313

1414

15+
def _update_maxbound(instance, attribute, new_value):
16+
"""Update maxbound when period block arrays change."""
17+
if hasattr(instance, '_updating_maxbound'):
18+
return new_value
19+
20+
# Calculate maxbound from all relevant arrays
21+
maxbound_values = []
22+
23+
# Check head array
24+
head_val = new_value if attribute and attribute.name == 'head' else getattr(instance, 'head', None)
25+
if head_val is not None:
26+
head = head_val if head_val.data.shape == head_val.shape else head_val.todense()
27+
maxbound_values.append(len(np.where(head != FILL_DNODATA)[0]))
28+
29+
# Check aux array
30+
aux_val = new_value if attribute and attribute.name == 'aux' else getattr(instance, 'aux', None)
31+
if aux_val is not None:
32+
aux = aux_val if aux_val.data.shape == aux_val.shape else aux_val.todense()
33+
maxbound_values.append(len(np.where(aux != FILL_DNODATA)[0]))
34+
35+
# Check boundname array
36+
boundname_val = new_value if attribute and attribute.name == 'boundname' else getattr(instance, 'boundname', None)
37+
if boundname_val is not None:
38+
boundname = boundname_val if boundname_val.data.shape == boundname_val.shape else boundname_val.todense()
39+
maxbound_values.append(len(np.where(boundname != "")[0]))
40+
41+
# Update maxbound if we have values
42+
if maxbound_values:
43+
instance._updating_maxbound = True
44+
try:
45+
instance.maxbound = max(maxbound_values)
46+
finally:
47+
delattr(instance, '_updating_maxbound')
48+
49+
return new_value
50+
51+
1552
@xattree
1653
class Chd(Package):
1754
multi_package: ClassVar[bool] = True
@@ -34,6 +71,7 @@ class Chd(Package):
3471
default=None,
3572
converter=Converter(dict_to_array, takes_self=True, takes_field=True),
3673
reader="urword",
74+
on_setattr=_update_maxbound,
3775
)
3876
aux: Optional[NDArray[np.float64]] = array(
3977
block="period",
@@ -44,6 +82,7 @@ class Chd(Package):
4482
default=None,
4583
converter=Converter(dict_to_array, takes_self=True, takes_field=True),
4684
reader="urword",
85+
on_setattr=_update_maxbound,
4786
)
4887
boundname: Optional[NDArray[np.str_]] = array(
4988
block="period",
@@ -54,32 +93,10 @@ class Chd(Package):
5493
default=None,
5594
converter=Converter(dict_to_array, takes_self=True, takes_field=True),
5695
reader="urword",
96+
on_setattr=_update_maxbound,
5797
)
5898

5999
def __attrs_post_init__(self):
60-
# TODO set up on_setattr hooks for period block
61-
# arrays to update maxbound? for now do it here
62-
# in post init. but this only works when values
63-
# are set in the initializer, not when they are
64-
# set later.
65-
if self.head is None:
66-
maxhead = 0
67-
else:
68-
head = self.head if self.head.data.shape == self.head.shape else self.head.todense()
69-
maxhead = len(np.where(head != FILL_DNODATA))
70-
if self.aux is None:
71-
maxaux = 0
72-
else:
73-
aux = self.aux if self.aux.data.shape == self.aux.shape else self.aux.todense()
74-
maxaux = len(np.where(aux != FILL_DNODATA))
75-
if self.boundname is None:
76-
maxboundname = 0
77-
else:
78-
boundname = (
79-
self.boundname
80-
if self.boundname.data.shape == self.boundname.shape
81-
else self.boundname.todense()
82-
)
83-
maxboundname = len(np.where(boundname != ""))
84-
85-
self.maxbound = max(maxhead, maxaux, maxboundname)
100+
# Trigger maxbound calculation on initialization
101+
if self.head is not None or self.aux is not None or self.boundname is not None:
102+
_update_maxbound(self, None, None)

flopy4/mf6/gwf/drn.py

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import ClassVar, Optional
33

44
import numpy as np
5-
from attrs import Converter
5+
from attrs import Converter, setters
66
from numpy.typing import NDArray
77
from xattree import xattree
88

@@ -12,6 +12,49 @@
1212
from flopy4.mf6.spec import array, field
1313

1414

15+
def _update_maxbound(instance, attribute, new_value):
16+
"""Update maxbound when period block arrays change."""
17+
if hasattr(instance, '_updating_maxbound'):
18+
return new_value
19+
20+
# Calculate maxbound from all relevant arrays
21+
maxbound_values = []
22+
23+
# Check elev array
24+
elev_val = new_value if attribute and attribute.name == 'elev' else getattr(instance, 'elev', None)
25+
if elev_val is not None:
26+
elev = elev_val if elev_val.data.shape == elev_val.shape else elev_val.todense()
27+
maxbound_values.append(len(np.where(elev != FILL_DNODATA)[0]))
28+
29+
# Check cond array
30+
cond_val = new_value if attribute and attribute.name == 'cond' else getattr(instance, 'cond', None)
31+
if cond_val is not None:
32+
cond = cond_val if cond_val.data.shape == cond_val.shape else cond_val.todense()
33+
maxbound_values.append(len(np.where(cond != FILL_DNODATA)[0]))
34+
35+
# Check aux array
36+
aux_val = new_value if attribute and attribute.name == 'aux' else getattr(instance, 'aux', None)
37+
if aux_val is not None:
38+
aux = aux_val if aux_val.data.shape == aux_val.shape else aux_val.todense()
39+
maxbound_values.append(len(np.where(aux != FILL_DNODATA)[0]))
40+
41+
# Check boundname array
42+
boundname_val = new_value if attribute and attribute.name == 'boundname' else getattr(instance, 'boundname', None)
43+
if boundname_val is not None:
44+
boundname = boundname_val if boundname_val.data.shape == boundname_val.shape else boundname_val.todense()
45+
maxbound_values.append(len(np.where(boundname != "")[0]))
46+
47+
# Update maxbound if we have values
48+
if maxbound_values:
49+
instance._updating_maxbound = True
50+
try:
51+
instance.maxbound = max(maxbound_values)
52+
finally:
53+
delattr(instance, '_updating_maxbound')
54+
55+
return new_value
56+
57+
1558
@xattree
1659
class Drn(Package):
1760
multi_package: ClassVar[bool] = True
@@ -33,13 +76,15 @@ class Drn(Package):
3376
default=None,
3477
converter=Converter(dict_to_array, takes_self=True, takes_field=True),
3578
reader="urword",
79+
on_setattr=_update_maxbound,
3680
)
3781
cond: Optional[NDArray[np.float64]] = array(
3882
block="period",
3983
dims=("nper", "nnodes"),
4084
default=None,
4185
converter=Converter(dict_to_array, takes_self=True, takes_field=True),
4286
reader="urword",
87+
on_setattr=_update_maxbound,
4388
)
4489
aux: Optional[NDArray[np.float64]] = array(
4590
block="period",
@@ -50,6 +95,7 @@ class Drn(Package):
5095
default=None,
5196
converter=Converter(dict_to_array, takes_self=True, takes_field=True),
5297
reader="urword",
98+
on_setattr=_update_maxbound,
5399
)
54100
boundname: Optional[NDArray[np.str_]] = array(
55101
block="period",
@@ -60,37 +106,10 @@ class Drn(Package):
60106
default=None,
61107
converter=Converter(dict_to_array, takes_self=True, takes_field=True),
62108
reader="urword",
109+
on_setattr=_update_maxbound,
63110
)
64111

65112
def __attrs_post_init__(self):
66-
# TODO set up on_setattr hooks for period block
67-
# arrays to update maxbound? for now do it here
68-
# in post init. but this only works when values
69-
# are set in the initializer, not when they are
70-
# set later.
71-
if self.elev is None:
72-
maxelev = 0
73-
else:
74-
elev = self.elev if self.elev.data.shape == self.elev.shape else self.elev.todense()
75-
maxelev = len(np.where(elev != FILL_DNODATA))
76-
if self.cond is None:
77-
maxcond = 0
78-
else:
79-
cond = self.cond if self.cond.data.shape == self.cond.shape else self.cond.todense()
80-
maxcond = len(np.where(cond != FILL_DNODATA))
81-
if self.aux is None:
82-
maxaux = 0
83-
else:
84-
aux = self.aux if self.aux.data.shape == self.aux.shape else self.aux.todense()
85-
maxaux = len(np.where(aux != FILL_DNODATA))
86-
if self.boundname is None:
87-
maxboundname = 0
88-
else:
89-
boundname = (
90-
self.boundname
91-
if self.boundname.data.shape == self.boundname.shape
92-
else self.boundname.todense()
93-
)
94-
maxboundname = len(np.where(boundname != ""))
95-
96-
self.maxbound = max(maxelev, maxcond, maxaux, maxboundname)
113+
# Trigger maxbound calculation on initialization
114+
if self.elev is not None or self.cond is not None or self.aux is not None or self.boundname is not None:
115+
_update_maxbound(self, None, None)

flopy4/mf6/gwf/rch.py

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import ClassVar, Optional
33

44
import numpy as np
5-
from attrs import Converter
5+
from attrs import Converter, setters
66
from numpy.typing import NDArray
77
from xattree import xattree
88

@@ -12,6 +12,43 @@
1212
from flopy4.mf6.spec import array, field
1313

1414

15+
def _update_maxbound(instance, attribute, new_value):
16+
"""Update maxbound when period block arrays change."""
17+
if hasattr(instance, '_updating_maxbound'):
18+
return new_value
19+
20+
# Calculate maxbound from all relevant arrays
21+
maxbound_values = []
22+
23+
# Check recharge array
24+
recharge_val = new_value if attribute and attribute.name == 'recharge' else getattr(instance, 'recharge', None)
25+
if recharge_val is not None:
26+
recharge = recharge_val if recharge_val.data.shape == recharge_val.shape else recharge_val.todense()
27+
maxbound_values.append(len(np.where(recharge != FILL_DNODATA)[0]))
28+
29+
# Check aux array
30+
aux_val = new_value if attribute and attribute.name == 'aux' else getattr(instance, 'aux', None)
31+
if aux_val is not None:
32+
aux = aux_val if aux_val.data.shape == aux_val.shape else aux_val.todense()
33+
maxbound_values.append(len(np.where(aux != FILL_DNODATA)[0]))
34+
35+
# Check boundname array
36+
boundname_val = new_value if attribute and attribute.name == 'boundname' else getattr(instance, 'boundname', None)
37+
if boundname_val is not None:
38+
boundname = boundname_val if boundname_val.data.shape == boundname_val.shape else boundname_val.todense()
39+
maxbound_values.append(len(np.where(boundname != "")[0]))
40+
41+
# Update maxbound if we have values
42+
if maxbound_values:
43+
instance._updating_maxbound = True
44+
try:
45+
instance.maxbound = max(maxbound_values)
46+
finally:
47+
delattr(instance, '_updating_maxbound')
48+
49+
return new_value
50+
51+
1552
@xattree
1653
class Rch(Package):
1754
multi_package: ClassVar[bool] = True
@@ -34,6 +71,7 @@ class Rch(Package):
3471
default=None,
3572
converter=Converter(dict_to_array, takes_self=True, takes_field=True),
3673
reader="urword",
74+
on_setattr=_update_maxbound,
3775
)
3876
aux: Optional[NDArray[np.float64]] = array(
3977
block="period",
@@ -44,6 +82,7 @@ class Rch(Package):
4482
default=None,
4583
converter=Converter(dict_to_array, takes_self=True, takes_field=True),
4684
reader="urword",
85+
on_setattr=_update_maxbound,
4786
)
4887
boundname: Optional[NDArray[np.str_]] = array(
4988
block="period",
@@ -54,32 +93,10 @@ class Rch(Package):
5493
default=None,
5594
converter=Converter(dict_to_array, takes_self=True, takes_field=True),
5695
reader="urword",
96+
on_setattr=_update_maxbound,
5797
)
5898

5999
def __attrs_post_init__(self):
60-
# TODO set up on_setattr hooks for period block
61-
# arrays to update maxbound? for now do it here
62-
# in post init. but this only works when values
63-
# are set in the initializer, not when they are
64-
# set later.
65-
if self.recharge is None:
66-
maxrecharge = 0
67-
else:
68-
recharge = self.head if self.head.data.shape == self.head.shape else self.head.todense()
69-
maxrecharge = len(np.where(recharge != FILL_DNODATA))
70-
if self.aux is None:
71-
maxaux = 0
72-
else:
73-
aux = self.aux if self.aux.data.shape == self.aux.shape else self.aux.todense()
74-
maxaux = len(np.where(aux != FILL_DNODATA))
75-
if self.boundname is None:
76-
maxboundname = 0
77-
else:
78-
boundname = (
79-
self.boundname
80-
if self.boundname.data.shape == self.boundname.shape
81-
else self.boundname.todense()
82-
)
83-
maxboundname = len(np.where(boundname != ""))
84-
85-
self.maxbound = max(maxrecharge, maxaux, maxboundname)
100+
# Trigger maxbound calculation on initialization
101+
if self.recharge is not None or self.aux is not None or self.boundname is not None:
102+
_update_maxbound(self, None, None)

0 commit comments

Comments
 (0)