def main(zGr, c, kD, S, t, rWell, zWell, Q): '''Run axial fdm model and return results. parameters ---------- zGr : ndarray(nLay) z values of layer interfaces c : ndarray(naquitard) resistances [d] of aquitards kD : ndarray(naquifer) tranmissivities of aquifers S : ndarray(naquitard + naquifer) strorage coefficients of all aquitards and aquifers in order t : ndarray(nt) times when output is wanted rWell : float well radius zWell : tuple of 2 floats top and bottom of well screen Q : float if neg. extraction from screen of positive injection ''' gr = grid(rWell, zGr) # use default x and y coordinates kh, kv, ss = k_s(gr, c=c, kD=kD, S=S) FQ, Iw = get_FQ(gr, z=zWell, Q=Q, kh=kh) HI = gr.const(0.) IBOUND = gr.const(1, dtype=int) IBOUND[0] = -1 kh.ravel()[Iw] = 1000. # hard wired, gravel pack if np.all(IBOUND[0] == -1): kv[0] /= 2.0 # top layer resistance with centered heads kwargs = { 'gr': gr, 't': t, 'kxyz': (kh, kh, kv), 'Ss': ss, 'FQ': FQ, 'HI': HI, 'IBOUND': IBOUND, 'epsilon': 1.0 } return fdm3t.fdm3t(**kwargs)
def __init__(self, mluxmlfile, tshift=0, Rmax=1e4, **kwargs): '''Return MLUtest, pumping test results based on mlu file. parameters ---------- tshift : float Delay imposed on measurement time (pump upstart delay) Rmax: float Duter radius of model where head is fixed. kwargs ------ Rw : float Well radius (not in mlu file) if None, use 2 * screen of well ''' mlu = Mluobj(mluxmlfile) aqSys = mlu.aqSys self.obswells = mlu.obsWells nz = len(aqSys.aquifs) + len(aqSys.atards) self.iaquif = np.ones(nz, dtype=bool) self.top_aquitard = aqSys.top_aquitard != 'absent' if self.top_aquitard: self.iaquif[::2] = False else: self.iaquif[1::2] = False self.iatard = np.logical_not(self.iaquif) rmin = kwargs.pop('Rw', 2 * mlu.wells[0].screen) # Piezometer data from xml file tmax = 0 tmin = np.inf x0, y0 = mlu.wells[0].x, mlu.wells[0].y r = rmin rmax = rmin self.names = list() self.points = list() self.ow_aquif = list() for ow in mlu.obsWells: self.names.append(ow.name) r = max(rmin, np.sqrt((x0 - ow.x)**2 + (y0 - ow.y)**2)) rmax = max(rmax, r) tmin = min(tmin, ow.data[0, 0]) tmax = max(tmax, ow.data[-1, 0]) self.ow_aquif.append(ow.layer - 1) self.points.append((r, mlu.aqSys.aquifs[ow.layer - 1].zmid)) # attribute discharge over screened aquifers Qs = np.zeros(len(mlu.aqSys.aquifs)) kDtot = 0. for iaq, (screened, aq) in enumerate(zip(mlu.wells[0].screens, mlu.aqSys.aquifs)): if screened == '1': kDtot += aq.T Qs[iaq] = aq.T Qs *= mlu.wells[0].Q / kDtot # radial grid, general max r. r = np.hstack( (0, np.logspace(np.log10(rmin), np.log10(Rmax), int(10 * np.ceil(np.log10(Rmax / rmin)) + 1)))) zf = np.array([(aquif.ztop, aquif.zbot) for aquif in aqSys.aquifs]) za = np.array([(atard.ztop, atard.zbot) for atard in aqSys.atards]) z = np.unique(np.hstack((zf[:, 0], zf[:, 1], za[:, 0], za[:, 1]))) self.gr = Grid(r, [-0.5, 0.5], z, axial=True) # sufficiently detailed times for graphs self.t = np.logspace(np.log10(tmin), np.log10(tmax), 60) self.t = np.unique(np.hstack((0., self.t))) # start at 0 # conductivities kD = np.array([aq.T for aq in aqSys.aquifs]) c = np.array([at.c for at in aqSys.atards]) kh = np.zeros(self.gr.nz) kh[self.iaquif] = kD / self.gr.dz[self.iaquif] kv = np.ones(self.gr.nz) * 1e6 # just large kv[self.iatard] = self.gr.dz[self.iatard] / c if self.iatard[0]: kv[0] /= 2. if self.iatard[-1]: kv[-1] /= 2. self.Kh = self.gr.const(kh) self.Kv = self.gr.const(kv) self.Kh[self.iaquif, :, 0] = 100. # no resistance inside well # storativities (both aquitards and aquifers) Saq = np.array([aq.S for aq in aqSys.aquifs]) Sat = np.array([at.S for at in aqSys.atards]) ss = np.zeros(self.gr.nz) ss[self.iaquif] = Saq / self.gr.dz[self.iaquif] ss[self.iatard] = Sat / self.gr.dz[self.iatard] self.Ss = self.gr.const(ss) kd_tot = 0. kd = np.zeros_like(kD) for i, scr in enumerate(mlu.wells[0].screens): if scr == '1': kd[i] = kD[i] kd_tot += kD[i] self.Q = mlu.wells[0].Q * kd / kd_tot self.FQ = self.gr.const(0) self.FQ[self.iaquif, 0, 0] = self.Q self.HI = self.gr.const(0.) self.IBOUND = self.gr.const(1, dtype=int) self.IBOUND[:, :, -1] = -1 if self.iatard[0]: self.IBOUND[0] = -1 if self.iatard[-1]: self.IBOUND[-1] = -1 # TODO: verify that the inflow over the outer boundary is < 5% or so. self.out = fdm3t(gr=self.gr, t=self.t, kxyz=(self.Kh, self.Kh, self.Kv), Ss=self.Ss, FQ=self.FQ, HI=self.HI, IBOUND=self.IBOUND, epsilon=1.0) ax = mlu.plot_drawdown(mlu.obsNames, tshift=tshift, marker='.', linestyle='none', **kwargs) #hantush(...) r = np.array(self.points)[:, 0] self.dd = hantushn(Q=self.Q, r=r, t=self.t[1:], Saq=Saq, Sat=Sat, c=c, T=kD, N=8) self.show(xscale='log', ax=ax, labels=self.names)
def __init__(self, gr=None, kD=None, c=None, S=None, top_aquitard=False, t=None, Q=None, obswells=None, **kwargs): '''Create a Pumptest object and simulate it with fdm and hantushn. 'rw and R are assumed to be gr.x[0] and gr.x[-1] parameters ---------- kD : sequence of floats transmissivities of aquifers [L2/T] S : tuple (Saq, Sat) | sequence Saq Saq : squence of floats storage coefficients of aquifers Sat : sequence of floats storage coefficients of aquitards if not a tuplbe with two sequences of floats, then S is assumed Saq for aquifers with all Sat = 0 c : sequence of floats hydraulic resistances of aquitards [T] top_aquitard : bool whether top layer is an aquitard t : sequence of floats times to compute heads Q : sequence fo floats discharge from each aquifier ''' gr.axial = True self.t = np.array(t) self.Q = np.array(Q) self.gr = gr self.gr.Axial = True self.top_aquitard = top_aquitard assert self.gr.nz == len(kD) + len(c),\ "len(kD) <{}> + len(c) <{}> != gr.nz <{}>"\ .format(len(kD), len(c), gr.nz) if isinstance(S, tuple): assert len(kD) == len(S[0]),\ 'len(kD) <{}> != len(S[0]) <{}>'.format(len(kD), len(S[0])) assert len(c) == len(S[1]),\ 'len(c) <{}> != len(S[[1]) <{}>'.format(len(c), len(S[1])) else: # if not a sequence, then S is Saq assert len(kD) == len(S),\ "len(kD) <{}> != len(S) <{}>".format(len(kD), len(S)) if len(c) > len(kD): top_aquitard = True #bot_aquitard = (len(c) > len(kD)) or \ # (len(c) == len(kD)) and (not top_aquitard) self.iaquif = np.ones(self.gr.nz, dtype=bool) if self.top_aquitard: self.iaquif[0::2] = False else: self.iaquif[1::2] = False self.iatard = np.logical_not(self.iaquif) # conductivities kh = np.zeros(self.gr.nz) kh[self.iaquif] = np.array(kD) / self.gr.dz[self.iaquif] kv = np.ones(self.gr.nz) * 1e6 # just large kv[self.iatard] = self.gr.dz[self.iatard] / np.array(c) if self.iatard[0]: kv[0] /= 2. if self.iatard[-1]: kv[-1] /= 2. self.Kh = self.gr.const(kh) self.Kv = self.gr.const(kv) self.Kh[self.iaquif, :, 0] = 100. # no resistance inside well # aquitards and aquifers can both have storage ss = np.zeros(self.gr.nz) if isinstance(S, tuple): Saq = S[0] Sat = S[1] assert len(Saq) == len(kD), 'len(Saq) <{}> != len(kD)<{}>'\ .format(len(Saq), len(kD)) assert len(Sat) == len(c), 'len(Sat) <{}> != len(kD) <{}>'\ .format(len(Sat), len(kD)) ss[self.iaquif] = Saq / self.gr.dz[self.iaquif] ss[self.iatard] = Sat / self.gr.dz[self.iatard] else: # S applies to only aquifers Saq = S Sat = np.zeros_like(c) assert len(Saq) == len(S), 'len(Saq) <{}> != len(kD) <{}>'\ .format(len(Saq), len(kD)) ss[self.iaquif] = S / self.gr.dz[self.iaquif] self.Ss = self.gr.const(ss) # Make sure t starts at zero to include initial heads. self.t = np.unique(np.hstack((0., np.array(t)))) assert np.all(t) >= 0, "All times must be > zero." # Discharge must be given for each aquifer assert len(Q) == len(kD), "len(Q) <{}> != len(kD) <{}>," +\ "specify a discharge for each aqufier."\ .format(len(Q), len(kD)) # Boundary and initial conditions self.FQ = self.gr.const(0) self.FQ[self.iaquif, 0, 0] = self.Q # Initial condition all heads zero (always in pumping test) self.HI = self.gr.const(0.) # Boundary array (Modflow-like) self.IBOUND = self.gr.const(1, dtype=int) # Only prescribe heads to be fixed if aquitard on top and or bottom # Just make sure that the radius of the model is large enough. if self.iatard[0]: self.IBOUND[0] = -1 if self.iatard[-1]: self.IBOUND[-1] = -1 # Simulate using FDM self.out = fdm3t(gr=self.gr, t=self.t, kxyz=(self.Kh, self.Kh, self.Kv), Ss=self.Ss, FQ=self.FQ, HI=self.HI, IBOUND=self.IBOUND, epsilon=1.0) # Also compute Hantush. Hantush computes at observation distances. # For this we need the observation points self.names = list() self.r_ow = list() self.z_ow = list() assert obswells is not None, "obswells == None, specify obswells as [(name, r, z), ..]" for ow in obswells: self.names.append(ow[0]) self.r_ow.append(ow[1]) self.z_ow.append(ow[2]) # store layer nr of each obswell self.layNr = self.gr.lrc(self.r_ow, np.zeros(len(self.r_ow)), self.z_ow)[:, 0] # get and store aquifer number of each obswell aqNr = np.zeros(self.gr.nz, dtype=int) - 1 k = 0 for i, ia in enumerate(self.iaquif): if ia: aqNr[i] = k k += 1 self.aqNr = aqNr[self.layNr] assert np.all(self.aqNr) > -1, "one or more obswells not in aquifer" self.dd = hantushn(Q=self.Q, r=self.r_ow, t=self.t[1:], Saq=Saq, Sat=Sat, c=c, T=kD, N=8)
# specify observation points [(anme, r, iaquifer), ...] points = [('well', 0.5*(rwell[0] + rwell[1]), -40.), ('TT-001 ', 1., -40.), ('TT-050' , 50., -40.), ('TT-070' , 70., -40.), ('TT-120' , 120., -40.), ('TT-175' , 175., -40.), ('TT-350' , 350., -40.), ('TT-700' , 700., -40.), ('TO-001', 1., -50.), ('TO-050', 50., -50.), ('TO-080', 70., -50.), ('TO-120', 120., -50.), ('TO-175', 175., -50.), ('TO-350', 350., -50.), ('TO-700', 700., -50.), ('BO-001', 1., -15.)] points = [points[i] for i in [0, 1, 2, 8]] out = fdm3t(gr=gr, t=t, kxyz=(Kh, Kh, Kv), Ss=Ss, FQ=FQ, HI=HI, IBOUND=IBOUND, epsilon=1.0) show(gr, t, out, points, xscale='log', grid=True) C = contour(gr, out, dphi=0.25, dpsi=10, xlim=(0, 150))
def compare_hant_fdm(aqSys, obsWells, t=None, Q=None, epsilon=1.0, **kwargs): '''Compare hantushn vor r's and all t with fdm3t obsWells : dictionarary with keyys ['name', 'r', 'layer') Input prerequisites aqSys : Aquifer_system epsilon : float implicitness, use value between 0.5 to 1 (Modflow uses 1.0) ''' N = kwargs.pop('N', 10) # simulate analytically using Hantushn dd = hantushn(Q=Q, r=obsWells.r, t=t, Sat=aqSys.Sat, Saq=aqSys.Saq, c=aqSys.c, T=aqSys.kD, N=N) dd = dd[:, obsWells.aquifer, np.arange(dd.shape[-1], dtype=int)] # simulate numerically using fdm3t FQ = aqSys.gr.const(0.) FQ[1::2, 0, 0] = Q HI = aqSys.gr.const(0.) IBOUND = aqSys.gr.const(1, dtype=int) IBOUND[[0, -1], :, :] = -1 Kh = aqSys.gr.const(aqSys.kh) Kv = aqSys.gr.const(aqSys.kv) Ss = aqSys.gr.const(aqSys.Ss) out = fdm3t(gr=aqSys.gr, t=t, kxyz=(Kh, Kh, Kv), Ss=Ss, FQ=FQ, HI=HI, IBOUND=IBOUND, epsilon=epsilon) phi = out['Phi'][:, :, 0, :] # squeeze y (axis 2) # interpolator interpolator = interp1d(aqSys.gr.xm, phi, axis=2) phi = interpolator(obsWells.r) # shape [Nt, Nz, len(obsWells)] # select using fancy indexing for last two axes phi = phi[:, obsWells.layer, np.arange(phi.shape[-1])] # plot the results of both Hantush and fdm3t ax = kwargs.pop('ax', None) if ax is None: fig, ax = plt.subplots() ax.set_title( kwargs.pop('title', 'Hantushn versus fdm3 for a set of observatio points')) ax.set_xlabel(kwargs.pop('xlabel', 't [d]')) ax.set_ylabel(kwargs.pop('ylabel', 's [m]')) ax.set_xscale(kwargs.pop('xscale', 'log')) ax.set_yscale(kwargs.pop('yscale', 'linear')) ax.grid(True) xlim = kwargs.pop('xlim', None) ylim = kwargs.pop('ylim', None) if xlim is not None: ax.set_xlim(xlim) if ylim is not None: ax.set_ylim(ylim) for i, name in enumerate(obsWells.names): ax.plot(t, phi[:, i], label='fdm ' + name, color=colors[i], ls='-', lw=2) ax.plot(t, dd[:, i], label='han ' + name, color=colors[i], ls='--') ax.legend(loc='best') return ax