Skip to content

Commit

Permalink
Add LineSink1D:
Browse files Browse the repository at this point in the history
- first linesink1d commit
- add LineSink1DBase, DischargeLineSink1D, LineSink1D and HeadLineSink1D
- add example notebook with comparison to analytical solutions. Only single aquifers for now.
  • Loading branch information
dbrakenhoff committed Nov 7, 2024
1 parent afde9bd commit eed3372
Show file tree
Hide file tree
Showing 3 changed files with 656 additions and 0 deletions.
415 changes: 415 additions & 0 deletions docs/03examples/linesink1d.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions ttim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
LineSink,
LineSinkDitchString,
)
from .linesink1d import DischargeLineSink1D, HeadLineSink1D, LineSink1D, LineSink1DBase

# Import all classes and functions
from .model import Model3D, ModelMaq
Expand Down
240 changes: 240 additions & 0 deletions ttim/linesink1d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import matplotlib.pyplot as plt
import numpy as np

from ttim.element import Element
from ttim.equation import HeadEquation


class LineSink1DBase(Element):
"""LineSink1D Base Class.
All LineSink1D elements are derived from this class
"""

def __init__(
self,
model,
xls=0,
tsandbc=[(0, 1)],
res=0,
wh="H",
layers=0,
type="",
name="LineSink1DBase",
label=None,
):
Element.__init__(
self,
model,
nparam=1,
nunknowns=0,
layers=layers,
tsandbc=tsandbc,
type=type,
name=name,
label=label,
)
# Defined here and not in Element as other elements can have multiple
# parameters per layers
self.nparam = len(self.layers)
self.xls = float(xls)
self.res = np.atleast_1d(res).astype(np.float64)
self.wh = wh
self.model.addelement(self)

def __repr__(self):
return self.name + " at " + str(self.xls)

def initialize(self):
# Control point to make sure the point is always the same for
# all elements
self.xc = np.array([self.xls])
self.yc = np.zeros(1)
self.ncp = 1
self.aq = self.model.aq.find_aquifer_data(self.xc[0], self.yc[0])
self.setbc()
coef = self.aq.coef[self.layers, :]
self.setflowcoef()
# term is shape (self.nparam,self.aq.naq,self.model.npval)
self.term = self.flowcoef * coef
self.term2 = self.term.reshape(
self.nparam, self.aq.naq, self.model.nint, self.model.npint
)
self.dischargeinf = self.flowcoef * coef
self.dischargeinflayers = np.sum(
self.dischargeinf * self.aq.eigvec[self.layers, :, :], 1
)
if isinstance(self.wh, str):
if self.wh == "H":
self.wh = self.aq.Haq[self.layers]
elif self.wh == "2H":
self.wh = 2.0 * self.aq.Haq[self.layers]
else:
self.wh = np.atleast_1d(self.wh) * np.ones(self.nlayers)
# Q = (h - hls) / resfach
self.resfach = self.res / (self.wh)
# Q = (Phi - Phils) / resfacp
self.resfacp = self.resfach * self.aq.T[self.layers]

def setflowcoef(self):
"""Separate function so that this can be overloaded for other types."""
self.flowcoef = 1.0 / self.model.p # Step function

def potinf(self, x, _, aq=None):
"""Can be called with only one x value."""
if aq is None:
aq = self.model.aq.find_aquifer_data(x, 0.0)
rv = np.zeros((self.nparam, aq.naq, self.model.npval), dtype=complex)
if aq == self.aq:
if (x - self.xls) < 0.0:
pot = -0.5 * aq.lab * np.exp((x - self.xls) / aq.lab)
elif (x - self.xls) >= 0.0:
pot = -0.5 * aq.lab * np.exp(-(x - self.xls) / aq.lab)
else:
raise ValueError("Something wrong with passed x value.")
rv[:] = self.term * pot
return rv

def disvecinf(self, x, y, aq=None):
"""Can be called with only one x,y value."""
if aq is None:
aq = self.model.aq.find_aquifer_data(x, y)
rvx = np.zeros((self.nparam, aq.naq, self.model.npval), dtype=complex)
rvy = np.zeros((self.nparam, aq.naq, self.model.npval), dtype=complex)
if aq == self.aq:
if (x - self.xls) < 0.0:
qx = 0.5 * np.exp((x - self.xls) / aq.lab)
elif (x - self.xls) >= 0.0:
qx = -0.5 * np.exp(-(x - self.xls) / aq.lab)
else:
raise ValueError("Something wrong with passed x value.")
rvx[:] = self.term * qx
return rvx, rvy

def changetrace(
self, xyzt1, xyzt2, aq, layer, ltype, modellayer, direction, hstepmax
):
raise NotImplementedError("changetrace not implemented for this element")

def plot(self, ax=None):
if ax is None:
_, ax = plt.subplots()
for ilay in self.layers:
ax.plot(
[self.xls, self.xls],
[self.model.aq.zaqtop[ilay], self.model.aq.zaqbot[ilay]],
"k-",
)


class DischargeLineSink1D(LineSink1DBase):
r"""Linesink1D with a specified discharge for each layer that the linesink.
Parameters
----------
model : Model object
model to which the element is added
x : float
x-coordinate of the linesink
tsandq : list of tuples
tuples of starting time and specific discharge after starting time
res : float
resistance of the linesink
layers : int, array or list
layer (int) or layers (list or array) in which linesink is located
label : string or None (default: None)
label of the linesink
Examples
--------
Example of an infinitely long linesink that pumps with a specific discharge of
100 between times 10 and 50, with a specific discharge of 20 between
times 50 and 200, and zero speficic discharge after time 200.
>>> DischargeLineSink1D(ml, tsandq=[(10, 100), (50, 20), (200, 0)])
"""

def __init__(
self, model, xls=0, tsandq=[(0, 1)], res=0, wh="H", layers=0, label=None
):
super().__init__(
model,
xls,
tsandbc=tsandq,
res=res,
wh=wh,
layers=layers,
type="g",
name="DischargeLineSink1D",
label=label,
)


# TODO: add equation for dividing discharge over layers:
class LineSink1D(LineSink1DBase):
r"""Linesink1D with a specified discharge.
Parameters
----------
model : Model object
model to which the element is added
x : float
x-coordinate of the linesink
tsandq : list of tuples
tuples of starting time and specific discharge after starting time
res : float
resistance of the linesink
layers : int, array or list
layer (int) or layers (list or array) in which linesink is located
label : string or None (default: None)
label of the linesink
Examples
--------
Example of an infinitely long linesink that pumps with a specific discharge of
100 between times 10 and 50, with a specific discharge of 20 between
times 50 and 200, and zero speficic discharge after time 200.
>>> DischargeLineSink1D(ml, tsandq=[(10, 100), (50, 20), (200, 0)])
"""

def __init__(
self, model, xls=0, tsandq=[(0, 1)], res=0, wh="H", layers=0, label=None
):
super().__init__(
model,
xls,
tsandbc=tsandq,
res=res,
wh=wh,
layers=layers,
type="g",
name="DischargeLineSink1D",
label=label,
)


class HeadLineSink1D(LineSink1DBase, HeadEquation):
def __init__(
self, model, xls=0, tsandh=[(0, 1)], res=0, wh="H", layers=0, label=None
):
super().__init__(
model,
xls,
tsandbc=tsandh,
res=res,
wh=wh,
layers=layers,
type="v",
name="HeadLineSink1D",
label=label,
)
self.nunknowns = self.nparam

def initialize(self):
super().initialize()
self.parameters = np.zeros(
(self.model.ngvbc, self.nparam, self.model.npval), dtype=complex
)
# Needed in solving, solve for a unit head
self.pc = self.aq.T[self.layers]

0 comments on commit eed3372

Please sign in to comment.