├── .gitignore
├── README.md
├── discretization
├── FEM.py
├── FVML.py
├── FVMO.py
├── TPFA.py
├── __init__.py
├── mesh.py
└── operators.py
├── elliptic_convergence.py
├── poisson.py
├── richards_constant_hydraulic_conductivity.py
├── richards_non_linear.py
└── utils
├── differentiation.py
└── flux_error.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .pyc
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # masterthesis
2 | This repo contains discretization methods for the [elliptic PDE](https://en.wikipedia.org/wiki/Elliptic_partial_differential_equation)
3 |
4 |
,
5 |
6 | in two spatial dimensions that handle general quadrilateral grids. In particular, the MPFA-O and MPFA-L methods are implemented, they yield locally mass conservative discretizations that are consistent for rough grids. The MPFA-L method has particulary good monotonicity properties, i.e., the [maximum principle](https://en.wikipedia.org/wiki/Maximum_principle) is respected for a wide range of grids. It's therefore considered as the state of the art method for porous media flow problems on quadrilatereal grids.
7 |
8 | The code requires numpy for assembling discretization matrices, for running the convergence tests one also needs scipy and sympy.
9 | # Quick tutorial #
10 | The way to define a domain and discretize it into control volumes, is to discretize the unit square with rectangles, then perturb the grid points. This approach is very flexible and alows for complicated domains and control volume discretizations.
11 |
12 | ```python
13 | from discretization.mesh import Mesh
14 | import numpy as np
15 |
16 | nx = ny = 6 #Number of grid points in x and y direction on the unit square
17 | perturbation = lambda p:np.array([p[0],0.5*p[0]+p[1]]) #perturbation on every grid point p
18 | mesh = Mesh(nx,ny,perturbation,ghostboundary=True) #Creates a mesh object with a strip of ghostcells for boundary handling
19 | mesh.plot()
20 | ```
21 | This would result in the parallelogram discretization, note that we have 8 grid points (in orange) in each direction, 2 more than 6, as we have a strip of ghost cells.
22 |
23 | 
24 |
25 | For solving the [Poisson equation ](https://en.wikipedia.org/wiki/Poisson%27s_equation) on this, one would define the problem data with numpy arrays and python functions, then pass it to the compute_matrix and compute_vector functions, together with the mesh object. In this example we have a homogeneous domain (permeability is a matrix of ones), and a isotropic medium (tensor is diagonal).
26 | ```python
27 | from discretization.FVML import compute_matrix,compute_vector
28 | import math
29 |
30 | source = lambda x , y : math.sin(y)*math.cos(x)
31 | boundary_condition = lambda x , y :0
32 | tensor = np.eye(2)
33 | permeability = np.ones(( mesh.num_unknowns,mesh.num_unknowns))
34 |
35 | A = np.zeros((mesh.num_unknowns,mesh.num_unknowns))# stiffness matrix
36 | f = np.zeros(mesh . num_unknowns)# load vector
37 |
38 | compute_matrix(mesh,A,tensor,permeability)
39 | compute_vector(mesh,f,source,boundary_condition)
40 |
41 | u = np.linalg.solve(A,f)
42 | mesh.plot_vector(u)
43 | ```
44 | This would result in the solution
45 |
46 | 
47 |
48 |
49 | For more interesting equations, such as the time dependent, non-linear [Richards' equation](https://en.wikipedia.org/wiki/Richards_equation), see [this file](richards_non_linear.py).
50 |
--------------------------------------------------------------------------------
/discretization/FEM.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import math
3 | import sympy as sym
4 | def compute_matrix(mesh,matrix,K,k_global = None):
5 | """Assembles MPFA-L stiffness matrix.
6 |
7 | Parameters
8 | ----------
9 | mesh : Mesh
10 | A mesh object with nodes, cellcenters, normal vectors, midpoints etc.
11 |
12 | matrix : NxN matrix handle.
13 | Could be numpy or scipy square matrix with number of rows, N equal to the degrees of freedom.
14 | K : 2x2 numpy array
15 | The permeability tensor. This is constant across the domain.
16 | k_global : N dimensional numpy vector.
17 | This specifies the scalar permeability at each point. Should be layd out in fortran ordering.
18 | """
19 | elements = mesh.elements.astype(int)
20 | coordinates = np.reshape(mesh.cell_centers,(mesh.num_unknowns,2),order='C')
21 | boundary_elements_dirichlet = mesh.boundary_elements
22 | if k_global is None:
23 | k_global = np.ones((mesh.cell_centers.shape[0],mesh.cell_centers.shape[1]))
24 | k_global = np.ravel(k_global)
25 | shape_grad = np.array([np.matrix([[1],[0]]),np.matrix([[0],[1]]),np.matrix([[-1],[-1]])])
26 |
27 | def local_to_reference_map(ele_num):
28 | mat_coords = np.array([[coordinates[elements[ele_num,0]][0],coordinates[elements[ele_num,0]][1],1],[coordinates[elements[ele_num,1]][0],coordinates[elements[ele_num,1]][1],1],[coordinates[elements[ele_num,2]][0],coordinates[elements[ele_num,2]][1],1]])
29 | b1 = np.array([[1],[0],[0]])
30 | b2 = np.array([[0],[1],[0]])
31 | a1 = np.linalg.solve(mat_coords,b1)
32 | a2 = np.linalg.solve(mat_coords,b2)
33 | J = np.matrix([[a1[0][0],a1[1][0]],[a2[0][0],a2[1][0]]])
34 | c = np.matrix([[a1[2][0]],[a2[2][0]]])
35 | return [J,c]
36 | def reference_to_local_map(ele_num):
37 | mat_coords = np.array([[1,0,1],[0,1,1],[0,0,1]])
38 | b1 = np.array([[coordinates[elements[ele_num][0]][0]],[coordinates[elements[ele_num][1]][0]],[coordinates[elements[ele_num][2]][0]]])
39 | b2 = np.array([[coordinates[elements[ele_num][0]][1]],[coordinates[elements[ele_num][1]][1]],[coordinates[elements[ele_num][2]][1]]])
40 | a1 = np.linalg.solve(mat_coords,b1)
41 | a2 = np.linalg.solve(mat_coords,b2)
42 | J = np.matrix([[a1[0][0],a1[1][0]],[a2[0][0],a2[1][0]]])
43 | c = np.matrix([[a1[2][0]],[a2[2][0]]])
44 | return [J,c]
45 |
46 | # Matrix assembly
47 | for e in range(len(elements)):
48 | # extract element information
49 | [J,c] = local_to_reference_map(e)
50 | [M,d] = reference_to_local_map(e)
51 | transform = J.dot(J.transpose()) #J*J^t; derivative transformation
52 | jac = np.linalg.det(J) #Determinant of tranformation matrix = inverse of area of local elements
53 | #Local assembler
54 | for j in range(3):
55 | for i in range(3):
56 | x_r = M@np.array([[1/3],[1/3]])+d
57 | K_int = k_global[elements[e][2]]*(1-x_r[0]-x_r[1])+k_global[elements[e][0]]*x_r[0]+k_global[elements[e][1]]*x_r[1]
58 | # K_int = (k_global[elements[e][2]]+k_global[elements[e][0]]+k_global[elements[e][1]])/3
59 | # K_int = 0.5*k_global[elements[e][j]]
60 | matrix[elements[e][i],elements[e][j]] += K_int*0.5*shape_grad[i].transpose().dot(K@transform.dot(shape_grad[j]))/jac
61 | for e in range(len(boundary_elements_dirichlet)):
62 | matrix[boundary_elements_dirichlet[e][0],:]=0
63 | matrix[boundary_elements_dirichlet[e][0],boundary_elements_dirichlet[e][0]]=1
64 | matrix[boundary_elements_dirichlet[e][1],:]=0
65 | matrix[boundary_elements_dirichlet[e][1],boundary_elements_dirichlet[e][1]]=1
66 | return matrix
67 |
68 | def compute_vector(mesh,vector,f,boundary):
69 | x = sym.Symbol('x')
70 | y = sym.Symbol('y')
71 | #s = sym.Symbol('s')
72 |
73 | elements = mesh.elements.astype(int)
74 | coordinates = np.reshape(mesh.cell_centers,(mesh.num_unknowns,2),order='C')
75 | shape_func = np.array([x,y,1-y-x])
76 | #shape_func_1d = np.array([0.5-0.5*x,0.5+0.5*x])
77 |
78 | boundary_elements_dirichlet = mesh.boundary_elements
79 |
80 | def local_to_reference_map(ele_num):
81 | mat_coords = np.array([[coordinates[elements[ele_num][0]][0],coordinates[elements[ele_num][0]][1],1],[coordinates[elements[ele_num][1]][0],coordinates[elements[ele_num][1]][1],1],[coordinates[elements[ele_num][2]][0],coordinates[elements[ele_num][2]][1],1]])
82 | b1 = np.array([[1],[0],[0]])
83 | b2 = np.array([[0],[1],[0]])
84 | a1 = np.linalg.solve(mat_coords,b1)
85 | a2 = np.linalg.solve(mat_coords,b2)
86 | J = np.matrix([[a1[0][0],a1[1][0]],[a2[0][0],a2[1][0]]])
87 | c = np.matrix([[a1[2][0]],[a2[2][0]]])
88 | return [J,c]
89 |
90 | def reference_to_local_map(ele_num):
91 | mat_coords = np.array([[1,0,1],[0,1,1],[0,0,1]])
92 | b1 = np.array([[coordinates[elements[ele_num][0]][0]],[coordinates[elements[ele_num][1]][0]],[coordinates[elements[ele_num][2]][0]]])
93 | b2 = np.array([[coordinates[elements[ele_num][0]][1]],[coordinates[elements[ele_num][1]][1]],[coordinates[elements[ele_num][2]][1]]])
94 | a1 = np.linalg.solve(mat_coords,b1)
95 | a2 = np.linalg.solve(mat_coords,b2)
96 | J = np.matrix([[a1[0][0],a1[1][0]],[a2[0][0],a2[1][0]]])
97 | c = np.matrix([[a1[2][0]],[a2[2][0]]])
98 | return [J,c]
99 |
100 | def quad_2d_2nd_order_shape(ele_num, f, loc_node):
101 | [J,c] = reference_to_local_map(ele_num)
102 | x_1 = J.dot(np.array([[1/6],[1/6]]))+c
103 | x_2 = J.dot(np.array([[2/3],[1/6]]))+c
104 | x_3 = J.dot(np.array([[1/6],[2/3]]))+c
105 | return (1/6)*(f(x_1[0][0],x_1[1][0])*shape_func[loc_node].subs([(x,1/6),(y,1/6)])+f(x_2[0][0],x_2[1][0])*shape_func[loc_node].subs([(x,2/3),(y,1/6)])+f(x_3[0][0],x_3[1][0])*shape_func[loc_node].subs([(x,1/6),(y,2/3)]))
106 | # #Parametrizes straight boundary segment to [-1,1]
107 | # def param_1d_ele(ele_num):
108 | # return np.array([[coordinates[boundary_elements_neumann[ele_num][0]][0]+(coordinates[boundary_elements_neumann[ele_num][1]][0]-coordinates[boundary_elements_neumann[ele_num][0]][0])*0.5*(s+1)],[coordinates[boundary_elements_neumann[ele_num][0]][1]+(coordinates[boundary_elements_neumann[ele_num][1]][1]-coordinates[boundary_elements_neumann[ele_num][0]][1])*0.5*(s+1)]])
109 |
110 | # #Calculates length of 1d interval
111 | # def param_1d_ele_derivative(ele_num):
112 | # #return math.sqrt((coordinates[boundary_elements_neumann[ele_num][0]][0]-coordinates[boundary_elements_neumann[ele_num][0]][1])^2+(coordinates[boundary_elements_neumann[ele_num][1]][0]-coordinates[boundary_elements_neumann[ele_num][1]][1])^2)
113 | # return 0.5*math.sqrt((coordinates[boundary_elements_neumann[ele_num][0]][0]-coordinates[boundary_elements_neumann[ele_num][1]][0])**2+(coordinates[boundary_elements_neumann[ele_num][0]][1]-coordinates[boundary_elements_neumann[ele_num][1]][1])**2)
114 | # # Second order quadrature on boundary line integral
115 | # def quad_2nd_ord_line(f,ele_num,loc_node):
116 | # r = param_1d_ele(ele_num)
117 | # dr = param_1d_ele_derivative(ele_num)
118 | # x_1 = r[0][0].subs(s,-1/math.sqrt(3))
119 | # x_2 = r[0][0].subs(s,1/math.sqrt(3))
120 | # y_1 = r[1][0].subs(s,-1/math.sqrt(3))
121 | # y_2 = r[1][0].subs(s,1/math.sqrt(3))
122 | # return (f(x_1, y_1)*shape_func_1d[loc_node].subs(x,-1/math.sqrt(3))+f(x_2,y_2)*shape_func_1d[loc_node].subs(x,1/math.sqrt(3)))*dr
123 | for e in range(len(elements)):
124 | # extract element information
125 | [J,c] = local_to_reference_map(e)
126 | jac = np.linalg.det(J) #Determinant of tranformation matrix = inverse of area of local elements
127 | #Local assembler
128 | for j in range(3):
129 | vector[elements[e][j]] = float(vector[elements[e][j]]) + quad_2d_2nd_order_shape(e,f,j)/jac
130 | for e in range(len(boundary_elements_dirichlet)):
131 | vector[boundary_elements_dirichlet[e][0]]=boundary(coordinates[boundary_elements_dirichlet[e][0]][0], coordinates[boundary_elements_dirichlet[e][0]][1])
132 | vector[boundary_elements_dirichlet[e][1]]=boundary(coordinates[boundary_elements_dirichlet[e][1]][0], coordinates[boundary_elements_dirichlet[e][1]][1])
133 | return vector
134 |
135 |
--------------------------------------------------------------------------------
/discretization/FVML.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | def compute_matrix(mesh, matrix,K,k_global=None,flux_matrix = None):
4 | """Assembles MPFA-L stiffness matrix.
5 |
6 | Parameters
7 | ----------
8 | mesh : Mesh
9 | A mesh object with nodes, cellcenters, normal vectors, midpoints etc.
10 |
11 | matrix : NxN matrix handle.
12 | Could be numpy or scipy square matrix with number of rows, N equal to the degrees of freedom.
13 | K : 2x2 numpy array
14 | The permeability tensor. This is constant across the domain.
15 | k_global : N dimensional numpy vector.
16 | This specifies the scalar permeability at each point. Should be layd out in fortran ordering.
17 | flux_matrix : two NxN matrix handles
18 | This is to recover the flux.
19 |
20 | """
21 | nodes = mesh.nodes
22 | cell_centers = mesh.cell_centers
23 | if k_global is None:
24 | k_global = np.ones((cell_centers.shape[0],cell_centers.shape[1]))
25 |
26 | nx = nodes.shape[1]
27 | ny = nodes.shape[0]
28 |
29 | num_unknowns = cell_centers.shape[1]*cell_centers.shape[0]
30 |
31 | meshToVec = mesh.meshToVec
32 | if flux_matrix is not None:
33 | flux_matrix_x = flux_matrix['x']
34 | flux_matrix_y = flux_matrix['y']
35 |
36 | R = np.array([[0,1],[-1,0]])
37 |
38 | T = np.zeros((4,2,3))
39 | omega = np.zeros((2,3,7))
40 | V = np.zeros((7,2))
41 |
42 | interface = np.zeros((4,2))
43 | centers = np.zeros((4,2))
44 | n = np.zeros((4,2))
45 | k_loc = np.zeros((4))
46 |
47 | def local_assembler(j,i,vec,start, matrix_handle,index):
48 | global_vec = np.zeros(num_unknowns)
49 |
50 | indexes = [meshToVec(j-1,i-1),meshToVec(j-1,i),meshToVec(j,i),meshToVec(j,i-1)]
51 |
52 |
53 | for ii,jj in zip(range(start,start+2),range(2)):
54 |
55 | matrix_handle[index,indexes[ii%4]] += vec[jj]
56 |
57 | matrix_handle[index,indexes[(start-1)%4]] += vec[2]
58 | return global_vec
59 |
60 | def compute_triangle_normals(start_index, interface, centers, node_midpoint,V):
61 | V[0,:] = R@(interface[(start_index-1)%4,:]-centers[start_index%4])
62 | V[1,:] = -R@(interface[start_index%4,:]-centers[start_index%4])
63 | V[2,:] = R@(interface[start_index%4,:]-centers[(start_index+1)%4])
64 | V[3,:] = -R@(nodes[i,j]-centers[(start_index+1)%4])
65 | V[4,:] = R@(nodes[i,j]-centers[(start_index-1)%4])
66 | V[5,:] = -R@(interface[(start_index-1)%4,:]-centers[(start_index-1)%4])
67 | V[6,:] = R@(nodes[i,j]-centers[start_index%4])
68 |
69 |
70 | # @jit(fastmath=True) #halfes the running time for matrix assembly, but no tensor permability
71 | def compute_omega(n,K,V,t,omega,center,k_loc_rel):
72 | for ii in range(2):
73 | for jj in range(3):
74 | for kk in range(7):
75 | if ii == 0:
76 | omega[ii,jj,kk] = n[center,:].T.dot(V[kk,:]*1/t[jj])*k_loc_rel[jj]
77 | else:
78 | omega[ii,jj,kk] = n[center-1,:].T.dot(V[kk,:]*1/t[jj])*k_loc_rel[jj]
79 |
80 | def compute_T(center,k_loc):
81 | compute_triangle_normals(center,interface,centers,nodes[i,j],V)
82 | t = np.array([V[0,:].T@R@V[1,:],V[2,:].T@R@V[3,:],V[4,:].T@R@V[5,:]])
83 | k_loc_rel = np.array([k_loc[center],k_loc[(center+1)%4],k_loc[(center-1)%4]])
84 | compute_omega(n,K,V,t,omega,center,k_loc_rel)
85 |
86 | xi_1 = (V[6,:].T@R@V[0,:])/(V[0,:].T@R@V[1,:])
87 | xi_2 = (V[6,:].T@R@V[1,:])/(V[0,:].T@R@V[1,:])
88 |
89 | C = np.array([[-omega[0,0,0], -omega[0,0,1]],
90 | [-omega[1,0,0], -omega[1,0,1]]])
91 |
92 | D = np.array([[omega[0,0,0]+omega[0,0,1], 0, 0],
93 | [omega[1,0,0]+omega[1,0,1], 0, 0]])
94 |
95 | A = np.array([[omega[0,0,0]-omega[0,1,3]-omega[0,1,2]*xi_1, omega[0,0,1]-omega[0,1,2]*xi_2],
96 | [omega[1,0,0]-omega[1,2,5]*xi_1, omega[1,0,1]-omega[1,2,4]-omega[1,2,5]*xi_2]])
97 |
98 | B = np.array([[omega[0,0,0]+omega[0,0,1]+omega[0,1,2]*(1-xi_1-xi_2), -omega[0,1,2]-omega[0,1,3], 0],
99 | [omega[1,0,0]+omega[1,0,1]+omega[1,2,5]*(1-xi_1-xi_2), 0, -omega[1,2,4]-omega[1,2,5]]])
100 |
101 | T = C@np.linalg.inv(A)@B+D
102 | return T
103 |
104 | def choose_triangle(T,i):
105 | if abs(T[i,0,0])cell_centers.shape[1],range(self.num_unknowns)):
130 | elements[e,:] = np.array([int(i),int(i+cell_centers.shape[1]+1),int(i+cell_centers.shape[1])],dtype=int)
131 | e = e + 1
132 | elements[e,:] = np.array([int(i),int(i+1),int(i+cell_centers.shape[1]+1)],dtype=int)
133 | e = e + 1
134 | boundary_elements = ['n','n','n','n']
135 | for point, boundary_point,index in zip(points,boundary,range(points.shape[0])):
136 | if boundary_point != 0:
137 | boundary_loc = int(boundary_point-1)
138 | if type(boundary_elements[boundary_loc])==str:
139 | boundary_elements[boundary_loc] = int(index)
140 | elif isinstance(boundary_elements[boundary_loc],int):
141 | boundary_elements[boundary_loc] = np.array([[boundary_elements[boundary_loc],index]])
142 | else:
143 | boundary_elements[boundary_loc] = np.concatenate((boundary_elements[boundary_loc],np.array([[boundary_elements[boundary_loc][boundary_elements[boundary_loc].shape[0]-1,1],index]])))
144 | bottom = boundary_elements[0]
145 | right = boundary_elements[1]
146 | top = boundary_elements[2]
147 | left = boundary_elements[3]
148 | bottom = np.concatenate((np.array([[left[0,0],bottom[0,0]]]),bottom,np.array([[bottom[bottom.shape[0]-1,1],right[0,0]]])))
149 | top= np.concatenate((np.array([[left[left.shape[0]-1,1],top[0,0]]]),top,np.array([[top[top.shape[0]-1,1],right[right.shape[0]-1,1]]])))
150 | boundary_elements = np.concatenate((bottom,right,top,left))
151 | return (elements,boundary_elements)
152 | T = Delaunay(points)
153 | boundary_elements = ['n','n','n','n']
154 | for point, boundary_point,index in zip(points,boundary,range(points.shape[0])):
155 | if boundary_point != 0:
156 | boundary_loc = int(boundary_point-1)
157 | if type(boundary_elements[boundary_loc])==str:
158 | boundary_elements[boundary_loc] = int(index)
159 | elif isinstance(boundary_elements[boundary_loc],int):
160 | boundary_elements[boundary_loc] = np.array([[boundary_elements[boundary_loc],index]])
161 | else:
162 | boundary_elements[boundary_loc] = np.concatenate((boundary_elements[boundary_loc],np.array([[boundary_elements[boundary_loc][boundary_elements[boundary_loc].shape[0]-1,1],index]])))
163 | bottom = boundary_elements[0]
164 | right = boundary_elements[1]
165 | top = boundary_elements[2]
166 | left = boundary_elements[3]
167 | bottom = np.concatenate((np.array([[left[0,0],bottom[0,0]]]),bottom,np.array([[bottom[bottom.shape[0]-1,1],right[0,0]]])))
168 | top= np.concatenate((np.array([[left[left.shape[0]-1,1],top[0,0]]]),top,np.array([[top[top.shape[0]-1,1],right[right.shape[0]-1,1]]])))
169 | boundary_elements = np.concatenate((bottom,right,top,left))
170 | boundary_set = set(boundary_elements.flatten())
171 | elements = T.simplices
172 | remove_list = []
173 | for i in range(elements.shape[0]):
174 | if elements[i,0] in boundary_set and elements[i,1] in boundary_set and elements[i,2] in boundary_set:
175 | remove_list.append(i)
176 | elements = np.delete(elements,remove_list,0)
177 | return (elements,boundary_elements)
178 |
179 | def max_h(self):
180 | m = 0
181 | for i in range(self.nodes.shape[0]-1):
182 | for j in range(self.nodes.shape[1]-1):
183 | up = np.linalg.norm(self.nodes[i+1,j,:]-self.nodes[i,j,:])
184 | right = np.linalg.norm(self.nodes[i,j+1,:]-self.nodes[i,j,:])
185 | diag1 = np.linalg.norm(self.nodes[i+1,j+1,:]-self.nodes[i,j,:])
186 | diag2 = np.linalg.norm(self.nodes[i+1,j,:]-self.nodes[i,j+1,:])
187 | m = max((m,up,right,diag1,diag2))
188 | return m
189 |
190 |
191 | def plot(self):
192 | plt.scatter(self.cell_centers[:,:,0],self.cell_centers[:,:,1])
193 | plt.scatter(self.nodes[:,:,0], self.nodes[:,:,1])
194 | # plt.scatter(self.BC_nodes[:,:,0],self.BC_nodes[:,:,1])
195 | # plt.scatter(self.BC_midpoints[:,:,0],self.BC_midpoints[:,:,1])
196 | segs1 = np.stack((self.nodes[:,:,0],self.nodes[:,:,1]), axis=2)
197 | segs2 = segs1.transpose(1,0,2)
198 | plt.gca().add_collection(LineCollection(segs1))
199 | plt.gca().add_collection(LineCollection(segs2))
200 | # plt.quiver(*self.midpoints[1,1,0,:],self.normals[1,1,0,0],self.normals[1,1,0,1])
201 | # plt.quiver(*self.midpoints[1,1,1,:],self.normals[1,1,1,0],self.normals[1,1,1,1])
202 | #plt.savefig('perturbed_grid_aspect_0.2_mesh.pdf')
203 | # points = np.reshape(self.cell_centers,(self.cell_centers.shape[0]*self.cell_centers.shape[1],2))
204 | # plt.triplot(points[:,0],points[:,1],self.elements)
205 |
206 | plt.show()
207 |
208 | def meshToVec(self,j:int,i:int)->int:
209 | return i*self.cell_centers.shape[0] + j
210 |
211 | def vecToMesh(self,h:int)->Tuple[int,int]:
212 | return (h % self.cell_centers.shape[0], math.floor(h/self.cell_centers.shape[0]))
213 |
214 | def plot_vector(self,vec,text = 'text'):
215 | vec_center = np.zeros((self.cell_centers.shape[0],self.cell_centers.shape[1]))
216 | num_unknowns = self.cell_centers.shape[1]*self.cell_centers.shape[0]
217 | for i in range(num_unknowns):
218 | vec_center[self.vecToMesh(i)] = vec[i]
219 | fig = plt.figure(figsize=plt.figaspect(0.5))
220 | plt.contourf(self.cell_centers[:,:,0],self.cell_centers[:,:,1],vec_center,20,)
221 | plt.colorbar()
222 | plt.show()
223 | def plot_interaction(self):
224 | #plot nodes
225 | plt.scatter(self.nodes[:,:,0], self.nodes[:,:,1])
226 | segs1 = np.stack((self.nodes[:,:,0],self.nodes[:,:,1]), axis=2)
227 | segs2 = segs1.transpose(1,0,2)
228 | plt.gca().add_collection(LineCollection(segs1))
229 | plt.gca().add_collection(LineCollection(segs2))
230 |
231 | centers = self.cell_centers
232 | points = np.zeros((2*centers.shape[0]-1,centers.shape[1],2))
233 | for i in range(centers.shape[1]-1):
234 | points[2*i,:,:] = centers[i,:,:]
235 | points[2*i+1,:,:] = self.midpoints[i,:,0,:]
236 | segs1 = np.stack((points[:,:,0],points[:,:,1]),axis=2)
237 | segs2 = segs1.transpose(1,0,2)
238 | plt.gca().add_collection(LineCollection(segs2,color='g',linestyle='dashed'))
239 | plt.show()
240 |
241 |
242 |
243 | def plot_funtion(self,fun,text = 'text'):
244 | vec_center = np.zeros((self.cell_centers.shape[0],self.cell_centers.shape[1]))
245 | num_unknowns = self.cell_centers.shape[1]*self.cell_centers.shape[0]
246 | for i in range(num_unknowns):
247 | xx,yy = self.cell_centers[self.vecToMesh(i)]
248 | vec_center[self.vecToMesh(i)] = fun(xx,yy)
249 | fig = plt.figure(figsize=plt.figaspect(0.5))
250 | plt.contourf(self.cell_centers[:,:,0],self.cell_centers[:,:,1],vec_center,20,)
251 | plt.colorbar()
252 | # fig.suptitle(text)
253 |
254 | plt.show()
255 |
256 | def interpolate(self,fun):
257 | u = np.zeros(self.num_unknowns)
258 | for i in range(self.cell_centers.shape[0]):
259 | for j in range(self.cell_centers.shape[1]):
260 | x = self.cell_centers[i,j,0]
261 | y = self.cell_centers[i,j,1]
262 | u[self.meshToVec(i,j)] = fun(x,y)
263 | return u
264 |
265 | def compute_error(self,u,u_exact):
266 | u_exact_vec = u.copy()
267 | volumes = u.copy()
268 | for i in range(self.cell_centers.shape[0]):
269 | for j in range(self.cell_centers.shape[1]):
270 | u_exact_vec[self.meshToVec(i,j)] = u_exact(self.cell_centers[i,j,0],self.cell_centers[i,j,1])
271 | volumes[self.meshToVec(i,j)] = self.volumes[i,j]
272 | L2_error = math.sqrt(np.square(u-u_exact_vec).T@volumes/(np.ones(volumes.shape)@volumes))
273 | max_error = np.max(np.abs(u-u_exact_vec))
274 | return(L2_error,max_error)
275 |
276 | if __name__=="__main__":
277 | mesh = Mesh(5,5,lambda p:np.array(p[1]**2+[p[0],p[1]]))
278 | print(mesh.BC_nodes)
279 | mesh.plot()
280 |
281 |
--------------------------------------------------------------------------------
/discretization/operators.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from scipy.sparse import csr_matrix,lil_matrix, diags
3 | from scipy.sparse.linalg import spsolve
4 |
5 | def mass_matrix(mesh, sparse = False):
6 | """
7 | Constructs diagonal mass matrix
8 | """
9 | if sparse:
10 | mass = diags(np.ravel(mesh.volumes,order='F'),format='lil')
11 | else:
12 | mass = np.diag(np.ravel(mesh.volumes,order='F'))
13 | for i in range(mesh.cell_centers.shape[0]):
14 | for j in range(mesh.cell_centers.shape[1]):
15 | if (i==0) or (i==mesh.num_nodes_x-2) or (j==0) or (j==mesh.num_nodes_y-2):
16 | mass[mesh.meshToVec(i,j),:] = 0
17 | if sparse:
18 | return csr_matrix(mass,dtype=float)
19 | else:
20 | return mass
21 |
22 | def gravitation_matrix(mesh,sparse = False):
23 | if sparse:
24 | matrix = lil_matrix((mesh.num_unknowns,mesh.num_unknowns))
25 | else:
26 | matrix = np.zeros((mesh.num_unknowns,mesh.num_unknowns))
27 | nodes = mesh.nodes
28 | normals = mesh.normals
29 | gravity = np.array([0,1])
30 | meshToVec = mesh.meshToVec
31 | for i in range(1,nodes.shape[0]-2):
32 | for j in range(1,nodes.shape[1]-2):
33 | flux_e = 2*normals[i,j,0,:].T@gravity
34 | flux_s = 2*normals[i,j,1,:].T@gravity
35 | flux_w = 2*normals[i,j+1,0,:].T@gravity
36 | flux_n = 2*normals[i+1,j,1,:].T@gravity
37 | matrix[meshToVec(i,j),meshToVec(i,j)] += -flux_e - flux_s + flux_n + flux_w
38 | matrix[meshToVec(i,j),meshToVec(i-1,j)] += flux_s
39 | matrix[meshToVec(i,j),meshToVec(i,j-1)] += flux_e
40 | matrix[meshToVec(i,j),meshToVec(i,j+1)] -= flux_w
41 | matrix[meshToVec(i,j),meshToVec(i+1,j)] -= flux_n
42 | if (i==0) or (i==nodes.shape[0]-2) or (j==0) or (j==nodes.shape[1]-2):
43 | matrix[meshToVec(i,j),:] = 0
44 | if sparse:
45 | return csr_matrix(matrix,dtype=float)
46 | else:
47 | return matrix
--------------------------------------------------------------------------------
/elliptic_convergence.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import sympy as sym
3 | import math
4 | import random
5 | from scipy.sparse import csr_matrix,lil_matrix
6 | from scipy.sparse.linalg import spsolve
7 | import matplotlib.pyplot as plt
8 |
9 | from discretization.FVML import compute_matrix as compute_matrix_l
10 | from discretization.FVMO import compute_matrix as compute_matrix_o
11 | from discretization.TPFA import compute_matrix as compute_matrix_tpfa
12 | from discretization.FVMO import compute_vector
13 | from discretization.FEM import compute_matrix as compute_matrix_FEM
14 | from discretization.FEM import compute_vector as compute_vector_FEM
15 | from discretization.mesh import Mesh
16 |
17 | from utils.flux_error import compute_flux_error
18 | from utils.differentiation import divergence, gradient
19 | """
20 | The code used for the figures in section 5.2
21 | """
22 |
23 | def compute_error(mesh,u,u_fabric):
24 | cx = mesh.cell_centers.shape[1]
25 | cy = mesh.cell_centers.shape[0]
26 | u_fabric_vec = u.copy()
27 | volumes = u.copy()
28 |
29 | for i in range(cy):
30 | for j in range(cx):
31 | u_fabric_vec[mesh.meshToVec(i,j)] = u_fabric(mesh.cell_centers[i,j,0],mesh.cell_centers[i,j,1])
32 | volumes[mesh.meshToVec(i,j)] = mesh.volumes[i,j]
33 | #mesh.plot_vector(u-u_fabric_vec,'error')
34 | L2err = math.sqrt(np.square(u-u_fabric_vec).T@volumes/(np.ones(volumes.shape).T@volumes))
35 | maxerr = np.max(np.abs(u-u_fabric_vec))
36 | return (L2err,maxerr)
37 |
38 | def random_perturbation(h,aspect):
39 | return lambda p: np.array([random.uniform(0,h)*random.choice([-1,1]) + p[0] - 0.5*p[1],(1/aspect)*random.uniform(0,h)*random.choice([-1,1]) + p[1]])
40 |
41 | def chevron_perturbation(n):
42 | return lambda p: np.array([p[0],p[1]*0.5 + 0.5*(p[1]-1)*p[1]*0.3*math.sin(n*p[0])])
43 |
44 | x = sym.Symbol('x')
45 | y = sym.Symbol('y')
46 | K = np.array([[1,0],[0,1]])
47 | u_fabric = sym.cos(y*math.pi)*sym.cosh(x*math.pi)
48 | source = -divergence(gradient(u_fabric,[x,y]),[x,y],permability_tensor=K)
49 | source = sym.lambdify([x,y],source)
50 | u_lam = sym.lambdify([x,y],u_fabric)
51 |
52 |
53 |
54 |
55 |
56 | start = 3
57 | end = 5
58 | aspect = 1
59 | result_pressure = np.zeros((end-start,9))
60 | result_flux = np.zeros((end-start,9))
61 |
62 | for i in range(start,end):
63 | result_pressure[i-start,0] = i
64 | result_flux[i-start,0] = i
65 | mesh = Mesh(2**i,aspect*2**i,random_perturbation(1/(4*2**i),aspect),ghostboundary=True)
66 | # mesh = Mesh(2**i,2*2**i,chevron_perturbation(2*2**i),ghostboundary=True)
67 | #mesh = Mesh(2**i,2**i,T)
68 | num_unknowns = mesh.num_unknowns
69 |
70 |
71 | #FVM-L
72 | A = lil_matrix((mesh.num_unknowns,mesh.num_unknowns))
73 | F = np.zeros(num_unknowns)
74 | flux_matrix = {'x': lil_matrix((num_unknowns,num_unknowns)),'y':lil_matrix((num_unknowns,num_unknowns))}
75 | compute_matrix_l(mesh,A,K,k_global=None,flux_matrix = flux_matrix)
76 | A = csr_matrix(A,dtype=float)
77 | compute_vector(mesh,F,source,u_lam)
78 | u = spsolve(A,F)
79 | fx = csr_matrix(flux_matrix['x'],dtype=float)
80 | fy = csr_matrix(flux_matrix['y'],dtype=float)
81 | l2err_flux,maxerr_flux = compute_flux_error(fx.dot(u),fy.dot(u),u_fabric,mesh)
82 | l2err,maxerr = compute_error(mesh,u,u_lam)
83 | result_pressure[i-start,1] = math.log(l2err,2)
84 | result_pressure[i-start,2] = math.log(maxerr,2)
85 | result_flux[i-start,1] = math.log(l2err_flux,2)
86 | result_flux[i-start,2] = math.log(maxerr_flux,2)
87 |
88 |
89 | #FEM
90 | A = lil_matrix((mesh.num_unknowns,mesh.num_unknowns))
91 | F = np.zeros(num_unknowns)
92 | compute_matrix_FEM(mesh,A,K,k_global=None)
93 | A = csr_matrix(A,dtype=float)
94 | f = compute_vector_FEM(mesh,F,source,u_lam)
95 | u = spsolve(A,f)
96 | u = np.reshape(u,(mesh.cell_centers.shape[0],mesh.cell_centers.shape[1]))
97 | u = np.ravel(u,order='F')
98 |
99 | l2err_flux,maxerr_flux = compute_flux_error(fx.dot(u),fy.dot(u),u_fabric,mesh)
100 | l2err,maxerr = compute_error(mesh,u,u_lam)
101 | result_pressure[i-start,7] = math.log(l2err,2)
102 | result_pressure[i-start,8] = math.log(maxerr,2)
103 | result_flux[i-start,7] = math.log(l2err_flux,2)
104 | result_flux[i-start,8] = math.log(maxerr_flux,2)
105 |
106 | #FVM-O
107 | A = lil_matrix((mesh.num_unknowns,mesh.num_unknowns))
108 | F = np.zeros(num_unknowns)
109 | flux_matrix = {'x': lil_matrix((num_unknowns,num_unknowns)),'y':lil_matrix((num_unknowns,num_unknowns))}
110 | compute_matrix_o(mesh,A,K,k_global=None,flux_matrix = flux_matrix)
111 | A = csr_matrix(A,dtype=float)
112 | compute_vector(mesh,F,source,u_lam)
113 | u = spsolve(A,F)
114 | fx = csr_matrix(flux_matrix['x'],dtype=float)
115 | fy = csr_matrix(flux_matrix['y'],dtype=float)
116 | l2err_flux,maxerr_flux = compute_flux_error(fx.dot(u),fy.dot(u),u_fabric,mesh)
117 | l2err,maxerr = compute_error(mesh,u,u_lam)
118 | result_pressure[i-start,3] = math.log(l2err,2)
119 | result_pressure[i-start,4] = math.log(maxerr,2)
120 | result_flux[i-start,3] = math.log(l2err_flux,2)
121 | result_flux[i-start,4] = math.log(maxerr_flux,2)
122 |
123 |
124 | #TPFA
125 | A = lil_matrix((mesh.num_unknowns,mesh.num_unknowns))
126 | F = np.zeros(num_unknowns)
127 | flux_matrix = {'x': lil_matrix((num_unknowns,num_unknowns)),'y':lil_matrix((num_unknowns,num_unknowns))}
128 | compute_matrix_tpfa(mesh,A,K,k_global=None,flux_matrix = flux_matrix)
129 | A = csr_matrix(A,dtype=float)
130 | compute_vector(mesh,F,source,u_lam)
131 | u = spsolve(A,F)
132 | fx = csr_matrix(flux_matrix['x'],dtype=float)
133 | fy = csr_matrix(flux_matrix['y'],dtype=float)
134 | l2err_flux,maxerr_flux = compute_flux_error(fx.dot(u),fy.dot(u),u_fabric,mesh)
135 | l2err,maxerr = compute_error(mesh,u,u_lam)
136 | result_pressure[i-start,5] = math.log(l2err,2)
137 | result_pressure[i-start,6] = math.log(maxerr,2)
138 | result_flux[i-start,5] = math.log(l2err_flux,2)
139 | result_flux[i-start,6] = math.log(maxerr_flux,2)
140 |
141 |
142 |
143 |
144 | print(result_pressure)
145 | print(result_flux)
146 |
147 | fig,(ax1,ax2) = plt.subplots(1,2,figsize=(10,5))
148 | fig.suptitle('potential error')
149 | p3, = ax1.plot(result_pressure[:,0],result_pressure[:,7],'-o',color='red')
150 | p3, = ax1.plot(result_pressure[:,0],result_pressure[:,3],'-v',color= 'g')
151 |
152 | p1, = ax1.plot(result_pressure[:,0],result_pressure[:,1],'--x',color='k')
153 | p3, = ax1.plot(result_pressure[:,0],result_pressure[:,5],'-*',color='y')
154 | ax1.set_title('$L_2$ error')
155 | ax1.grid()
156 | ax1.set(xlabel='$log_2 n$',ylabel='$log_2 e$')
157 |
158 |
159 | p4,=ax2.plot(result_pressure[:,0],result_pressure[:,8],'-o',color='red')
160 | p4.set_label('FEM')
161 | p4,=ax2.plot(result_pressure[:,0],result_pressure[:,4],'-v',color='g')
162 | p4.set_label('O-method')
163 | p2, = ax2.plot(result_pressure[:,0],result_pressure[:,2],'--x',color='k')
164 | p2.set_label('L-method')
165 | p4,=ax2.plot(result_pressure[:,0],result_pressure[:,6],'-*',color='y')
166 | p4.set_label('TPFA-method')
167 | ax2.grid()
168 | ax2.set_title('$max$ error')
169 |
170 | ax2.set(xlabel='$log_2 n$',ylabel='$log_2 e$')
171 | plt.legend(loc='lower center',bbox_to_anchor=(0.0, -0.3),ncol=4)
172 | fig.subplots_adjust(bottom=0.20)
173 | #plt.savefig('figs/pressure_chevron_grid.pdf')
174 |
175 | plt.show()
176 |
177 | fig,(ax1,ax2) = plt.subplots(1,2,figsize=(10,5))
178 | fig.suptitle('normal flow error')
179 | p3, = ax1.plot(result_flux[:,0],result_flux[:,7],'-o',color='r')
180 | p3, = ax1.plot(result_flux[:,0],result_flux[:,3],'-v',color='g')
181 |
182 | p1, = ax1.plot(result_flux[:,0],result_flux[:,1],'--x',color='k')
183 | p3, = ax1.plot(result_flux[:,0],result_flux[:,5],'-*',color='y')
184 |
185 | ax1.set_title('$L_2$ error')
186 | ax1.grid()
187 | ax1.set(xlabel='$log_2 n$',ylabel='$log_2 e$')
188 |
189 |
190 | p4,=ax2.plot(result_flux[:,0],result_flux[:,8],'-o',color='r')
191 | p4.set_label('FEM')
192 | p4,=ax2.plot(result_flux[:,0],result_flux[:,4],'-v',color='g')
193 | p4.set_label('O-method')
194 | p2, = ax2.plot(result_flux[:,0],result_flux[:,2],'--x',color='k')
195 | p2.set_label('L-method')
196 |
197 | p4,=ax2.plot(result_flux[:,0],result_flux[:,6],'-*',color='y')
198 | p4.set_label('TPFA-method')
199 |
200 | ax2.grid()
201 | ax2.set_title('$max$ error')
202 |
203 | ax2.set(xlabel='$log_2 n$',ylabel='$log_2 e$')
204 | plt.legend(loc='lower center',bbox_to_anchor=(0.0, -0.3),ncol=4)
205 | fig.subplots_adjust(bottom=0.20)
206 | #plt.savefig('figs/flow_chevron_grid.pdf')
207 |
208 | plt.show()
209 |
--------------------------------------------------------------------------------
/poisson.py:
--------------------------------------------------------------------------------
1 | from discretization.mesh import Mesh
2 | import numpy as np
3 | from discretization.FVML import compute_matrix,compute_vector
4 | import math
5 |
6 | nx = ny = 6
7 | perturbation = lambda p:np.array([p[0],0.5*p[0]+p[1]])
8 | mesh = Mesh(nx,ny,perturbation,ghostboundary=True)
9 |
10 | source = lambda x , y : math.sin(y)*math.cos(x)
11 | boundary_condition = lambda x , y :0
12 | tensor = np.eye(2)
13 | permeability = np.ones(( mesh.num_unknowns,mesh.num_unknowns))
14 |
15 | A = np.zeros((mesh.num_unknowns,mesh.num_unknowns))# stiffness matrix
16 | f = np.zeros(mesh . num_unknowns)# load vector
17 |
18 | compute_matrix(mesh,A,tensor,permeability)
19 | compute_vector(mesh,f,source,boundary_condition)
20 |
21 | u = np.linalg.solve(A,f)
22 | mesh.plot_vector(u)
--------------------------------------------------------------------------------
/richards_constant_hydraulic_conductivity.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import sympy as sym
3 | import math
4 |
5 | from discretization.FVML import compute_matrix , compute_vector
6 | from utils.differentiation import gradient, divergence
7 | from discretization.mesh import Mesh
8 | from discretization.operators import mass_matrix
9 | """
10 | The code used for table 5.1-5.3
11 | """
12 | def solve_richards(mesh: Mesh,timestep=None):
13 | #construct solution
14 | K = np.array([[1,0],[0,1]])#permeability tensor
15 | x = sym.Symbol('x')
16 | y = sym.Symbol('y')
17 | t = sym.Symbol('t')
18 | p = sym.Symbol('p')
19 | theta = 1/(1-p) #parametrized saturation
20 | u_exact = -t*x*(1-x)*y*(1-y)-1#constructed hydraulic head
21 |
22 | f = sym.diff(theta.subs(p,u_exact),t)-divergence(gradient(u_exact ,[x,y]),[x,y])#construct the force function given the exact solution
23 |
24 | #convert from sympy to regular python functions
25 | f = sym.lambdify([x,y,t],f)
26 | u_exact = sym.lambdify([x,y,t],u_exact)
27 | theta = sym.lambdify([p],theta)
28 |
29 | #time discretization of 0-1
30 | time_partition = np.linspace(0,1,math.ceil(1/timestep))
31 | tau = time_partition[1]-time_partition[0]
32 |
33 | #L-scheme parameters
34 | L = 1.2
35 | TOL = 0.0000000005
36 |
37 | u = mesh.interpolate(lambda x,y:u_exact(x,y,0))#initial condition
38 |
39 | #allocate storage
40 | u_l = u.copy() #L-scheme iterate
41 | u_t = u.copy() #timestep iterate
42 | F = u.copy() #source vector
43 | F.fill(0)
44 | A = np.zeros((mesh.num_unknowns,mesh.num_unknowns)) #stiffness matrix
45 | B = mass_matrix(mesh)
46 | compute_matrix(mesh, A, K)#compute stiffnes matrix
47 | lhs = L*B+tau*A
48 | transform = np.linalg.inv(lhs)
49 | #time iteration
50 | for t in time_partition[1:]:
51 | F.fill(0)#empty load vector
52 | compute_vector(mesh,F,lambda x,y: f(x,y,t),lambda x,y:u_exact(x,y,t))#compute load vector
53 | #L-scheme iteration
54 | while True:
55 | rhs = L*B@u_l + B@theta(u_t) - B@theta(u_l) + tau*F
56 | u = transform@rhs
57 | if np.linalg.norm(u-u_l)<=TOL+TOL*np.linalg.norm(u_l):
58 | break
59 | else:
60 | u_l = u
61 | u_t = u
62 | u_l = u
63 |
64 | return (mesh.max_h(),timestep,mesh.compute_error(u,lambda x,y : u_exact(x,y,1))[0])
65 |
66 | if __name__=="__main__":
67 | number_of_tests = 4
68 |
69 | result = []
70 | for i in range(1,number_of_tests+1):
71 | num_nodes = 2*2**i
72 | mesh = Mesh(1+num_nodes,1+num_nodes,lambda p: np.array([p[0]-0.5*p[1],p[1]]),ghostboundary=True)
73 | result.append(solve_richards(mesh,mesh.max_h()))
74 | output=''
75 | for i in range(number_of_tests):
76 | row = result[i]
77 | if i==0:
78 | output = f'{i+1}&${row[0]:.5f}$&${row[1]:.5f}$&${row[2]:.6f}$&-\\\ \n'
79 | else:
80 | old_row = result[i-1]
81 | improvement = old_row[2]/row[2]
82 | output += f'{i+1}&${row[0]:.5f}$&${row[1]:.5f}$&${row[2]:.6f}$&${improvement:.5f}$\\\ \n'
83 | print(output)
--------------------------------------------------------------------------------
/richards_non_linear.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import sympy as sym
3 | import math
4 | import matplotlib.pyplot as plt
5 |
6 | from discretization.FVML import compute_matrix , compute_vector
7 | from utils.differentiation import gradient, divergence
8 | from discretization.mesh import Mesh
9 | from discretization.operators import mass_matrix
10 | """
11 | The code used for table 5.4 and 5.5
12 | """
13 | def solve_richards(mesh: Mesh,timestep=None):
14 | #Van Genuchten parameteres
15 | a_g = 0.1844
16 | n_g = 3
17 | k_abs = 0.03
18 | my = 1
19 | exp_1 = n_g/(n_g-1)
20 | exp_2 = (n_g-1)/n_g
21 |
22 | #construct solution
23 | K = np.array([[1,0],[0,1]])#permeability tensor
24 | x = sym.Symbol('x')
25 | y = sym.Symbol('y')
26 | t = sym.Symbol('t')
27 | p = sym.Symbol('p')
28 | s = sym.Symbol('s')
29 | theta = sym.Piecewise(
30 | ((1+(-a_g*p)**n_g)**(-exp_2),p<=0),
31 | (1,p>0)
32 | ) #parametrized saturation
33 | kappa = ((k_abs/my)*s**(-1/2))*(1-(1-s**exp_1)**exp_2)**2#parametrized hydraulic conductivity as a function of saturation
34 | u_exact = -3*t*x*(1-x)*y*(1-y)-1#constructed hydraulic head
35 | f = sym.diff(theta.subs(p,u_exact),t)-divergence(gradient(u_exact ,[x,y]),[x,y],kappa.subs(s,theta.subs(p,u_exact)))#construct the force function given the exact solutio
36 |
37 | #convert from sympy to regular python functions
38 | f = sym.lambdify([x,y,t],f)
39 | u_exact = sym.lambdify([x,y,t],u_exact)
40 | kappa = sym.lambdify([p],kappa.subs(s,theta))
41 | theta = sym.lambdify([p],theta)
42 |
43 | #time discretization of 0-1
44 | time_partition = np.linspace(0,1,math.ceil(1/timestep))
45 | tau = time_partition[1]-time_partition[0]
46 |
47 | #L-scheme parameters
48 | L = 0.3
49 | TOL = 0.000000005
50 |
51 | u = mesh.interpolate(lambda x,y:u_exact(x,y,0))#initial condition
52 |
53 | #allocate storage
54 | u_l = u.copy() #L-scheme iterate
55 | u_t = u.copy() #timestep iterate
56 | F = u.copy() #source vector
57 | F.fill(0)
58 | A = np.zeros((mesh.num_unknowns,mesh.num_unknowns)) #stiffness matrix
59 | B = mass_matrix(mesh)
60 | #time iteration
61 | for t in time_partition[1:]:
62 | count=0
63 | F.fill(0)#empty load vector
64 | compute_vector(mesh,F,lambda x,y: f(x,y,t),lambda x,y:u_exact(x,y,t))#compute load vector
65 | #L-scheme iteration
66 | while True:
67 | count = count + 1
68 | conductivity = kappa(np.reshape(u_l, (mesh.cell_centers.shape[0],mesh.cell_centers.shape[1]),order='F'))
69 | A.fill(0)#empty the stiffness matrix
70 | compute_matrix(mesh, A, K,conductivity)#compute stiffnes matrix
71 | lhs = L*B+tau*A
72 | rhs = L*B@u_l + B@theta(u_t) - B@theta(u_l) + tau*F
73 | u = np.linalg.solve(lhs,rhs)
74 | if np.linalg.norm(u-u_l)<=TOL+TOL*np.linalg.norm(u_l):
75 | break
76 | else:
77 | u_l = u
78 | err = mesh.compute_error(u,lambda x,y : u_exact(x,y,t))
79 | print('error at time ',t," : ",err[0])
80 | print('L-scheme iterations: ',count)
81 | u_t = u
82 | u_l = u
83 |
84 | return (mesh.max_h(),timestep,mesh.compute_error(u,lambda x,y : u_exact(x,y,1))[0])
85 |
86 | if __name__=="__main__":
87 | number_of_tests = 3
88 |
89 | result = []
90 | for i in range(1,number_of_tests+1):
91 | num_nodes = 2*2**i
92 | mesh = Mesh(1+num_nodes,1+num_nodes,lambda p: np.array([p[0]-0.5*p[1],p[1]]),ghostboundary=True)
93 | result.append(solve_richards(mesh,mesh.max_h()))
94 | output=''
95 | for i in range(number_of_tests):
96 | row = result[i]
97 | if i==0:
98 | output = f'{i+1}&${row[0]:.5f}$&${row[1]:.5f}$&${row[2]:.6f}$&-\\\ \n'
99 | else:
100 | old_row = result[i-1]
101 | improvement = old_row[2]/row[2]
102 | output += f'{i+1}&${row[0]:.5f}$&${row[1]:.5f}$&${row[2]:.6f}$&${improvement:.5f}$\\\ \n'
103 | print(output)
--------------------------------------------------------------------------------
/utils/differentiation.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import sympy as sym
3 | import unittest
4 |
5 | def gradient(f, variables: list):
6 | vec = []
7 | for variable in variables:
8 | vec.append(sym.diff(f,variable))
9 | return vec
10 | def divergence(gradient: list,variables: list, permability = None,permability_tensor = None):
11 | if permability_tensor is not None:
12 | K_gradient = []
13 | for row in permability_tensor:
14 | r = 0
15 | for e,p in zip(gradient,row):
16 | r = r+e*p
17 | K_gradient.append(r)
18 | return divergence(K_gradient,variables,permability)
19 | if permability is None:
20 | return sum(map(lambda t: sym.diff(t[0],t[1]),zip(gradient,variables)))
21 | else:
22 | return sum(map(lambda t: sym.diff(permability*t[0],t[1]),zip(gradient,variables)))
23 | class TestDiffMethods(unittest.TestCase):
24 | def test_gradient(self):
25 | x = sym.Symbol('x')
26 | y = sym.Symbol('y')
27 | res = [2*x,2*y]
28 | var = list([x,y])
29 | f = x**2 + y**2
30 | self.assertEqual(gradient(f,var),res)
31 | def test_divergence(self):
32 | x = sym.Symbol('x')
33 | y = sym.Symbol('y')
34 | var = [x,y]
35 | res = 2*x+2*y
36 | self.assertEqual(divergence([x**2,y**2],var),res)
37 | def test_divergence_permability_tensor(self):
38 | x = sym.Symbol('x')
39 | y = sym.Symbol('y')
40 | K = np.array([[0,1],[1,0]])
41 | var = [x,y]
42 | res = 0
43 | self.assertEqual(divergence([x**2,y**2],var,permability_tensor=K),res)
44 | def test_divergence_permability(self):
45 | x = sym.Symbol('x')
46 | y = sym.Symbol('y')
47 | var = [x,y]
48 | res = 3*x**2 + 2*x*y
49 | self.assertEqual(divergence([x**2,y**2],var,permability = x),res)
50 | def test_combination(self):
51 | z = sym.Symbol('z')
52 | y = sym.Symbol('y')
53 | res = 6*z
54 | f = z**2+y**2
55 | self.assertEqual(divergence(gradient(f,[z,y]),[z,y],z),res)
56 | if __name__ =='__main__':
57 | unittest.main()
58 |
59 |
--------------------------------------------------------------------------------
/utils/flux_error.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from discretization.mesh import Mesh
3 | import sympy as sym
4 | import math
5 | x = sym.Symbol('x')
6 | y = sym.Symbol('y')
7 |
8 |
9 | def compute_flux_error(fx,fy,u_fabric,mesh: Mesh):
10 | u_x = sym.diff(u_fabric,x)
11 | u_x = sym.lambdify([x,y],u_x)
12 | u_y = sym.diff(u_fabric,y)
13 | u_y = sym.lambdify([x,y],u_y)
14 |
15 | normals = mesh.normals
16 | midpoints = mesh.midpoints
17 | meshToVec = mesh.meshToVec
18 | vecToMesh = mesh.vecToMesh
19 | volumes = mesh.volumes
20 |
21 | fx_exact = np.zeros((midpoints.shape[0]*(midpoints.shape[1]-1)))
22 | vec = fx
23 |
24 | for i in range(normals.shape[0]):
25 | for j in range(0,normals.shape[1]-1):
26 | if i==0 or i == normals.shape[0]-1:
27 | fx_exact[meshToVec(i,j)] = vec[meshToVec(i,j)]
28 | else:
29 | grad_u = np.array([[u_x(midpoints[i,j+1,0,0],midpoints[i,j+1,0,1])],[u_y(midpoints[i,j+1,0,0],midpoints[i,j+1,0,1])]])
30 | fx_exact[meshToVec(i,j)] = -normals[i,j+1,0,:].T@grad_u/np.linalg.norm(normals[i,j+1,0,:])
31 | fx[meshToVec(i,j)] = fx[meshToVec(i,j)]/np.linalg.norm(2*normals[i,j+1,0,:])
32 | error_x = vec[:midpoints.shape[0]*(midpoints.shape[1]-1)] - fx_exact
33 |
34 | fy_exact = np.zeros((midpoints.shape[1]*(midpoints.shape[0]-1)))
35 | vec = fy
36 | for i in range(normals.shape[0]-1):
37 | for j in range(normals.shape[1]-1):
38 | if j==0 or j == normals.shape[1]-1:
39 | fy_exact[meshToVec(i,j)] = vec[meshToVec(i,j)]
40 | else:
41 | grad_u = np.array([[u_x(midpoints[i+1,j,1,0],midpoints[i+1,j+1,1,1])],[u_y(midpoints[i+1,j,1,0],midpoints[i+1,j+1,1,1])]])
42 | fy_exact[meshToVec(i,j)] = -normals[i+1,j,1,:].T@grad_u/np.linalg.norm(normals[i+1,j,1,:])
43 | fy[meshToVec(i,j)] = fy[meshToVec(i,j)]/np.linalg.norm(2*normals[i+1,j,1,:])
44 | error_y = vec[:midpoints.shape[1]*(midpoints.shape[0]-1)] - fy_exact
45 |
46 |
47 | max_error_x = np.max(np.abs(error_x))
48 | error_x = np.square(error_x)
49 | volumes_x = np.zeros(error_x.shape)
50 | for i in range(error_x.shape[0]):
51 | a,b = vecToMesh(i)
52 | if a==0 or a == normals.shape[0]-1:
53 | volumes_x[i] = 0
54 | else:
55 | volumes_x[i] = (volumes[a,b]+volumes[a,b-1])
56 |
57 | max_error_y = np.max(np.abs(error_y))
58 | error_y = np.square(error_y)
59 | volumes_y = np.zeros(error_y.shape)
60 | for i in range(error_y.shape[0]):
61 | a,b = vecToMesh(i)
62 | if b == normals.shape[1]-1 or a==normals.shape[0]-1:
63 | volumes_y[i] = 0
64 | else:
65 | volumes_y[i] = (volumes[a,b]+volumes[a-1,b])
66 | l2_error = math.sqrt((volumes_x.T@error_x+volumes_y.T@error_y)/(np.ones(volumes_x.shape).T@volumes_x+np.ones(volumes_y.shape).T@volumes_y))
67 |
68 | return l2_error,max(max_error_x,max_error_y)
--------------------------------------------------------------------------------