Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update main to v0.2.0 #11

Merged
merged 23 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions docs/examples/crosscorrelation_seriesJ.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import matplotlib as mpl\n",
"import matplotlib.pyplot as plt\n",
"import pandas as pd\n",
"\n",
"import pastas_plugins.cross_correlation as ppcc"
]
Expand Down Expand Up @@ -96,11 +96,17 @@
],
"source": [
"nlags = 25\n",
"ccf_xy = ppcc.ccf(Y, X, alpha=0.05, nlags=nlags) # note the order of the arguments\n",
"acf_x = ppcc.ccf(X, X, alpha=0.05, nlags=nlags) # acf is ccf with itself\n",
"acf_y = ppcc.ccf(Y, Y, alpha=0.05, nlags=nlags) # acf is ccf with itself\n",
"ccf_xy = ppcc.ccf(Y, X, alpha=0.05, nlags=nlags) # note the order of the arguments\n",
"acf_x = ppcc.ccf(X, X, alpha=0.05, nlags=nlags) # acf is ccf with itself\n",
"acf_y = ppcc.ccf(Y, Y, alpha=0.05, nlags=nlags) # acf is ccf with itself\n",
"\n",
"f, ax = plt.subplots(3, 1, sharex=True, figsize=(6.5, 5.0), constrained_layout=True,)\n",
"f, ax = plt.subplots(\n",
" 3,\n",
" 1,\n",
" sharex=True,\n",
" figsize=(6.5, 5.0),\n",
" constrained_layout=True,\n",
")\n",
"ppcc.plot_corr(ccf_xy, ax=ax[0])\n",
"ax[0].set_ylabel(\"CCF\")\n",
"ax[0].set_title(\"Cross-correlation between input and output\")\n",
Expand Down
546 changes: 254 additions & 292 deletions docs/examples/modflow.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/examples/responses.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import pastas as ps\n",
"import numpy as np\n",
"\n",
"import pastas_plugins as pp\n",
"from pastas_plugins import responses"
Expand Down
89 changes: 49 additions & 40 deletions pastas_plugins/modflow/modflow.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import functools
import logging
from typing import List, Protocol

import flopy
import numpy as np
from pandas import DataFrame, Series
from pastas.typing import ArrayLike

logger = logging.getLogger(__name__)


class Modflow(Protocol):
def __init__(self) -> None: ...
Expand All @@ -17,15 +21,21 @@ def simulate(self) -> ArrayLike: ...


class ModflowRch:
def __init__(self, exe_name: str, sim_ws: str):
# self.initialhead = initialhead
def __init__(
self, exe_name: str, sim_ws: str, raise_on_modflow_error: bool = False
):
self.exe_name = exe_name
self.sim_ws = sim_ws
self._name = "mf_rch"
self._stress = None
self._simulation = None
self._gwf = None
self._changing_packages = ("IC", "NPF", "STO", "GHB", "RCH")
self._changing_packages = (
"STO",
"GHB",
"RCH",
)
self.raise_on_modflow_error = raise_on_modflow_error

def get_init_parameters(self, name: str) -> DataFrame:
parameters = DataFrame(columns=["initial", "pmin", "pmax", "vary", "name"])
Expand All @@ -40,6 +50,7 @@ def create_model(self) -> None:
version="mf6",
exe_name=self.exe_name,
sim_ws=self.sim_ws,
lazy_io=True,
)

_ = flopy.mf6.ModflowTdis(
Expand All @@ -52,25 +63,15 @@ def create_model(self) -> None:
gwf = flopy.mf6.ModflowGwf(
sim,
modelname=self._name,
newtonoptions="NEWTON",
)

_ = flopy.mf6.ModflowIms(
sim,
# print_option="SUMMARY",
complexity="SIMPLE",
# outer_dvclose=1e-9,
# outer_maximum=100,
# under_relaxation="DBD",
# under_relaxation_theta=0.7,
# inner_maximum=300,
# inner_dvclose=1e-9,
# rcloserecord=1e-3,
outer_dvclose=1e-2,
inner_dvclose=1e-2,
rcloserecord=1e-1,
linear_acceleration="BICGSTAB",
# scaling_method="NONE",
# reordering_method="NONE",
# relaxation_factor=0.97,
# filename=f"{name}.ims",
pname=None,
)
# sim.register_ims_package(imsgwf, [self._name])
Expand All @@ -83,12 +84,18 @@ def create_model(self) -> None:
ncol=1,
delr=1,
delc=1,
top=1000,
botm=-1000,
top=1.0,
botm=0.0,
idomain=1,
pname=None,
)

_ = flopy.mf6.ModflowGwfnpf(
gwf, save_flows=False, icelltype=0, k=1.0, pname="npf"
)

_ = flopy.mf6.ModflowGwfic(gwf, strt=0.0, pname="ic")

_ = flopy.mf6.ModflowGwfoc(
gwf,
head_filerecord=f"{gwf.name}.hds",
Expand All @@ -103,9 +110,7 @@ def create_model(self) -> None:
def update_model(self, p: ArrayLike):
sy, c, f = p[0:3]

d = 0
laytyp = 1

d = 0.0
r = self._stress[0] + f * self._stress[1]

# remove existing packages
Expand All @@ -114,19 +119,12 @@ def update_model(self, p: ArrayLike):
):
[self._gwf.remove_package(x) for x in self._changing_packages]

ic = flopy.mf6.ModflowGwfic(self._gwf, strt=d, pname="ic")
ic.write()

npf = flopy.mf6.ModflowGwfnpf(
self._gwf, save_flows=False, icelltype=laytyp, pname="npf"
)
npf.write()

haq = (self._gwf.dis.top.array - self._gwf.dis.botm.array)[0]
sto = flopy.mf6.ModflowGwfsto(
self._gwf,
save_flows=False,
iconvert=laytyp,
sy=sy,
iconvert=0,
ss=sy / haq,
transient=True,
pname="sto",
)
Expand All @@ -136,7 +134,7 @@ def update_model(self, p: ArrayLike):
ghb = flopy.mf6.ModflowGwfghb(
self._gwf,
maxbound=1,
stress_period_data={0: [[(0, 0, 0), d, 1 / c]]},
stress_period_data={0: [[(0, 0, 0), d, 1.0 / c]]},
pname="ghb",
)
ghb.write()
Expand All @@ -162,16 +160,27 @@ def update_model(self, p: ArrayLike):

self._gwf.name_file.write()

@functools.lru_cache(maxsize=5)
def _get_head(self, p):
self.update_model(p=p)
success, _ = self._simulation.run_simulation(silent=True)
if success:
return self._gwf.output.head().get_ts((0, 0, 0))[:, 1]
else:
logger.error(
"ModflowError: model run failed with parameters: "
f"sy={p[0]}, c={p[1]}, f={p[2]}"
)
if self.raise_on_modflow_error:
raise Exception(
"Modflow run failed. Check the LIST file for more information."
)
else:
return np.zeros(self._nper)

def simulate(self, p: ArrayLike, stress: List[Series]) -> ArrayLike:
if self._simulation is None:
self._stress = stress
self._nper = len(self._stress[0])
self.create_model()
self.update_model(p=p)
success, _ = self._simulation.run_simulation(silent=True)
if success:
heads = flopy.utils.HeadFile(
self._simulation.sim_path / f"{self._simulation.name}.hds"
).get_ts((0, 0, 0))
return heads[:, 1]
return np.zeros(self._stress[0].shape)
return self._get_head(tuple(p))
3 changes: 1 addition & 2 deletions pastas_plugins/modflow/stressmodels.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def set_init_parameters(self) -> None:
self.parameters = self.modflow.get_init_parameters(self.name)

def to_dict(self, series: bool = True) -> dict:
pass
raise NotImplementedError()

def get_stress(
self,
Expand Down Expand Up @@ -101,7 +101,6 @@ def simulate(
data=h,
index=stress[0].index,
name=self.name,
fastpath=True,
)

def _get_block(self, p, dt, tmin, tmax):
Expand Down
2 changes: 1 addition & 1 deletion pastas_plugins/modflow/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.0"
__version__ = "0.2.0"
130 changes: 130 additions & 0 deletions pastas_plugins/responses/parameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from numpy import pi, sqrt
from scipy.special import k0


def kraijenhoff_parameters(
S: float, K: float, D: float, x: float, L: float, N: float = 1.0
) -> tuple[float]:
"""Get Pastas parameters for Kraijenhoff van de Leur response function for
an homogeneous aquifer between two parallel canals.

Parameters
----------
S : float
The storativity of the aquifer [-].
K : float
The saturated hydraulic conductivity [L/T].
D : float
The saturated thickness of the aquifer [L].
x : float
The location in the aquifer [L] with x=0 in the center.
L : float
The aquifer length [L].
N : float
The recharge flux [L/T].

Returns
-------
tuple[float]
A tuple containing the response parameters A, a, and b.
"""
b = x / L
A = -N * L**2 / (2 * K * D) * (b**2 - (1 / 4))
a = S * L**2 / (pi**2 * K * D)
return A, a, b


def exponential_parameters(S: float, c: float, N: 1.0) -> tuple[float]:
"""Get Pastas parameters for an Exponential response for a linear
reservoir system.

Parameters
----------
S : float
The storativity of the aquifer [-].
c : float
The drainage resistance [T].
N : float
The recharge flux [L/T].

Returns
-------
tuple[float]
A tuple containing the response parameters A and a.
"""

A = N * c
a = c * S
return A, a


def hantush_parameters(
S: float, K: float, D: float, c: float, r: float, Q: float = -1.0
) -> tuple[float]:
"""Get Pastas parameters for an Hantush response for a well in a confined aquifer.

Parameters
----------
S : float
The storativity of the aquifer [-].
K : float
The saturated hydraulic conductivity [L/T].
D : float
The saturated thickness of the aquifer [L].
c : float
The drainage resistance [T].
r : float
The distance from the well resistance [L].
Q : float
The discharge of the well [L].

Returns
-------
tuple[float]
A tuple containing the response parameters A, a and b.
"""

T = K * D
lab = sqrt(T * c)
b = r**2 / (4.0 * lab**2.0)
a = c * S
A = Q * k0(r / lab) / (2.0 * pi * T)

return A, a, b


def theis_parameters(
S: float, K: float, D: float, x: float, L: float, Q: float = -1.0
) -> tuple[float]:
"""Get Pastas parameters for an Theis response for a well between two parallel canals.

Parameters
----------
S : float
The storativity of the aquifer [-].
K : float
The saturated hydraulic conductivity [L/T].
D : float
The saturated thickness of the aquifer [L].
x : float
The location in the aquifer [L] with x=0 in the center.
L : float
The aquifer length [L].
Q : float
The discharge of the well [L].

Note
----
Works only along the line y=0

Returns
-------
tuple[float]
A tuple containing the response parameters A, a and b.
"""
T = K * D
xw = 0.0 # with xw = 0
A = Q / (4.0 * pi * T)
a = S * L**2 / (pi**2.0 * T)
b = (x - xw) / L
return A, a, b
Loading