Skip to content

Commit 7d962fc

Browse files
Joost DelsmanJoost Delsman
authored andcommitted
add ani hfb support for wq
1 parent 157025c commit 7d962fc

File tree

4 files changed

+197
-2
lines changed

4 files changed

+197
-2
lines changed

imod/wq/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
AdvectionModifiedMOC,
1515
AdvectionTVD,
1616
)
17+
from imod.wq.ani import HorizontalAnisotropy, HorizontalAnisotropyFile
1718
from imod.wq.bas import BasicFlow
1819
from imod.wq.btn import BasicTransport
1920
from imod.wq.chd import ConstantHead
@@ -26,6 +27,7 @@
2627
EvapotranspirationTopLayer,
2728
)
2829
from imod.wq.ghb import GeneralHeadBoundary
30+
from imod.wq.hfb import HorizontalFlowBarrier
2931
from imod.wq.lpf import LayerPropertyFlow
3032
from imod.wq.mal import MassLoading
3133
from imod.wq.model import SeawatModel

imod/wq/ani.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import numpy as np
2+
import pathlib
3+
import re
4+
import shutil
5+
6+
import imod
7+
from imod.wq.pkgbase import Package
8+
9+
class HorizontalAnisotropyFile(Package):
10+
"""
11+
Horizontal Anisotropy package.
12+
13+
Parameters
14+
----------
15+
anifile: str
16+
is the file location of the imod-wq ani-file. This file contains the
17+
anisotropy factor and angle of each layer, either as a constant or a
18+
reference to the file location of an '.arr' file. No checks are
19+
implemented for this file, user is responsible for consistency with
20+
model.
21+
"""
22+
23+
_pkg_id = "ani"
24+
25+
_template = (
26+
"[ani]\n"
27+
" anifile = {anifile}\n"
28+
)
29+
30+
def __init__(
31+
self,
32+
anifile,
33+
):
34+
super().__init__()
35+
self["anifile"] = anifile
36+
37+
def _render(self, directory, *args, **kwargs):
38+
path_ani = pathlib.Path(str(self['anifile'].values))
39+
d = {"anifile": f"ani/{path_ani.name}"}
40+
41+
return self._template.format(**d)
42+
43+
def save(self, directory):
44+
"""Overload save function.
45+
Saves anifile to location, along with referenced .arr files"""
46+
directory.mkdir(exist_ok=True)
47+
48+
path_ani = pathlib.Path(str(self['anifile'].values))
49+
50+
# regex adapted from stackoverflow: https://stackoverflow.com/questions/54990405/a-general-regex-to-extract-file-paths-not-urls
51+
rgx = r'((?:[a-zA-Z]:|(?<![:/\\])[\\\/](?!CLOSE)(?!close )|\~[\\\/]|(?:\.{1,2}[\\\/])+)[\w+\\\s_\-\(\)\/]*(?:\.\w+)*)'
52+
with open(path_ani, "r") as f, open(directory / path_ani.name, "w") as f2:
53+
for line in f:
54+
p = re.search(rgx, line)
55+
if p:
56+
# path to file detected,
57+
# replace to relative and
58+
# copy to directory
59+
path = pathlib.Path(p[0])
60+
f2.write(line.replace(p[0], f"{directory.stem}/{path.name}"))
61+
if not path.is_absolute():
62+
path = path_ani.parent / path
63+
shutil.copyfile(path, directory / path.name)
64+
else:
65+
f2.write(line)
66+
67+
def _pkgcheck(self, ibound=None):
68+
pass
69+
70+
71+
class HorizontalAnisotropy(Package):
72+
"""
73+
Horizontal Anisotropy package.
74+
Anisotropy is a phenomenon for which the permeability k is not equal along the x- and y Cartesian axis.
75+
76+
Parameters
77+
----------
78+
factor : float or xr.DataArray of floats
79+
The anisotropic factor perpendicular to the main principal axis (axis of highest permeability).
80+
Factor between 0.0 (full anisotropic) and 1.0 (full isotropic).
81+
angle : float or xr.DataArray of floats
82+
The angle along the main principal axis (highest permeability) measured in degrees from north (0),
83+
east (90), south (180) and west (270).
84+
fn_ani : filename for created anifile, optional
85+
Default: ani.ani
86+
"""
87+
88+
_pkg_id = "ani"
89+
90+
_template = (
91+
"[ani]\n"
92+
" anifile = {anifile}\n"
93+
)
94+
95+
def __init__(
96+
self,
97+
factor,
98+
angle,
99+
fn_ani="ani.ani"
100+
):
101+
super().__init__()
102+
self["factor"] = factor
103+
self["factor"] = self["factor"].fillna(1.)
104+
self["angle"] = angle
105+
self["angle"] = self["angle"].fillna(0.)
106+
self["fn_ani"] = fn_ani
107+
108+
def _render(self, directory, *args, **kwargs):
109+
d = {"anifile": f"ani/{str(self['fn_ani'].values)}"}
110+
111+
return self._template.format(**d)
112+
113+
def save(self, directory):
114+
"""Overload save function.
115+
Saves anifile to location, along with referenced .arr files"""
116+
def _write(path, a, nodata=1.0e20, dtype=np.float32):
117+
if not np.all(a == a[0][0]):
118+
return np.savetxt(path, a, fmt="%.5f", delimiter=" ")
119+
else:
120+
# write single value to ani file
121+
pass
122+
123+
for name, da in self.dataset.data_vars.items(): # pylint: disable=no-member
124+
if "y" in da.coords and "x" in da.coords:
125+
path = pathlib.Path(directory).joinpath(f"{name}.arr")
126+
if name == "factor":
127+
nodata = 1.
128+
elif name == "angle":
129+
nodata = 0.
130+
else:
131+
nodata = 1.0e20
132+
imod.array_io.writing._save(path, da, nodata=nodata, pattern=None, dtype=np.float32, write=_write)
133+
134+
# write ani file
135+
with open(directory / f"{str(self['fn_ani'].values)}", "w") as f:
136+
for l in self.dataset.layer.values:
137+
for prm in ["factor","angle"]:
138+
a = self.dataset[prm].sel(layer=l).values
139+
if not np.all(a == a[0][0]):
140+
f.write(f"open/close {directory.as_posix()}/{prm}_l{float(l):.0f}.arr 1.0D0 (FREE) -1 {prm}_l{float(l):.0f}\n")
141+
else:
142+
f.write(f"constant {float(a[0][0]):.5f} {prm}_l{float(l):.0f}\n")
143+
144+
def _pkgcheck(self, ibound=None):
145+
pass

imod/wq/hfb.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import pathlib
2+
import shutil
3+
from imod.wq.pkgbase import Package
4+
5+
class HorizontalFlowBarrier(Package):
6+
"""
7+
Horizontal Flow Barrier package.
8+
9+
Parameters
10+
----------
11+
hfbfile: str
12+
is the file location of the imod-wq hfb-file. This file contains cell-
13+
to-cell resistance values. The hfb file can be constructed from generate
14+
files using imod-batch. No checks are implemented for this file, user is
15+
responsible for consistency with model.
16+
"""
17+
18+
_pkg_id = "hfb"
19+
20+
_template = (
21+
"[hfb]\n"
22+
" hfbfile = {hfbfile}\n"
23+
)
24+
25+
def __init__(
26+
self,
27+
hfbfile,
28+
):
29+
super().__init__()
30+
self["hfbfile"] = hfbfile
31+
32+
def _render(self, directory, *args, **kwargs):
33+
path_hfb = pathlib.Path(str(self["hfbfile"].values))
34+
d = {"hfbfile": f"hfb/{path_hfb.name}"}
35+
36+
return self._template.format(**d)
37+
38+
def save(self, directory):
39+
"""Overloads save function.
40+
Saves hfbfile to directory"""
41+
directory.mkdir(exist_ok=True)
42+
43+
path_hfb = pathlib.Path(str(self["hfbfile"].values))
44+
45+
shutil.copyfile(path_hfb, directory / path_hfb.name)
46+
47+
def _pkgcheck(self, ibound=None):
48+
pass

imod/wq/model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ def render(self, directory, result_dir, writehelp):
666666
directory=directory, globaltimes=globaltimes, nlayer=nlayer
667667
)
668668
)
669-
for key in ("vdf", "adv", "dsp"):
669+
for key in ("vdf", "adv", "dsp", "ani", "hfb"):
670670
content.append(
671671
self._render_pkg(
672672
key=key, directory=directory, globaltimes=globaltimes, nlayer=nlayer
@@ -793,7 +793,7 @@ def write(
793793
if (
794794
"x" in pkg.dataset.coords
795795
and "y" in pkg.dataset.coords
796-
or pkg._pkg_id == "wel"
796+
or pkg._pkg_id in ("wel", "ani", "hfb")
797797
):
798798
try:
799799
pkg.save(directory=directory / pkgname)

0 commit comments

Comments
 (0)