def test_error(self): self.err_mat = np.array([[3, 2, 1, 0], [-3, -2, 7, 1], [3, 2, -1, 5], [0, 1, 2, 3]]) self.err_rhs = np.array([6, 3, 9, 6]) sol_2 = pp.solve(self.err_mat, self.err_rhs, is_flat=False, solver=2) diff_2 = np.max(np.abs(np.dot(self.err_mat, sol_2) - self.err_rhs)) self.assertAlmostEqual(diff_2, 0.0)
def solve_linear(self, option = 0): """ solves the assembled system using either pentapy or numpy linalg solve """ if option == 0: self.c = pp.solve(self.A,self.b, is_flat = False) else: self.c = np.linalg.solve(self.A,self.b)
def pentapy_solve(A,B): #extracting the diagonals second_super = [np.append(A.diagonal(2),[0.0, 0.0])] #second subdiagonal first_super = [np.append(A.diagonal(1),[0.0])] #first subdiagonal main = [A.diagonal()] #main diagonal first_sub = [np.append([0.0],A.diagonal(-1))] #first superdiagonal second_sub = [np.append([0.0, 0.0],A.diagonal(-2))] #second superdiagonal A_diagflat = np.concatenate((second_super, first_super,main,first_sub,second_sub), axis=0) #solving the system of equation X = pp.solve(A_diagflat, B, is_flat=True) return X;
def test_solve2(self): self.mat_col = pp.shift_banded(self.mat, col_to_row=False) self.mat_ful = pp.create_full(self.mat, col_wise=False) sol_row = pp.solve(self.mat, self.rhs, is_flat=True, solver=2) sol_col = pp.solve( self.mat_col, self.rhs, is_flat=True, index_row_wise=False, solver=2, ) sol_ful = pp.solve(self.mat_ful, self.rhs, solver=2) diff_row = np.max(np.abs(np.dot(self.mat_ful, sol_row) - self.rhs)) diff_col = np.max(np.abs(np.dot(self.mat_ful, sol_col) - self.rhs)) diff_ful = np.max(np.abs(np.dot(self.mat_ful, sol_ful) - self.rhs)) diff_row_col = np.max( np.abs(self.mat_ful - pp.create_full(self.mat_col))) self.assertAlmostEqual(diff_row * 1e-5, 0.0) self.assertAlmostEqual(diff_col * 1e-5, 0.0) self.assertAlmostEqual(diff_ful * 1e-5, 0.0) self.assertAlmostEqual(diff_row_col * 1e5, 0.0)
# -*- coding: utf-8 -*- """ 5. Example: Failing Corner Cases -------------------------------- Here we demonstrate that the solver PTRANS-I can fail to solve a given system. A warning is given in that case and the output will be a nan-array. """ import numpy as np import pentapy as pp # create a full pentadiagonal matrix mat = np.array([[3, 2, 1, 0], [-3, -2, 7, 1], [3, 2, -1, 5], [0, 1, 2, 3]]) V = np.array([6, 3, 9, 6]) # solve the LES with mat as a qudratic input matrix X = pp.solve(mat, V, is_flat=False, solver=1) print(X)
def solve_4(in_val): mat, V = in_val return solve(mat, V, is_flat=False, solver=4)
def solve_2(in_val): """PTRANS-II""" mat, V = in_val return solve(mat, V, is_flat=False, solver=2)
# -*- coding: utf-8 -*- """ 1. Example: Solving ------------------- Here we create a random row wise flattened matrix M_flat and a random right hand side for the pentadiagonal equation system. After solving we calculate the absolute difference between the right hand side and the product of the matrix (which is transformed to a full quadratic one) and the solution of the system. """ import numpy as np import pentapy as pp size = 1000 # create a flattened pentadiagonal matrix M_flat = (np.random.random((5, size)) - 0.5) * 1e-5 V = np.random.random(size) * 1e5 # solve the LES with M_flat as row-wise flattened matrix X = pp.solve(M_flat, V, is_flat=True) # create the corresponding matrix for checking M = pp.create_full(M_flat, col_wise=False) # calculate the error print(np.max(np.abs(np.dot(M, X) - V)))
def grf_laplace( s, rad=None, S_part=None, K_part=None, R_part=None, dim=2, lat_ext=1.0, rate=None, K_well=None, cut_off_prec=1e-20, cond=0, cond_kw=None, ): """ The extended GRF-model for transient flow in Laplace-space. The General Radial Flow (GRF) Model allowes fractured dimensions for transient flow under a pumping condition in a confined aquifer. The solutions assumes concentric annuli around the pumpingwell, where each annulus has its own conductivity and storativity value. Parameters ---------- s : :class:`numpy.ndarray` Array with all Laplace-space-points where the function should be evaluated rad : :class:`numpy.ndarray` Array with all radii where the function should be evaluated S_part : :class:`numpy.ndarray` of length N Given storativity values for each disk K_part : :class:`numpy.ndarray` of length N Given conductivity values for each disk R_part : :class:`numpy.ndarray` of length N+1 Given radii separating the disks as well as starting- and endpoints dim : :class:`float` Flow dimension. Default: 3 lat_ext : :class:`float` The lateral extend of the flow-domain, used in `L^(3-dim)`. Default: 1 rate : :class:`float` Pumpingrate at the well K_well : :class:`float`, optional Conductivity at the well. Default: ``K_part[0]`` cut_off_prec : :class:`float`, optional Define a cut-off precision for the calculation to select the disks included in the calculation. Default ``1e-20`` cond : :class:`int`, optional Type of the pumping condition: * 0 : constant * 1 : periodic (needs "w" as cond_kw) * 2 : slug (rate will be interpreted as slug-volume) * 3 : interval (needs "t" as cond_kw) * callable: laplace-transformation of the transient pumping-rate Default: 0 cond_kw : :class:`dict` optional Keyword args for the pumping condition. Default: None Returns ------- grf_laplace : :class:`numpy.ndarray` Array with all values in laplace-space Examples -------- >>> grf_laplace([5,10],[1,2,3],[1e-3,1e-3],[1e-3,2e-3],[0,2,10], 2, 1, -1) array([[-2.71359196e+00, -1.66671965e-01, -2.82986917e-02], [-4.58447458e-01, -1.12056319e-02, -9.85673855e-04]]) """ cond_kw = {} if cond_kw is None else cond_kw cond = cond if callable(cond) else PUMP_COND[cond] # ensure that input is treated as arrays s = np.squeeze(s).reshape(-1) rad = np.squeeze(rad).reshape(-1) S_part = np.squeeze(S_part).reshape(-1) K_part = np.squeeze(K_part).reshape(-1) R_part = np.squeeze(R_part).reshape(-1) # the dimension is used by nu in the literature (See Barker 88) dim = float(dim) nu = 1.0 - dim / 2.0 nu1 = nu - 1 # the lateral extend is a bit subtle in fractured dimension lat_ext = float(lat_ext) rate = float(rate) # get the number of partitions parts = len(K_part) # set the conductivity at the well K_well = K_part[0] if K_well is None else float(K_well) # check the input if not len(R_part) - 1 == len(S_part) == len(K_part) > 0: raise ValueError("R_part, S_part and K_part need matching lengths.") if R_part[0] < 0.0: raise ValueError("The wellradius needs to be >= 0.") if not all([r1 < r2 for r1, r2 in zip(R_part[:-1], R_part[1:])]): raise ValueError("The radii values need to be sorted.") if not np.min(rad) > R_part[0] or np.max(rad) > R_part[-1]: raise ValueError("The given radii need to be in the given range.") if not all([con > 0 for con in K_part]): raise ValueError("The Conductivity needs to be positiv.") if not all([stor > 0 for stor in S_part]): raise ValueError("The Storage needs to be positiv.") if not dim > 0.0 or dim > 3.0: raise ValueError("The dimension needs to be positiv and <= 3.") if not lat_ext > 0.0: raise ValueError("The lateral extend needs to be positiv.") if not K_well > 0: raise ValueError("The well conductivity needs to be positiv.") # initialize the result res = np.zeros(s.shape + rad.shape) # the first sqrt of the diffusivity values diff_sr0 = np.sqrt(S_part[0] / K_part[0]) # set the general pumping-condtion depending on the well-radius if R_part[0] > 0.0: Qs = -s ** (-0.5) / diff_sr0 * R_part[0] ** nu1 * cond(s, **cond_kw) else: Qs = -(2 / diff_sr0) ** nu * s ** (-nu / 2) * cond(s, **cond_kw) # if there is a homgeneouse aquifer, compute the result by hand if parts == 1: # initialize the equation system V = np.zeros(2, dtype=float) M = np.array([[-gamma(1 - nu), 2.0 / gamma(nu)], [0, 1]]) for si, se in enumerate(s): Cs = np.sqrt(se) * diff_sr0 # set the pumping-condition at the well V[0] = Qs[si] # incorporate the boundary-conditions if R_part[0] > 0.0: M[0, :] = [-kv(nu1, Cs * R_part[0]), iv(nu1, Cs * R_part[0])] if R_part[-1] < np.inf: M[1, :] = [kv(nu, Cs * R_part[-1]), iv(nu, Cs * R_part[-1])] else: M[0, 1] = 0 # Bs is 0 in this case either way # solve the equation system As, Bs = np.linalg.solve(M, V) # calculate the head for ri, re in enumerate(rad): if re < R_part[-1]: res[si, ri] = re ** nu * ( As * kv(nu, Cs * re) + Bs * iv(nu, Cs * re) ) # if there is more than one partition, create an equation system else: # initialize LHS and RHS for the linear equation system # Mb is the banded matrix for the Eq-System V = np.zeros(2 * parts) Mb = np.zeros((5, 2 * parts)) X = np.zeros(2 * parts) # set the standard boundary conditions for rwell=0.0 and rinf=np.inf Mb[2, 0] = -gamma(1 - nu) Mb[1, 1] = 2.0 / gamma(nu) Mb[2, -1] = 1.0 # calculate the consecutive fractions of the conductivities Kfrac = K_part[:-1] / K_part[1:] # calculate the square-root of the diffusivities difsr = np.sqrt(S_part / K_part) # calculate a temporal substitution (factor from mass-conservation) tmp = Kfrac * difsr[:-1] / difsr[1:] # match the radii to the different disks pos = np.searchsorted(R_part, rad) - 1 # iterate over the laplace-variable for si, se in enumerate(s): Cs = np.sqrt(se) * difsr # set the pumping-condition at the well # --> implement other pumping conditions V[0] = Qs[si] # generate the equation system as banded matrix for i in range(parts - 1): Mb[0, 2 * i + 3] = -iv(nu, Cs[i + 1] * R_part[i + 1]) Mb[1, 2 * i + 2 : 2 * i + 4] = [ -kv(nu, Cs[i + 1] * R_part[i + 1]), -iv(nu1, Cs[i + 1] * R_part[i + 1]), ] Mb[2, 2 * i + 1 : 2 * i + 3] = [ iv(nu, Cs[i] * R_part[i + 1]), kv(nu1, Cs[i + 1] * R_part[i + 1]), ] Mb[3, 2 * i : 2 * i + 2] = [ kv(nu, Cs[i] * R_part[i + 1]), tmp[i] * iv(nu1, Cs[i] * R_part[i + 1]), ] Mb[4, 2 * i] = -tmp[i] * kv(nu1, Cs[i] * R_part[i + 1]) # set the boundary-conditions if needed if R_part[0] > 0.0: Mb[2, 0] = -kv(nu1, Cs[0] * R_part[0]) Mb[1, 1] = iv(nu1, Cs[0] * R_part[0]) if R_part[-1] < np.inf: Mb[-2, -2] = kv(nu, Cs[-1] * R_part[-1]) Mb[2, -1] = iv(nu, Cs[-1] * R_part[-1]) else: # erase the last row, since X[-1] will be 0 Mb[0, -1] = 0 Mb[1, -1] = 0 # find first disk which has no impact Mb_cond = np.max(np.abs(Mb), axis=0) Mb_cond_lo = Mb_cond < cut_off_prec Mb_cond_hi = Mb_cond > 1 / cut_off_prec Mb_cond = np.logical_or(Mb_cond_lo, Mb_cond_hi) cond = np.where(Mb_cond)[0] found = cond.shape[0] > 0 first = cond[0] // 2 if found else parts # initialize coefficients X[2 * first :] = 0.0 # only the first disk has an impact if first <= 1: M_sgl = np.eye(2, dtype=float) M_sgl[:, 0] = Mb[2:4, 0] M_sgl[:, 1] = Mb[1:3, 1] # solve the equation system try: X[:2] = np.linalg.solve(M_sgl, V[:2]) except np.linalg.LinAlgError: # set 0 if matrix singular X[:2] = 0 elif first > 1: # shrink the matrix M_sgl = Mb[:, : 2 * first] if first < parts: M_sgl[-1, -1] = 0 M_sgl[-2, -1] = 0 M_sgl[-1, -2] = 0 X[: 2 * first] = solve( M_sgl, V[: 2 * first], is_flat=True, index_row_wise=False ) np.nan_to_num(X, copy=False) # calculate the head (ignore small values) with warnings.catch_warnings(): warnings.simplefilter("ignore", RuntimeWarning) k0_sub = X[2 * pos] * kv(nu, Cs[pos] * rad) k0_sub[np.abs(X[2 * pos]) < cut_off_prec] = 0 i0_sub = X[2 * pos + 1] * iv(nu, Cs[pos] * rad) i0_sub[np.abs(X[2 * pos + 1]) < cut_off_prec] = 0 res[si, :] = rad ** nu * (k0_sub + i0_sub) # set problematic values to 0 # --> the algorithm tends to violate small values, # therefore this approach is suitable np.nan_to_num(res, copy=False) # scale to pumpingrate res *= rate / (K_well * sph_surf(dim) * lat_ext ** (3.0 - dim)) return res
# -*- coding: utf-8 -*- """ 2. Example: Comparison ---------------------- Here we compare the outcome of the PTRANS-I and PTRANS-II algorithm for a random input. """ import numpy as np import pentapy as pp size = 1000 # create a flattened pentadiagonal matrix M_flat = (np.random.random((5, size)) - 0.5) * 1e-5 V = np.random.random(size) * 1e5 # compare the two solvers X1 = pp.solve(M_flat, V, is_flat=True, solver=1) X2 = pp.solve(M_flat, V, is_flat=True, solver=2) # calculate the error print("rel. diff X1 X2: ", np.max(np.abs(X1 - X2)) / np.max(np.abs(X1 + X2))) print("max X1: ", np.max(np.abs(X1))) print("max X2: ", np.max(np.abs(X2))) M = pp.create_full(M_flat, col_wise=False) # calculate the error print("max |M*X1 - V|: ", np.max(np.abs(np.dot(M, X1) - V))) print("max |M*X2 - V|: ", np.max(np.abs(np.dot(M, X2) - V)))
def solve_4(in_val): mat, V = in_val return solve(mat, V, is_flat=True, index_row_wise=False, solver=4)
def solve_2(in_val): """PTRANS-II""" mat, V = in_val return solve(mat, V, is_flat=True, index_row_wise=False, solver=2)