def __init__(self, problem: Elasticity): self.problem = problem self.points = problem.points self.triangles = problem.triangles Cell2.points = problem.points quad_degree = 4 gauss = QGauss(dim=2, n=quad_degree) self.fe_values = FEValues(self.problem.fe, gauss, self.points, self.problem.edges, lambda p: True, update_gradients=True) self.cached_gradient_point = None self.cached_gradient = None
def setUp(self): dim = 2 degree = 1 quad_degree = 1 fe_q = FE_Q(dim, degree) quadrature = QGauss(dim, quad_degree) self.fe_values = FEValues(fe_q, quadrature, points=self.triangle_corners, edges=self.edges, is_dirichlet=lambda p: False, update_values=True, update_gradients=True) cell = Cell(dim, self.triangles[0]) self.fe_values.reinit(cell)
def test_gauss_degree_of_exactness_on_triangle(self): dim = 2 lower_x = 3 lower_y = 0 upper_x = lower_x # for easy scipy integration upper_y = 3 triangle_corners = np.array([[0, 0], [lower_x, lower_y], [upper_x, upper_y]]) def first_deg(p): return 2 * p[0] + 4 * p[1] - 1 def second_deg(p): return p[0]**2 - 2 * p[0] * p[1] - p[1]**2 / 2 + 1 def third_deg(p): return 4 * p[1]**3 + p[0]**2 - 3 * p[1] def four_deg(p): return p[0]**4 - third_deg(p) def five_deg(p): return p[1]**5 - four_deg(p) # Number of points to max polynomial degree degree_of_exactness = {1: 1, 3: 2, 4: 3, 7: 4} message = "" for func, degree in [(first_deg, 1), (second_deg, 2), (third_deg, 3), (four_deg, 4), (five_deg, 5)]: print("\nFunc", func.__name__) integral = dblquad(lambda x, y: func([y, x]), 0, lower_x, lambda x: lower_y / lower_x * x, lambda x: upper_y / upper_x * x)[0] print("INTEGRATED:", integral) for quad_degree in [1, 3, 4, 7]: fe_q = FE_Q(dim, degree=1) quadrature = QGauss(dim, quad_degree) fe_values = FEValues(fe_q, quadrature, points=triangle_corners, edges=self.edges, is_dirichlet=lambda p: False, update_values=True, update_gradients=True) cell = Cell(dim, self.triangles[0]) fe_values.reinit(cell) value = 0 for q_index in range(quad_degree): x_q = fe_values.quadrature_point(q_index) value += func(x_q) * fe_values.JxW(q_index) print("gauss points", quad_degree, "polydeg", degree, "value", value) try: if degree <= degree_of_exactness[quad_degree]: self.assertAlmostEqual( value, integral, 3, msg=f"A polynomial of degree {degree} should be " f"integrated exactly by a gaussian " f"{quad_degree} point quadrature.") else: self.assertNotEqual( value, integral, msg=f"{quad_degree} point Gauss should not handle " f"polynomial of degree {degree}.") except AssertionError as e: message += f"\n{e}" if message: raise AssertionError(message)
class InterpolatedSolution(Function): def __init__(self, problem: Elasticity): self.problem = problem self.points = problem.points self.triangles = problem.triangles Cell2.points = problem.points quad_degree = 4 gauss = QGauss(dim=2, n=quad_degree) self.fe_values = FEValues(self.problem.fe, gauss, self.points, self.problem.edges, lambda p: True, update_gradients=True) self.cached_gradient_point = None self.cached_gradient = None def value(self, p): for triangle in self.triangles: corners = self.points[triangle] if self.inside_triangle(p, corners): return self.interpolate(p, triangle) else: raise Exception("Point not inside mesh??") def gradient(self, p, value=None): if np.all(p == self.cached_gradient_point): return self.cached_gradient else: raise Exception("wut") def interpolate(self, p, triangle): cell = Cell2(2, triangle) self.fe_values.reinit(cell) numerical_sol = 0 numerical_grad = 0 # Interpolate the solution value and gradient in the quadrature # point. phis = [] grad_phis = [] for k in self.fe_values.dof_indices(): # This is the global point index if k % 2 == 0: # k is even # Then the test function should be phi_k = [v_k, 0] value = [self.fe_values[0].fe.shape_value(k // 2, p), 0] grad = [self.fe_values[0].fe.shape_grad(k // 2), [0, 0]] else: # k is odd # Then the test function should be phi_k+1 = [0, v_k] value = [0, self.fe_values[1].fe.shape_value(k // 2, p)] # TODO ser gradientene riktige ut? har de litt små # verdier? skal vel være på størrelsen 1/h grad = [[0, 0], self.fe_values[1].fe.shape_grad(k // 2)] phis.append(np.array(value)) grad_phis.append(np.array(grad)) for i in self.fe_values.dof_indices(): global_index = self.fe_values.loc2glob_dofs[i] numerical_sol += self.problem.solution[global_index] * phis[i] numerical_grad += self.problem.solution[global_index] * grad_phis[i] self.cached_gradient_point = p self.cached_gradient = numerical_grad return numerical_sol @staticmethod def inside_triangle(p, corners): indices = {0: [1, 2], 1: [0, 2], 2: [0, 1]} for i, corner in enumerate(corners): a = corners[indices[i][0]] b = corners[indices[i][1]] res = InterpolatedSolution.half_plane(p, [a, b], corners[i]) if res < 0: return False return True @staticmethod def half_plane(p: np.ndarray, plane, third): """ Returns a positive number if the point p is on the correct side of the plane. :param p: a point :param plane: as list of two numpy points :param third: the third point of the triangle :return: """ normal = np.array([-(plane[1][1] - plane[0][1]), plane[1][0] - plane[0][0]]) ac = third - plane[0] if normal @ ac < 0: # Make sure the normal vector points into the triangle normal *= -1 return normal @ (p - plane[0])
def compute_error(self): print("Compute error") analytical_soln: Function = self.AnalyticalSoln() gauss = QGauss(dim=self.dim, n=self.degree) fe_values = FEValues(self.fe, gauss, self.points, self.edges, self.is_dirichlet, update_gradients=True) l2_diff_integral = 0 h1_diff_integral = 0 for triangle in self.triangles: cell = Cell(self.dim, triangle) fe_values.reinit(cell) for q_index in fe_values.quadrature_point_indices(): numerical_sol = 0 numerical_grad = 0 # Interpolate the solution value and gradient in the quadrature # point. for i in fe_values.dof_indices(): global_index = fe_values.local2global[i] numerical_sol += self.solution[global_index] \ * fe_values.shape_value(i, q_index) numerical_grad += self.solution[global_index] \ * fe_values.shape_grad(i, q_index) x_q = fe_values.quadrature_point(q_index) exact_solution = analytical_soln.value(x_q) exact_grad = analytical_soln.gradient(x_q) # Integrate the square difference of the two l2_diff_integral += (numerical_sol - exact_solution) ** 2 * \ fe_values.JxW(q_index) # TODO calculate error in H1-norm # Integrate the square difference of the gradients. gradient_diff = numerical_grad - exact_grad h1_diff_integral += gradient_diff @ gradient_diff * \ fe_values.JxW(q_index) l2_error = np.sqrt(l2_diff_integral) h1_error = np.sqrt(l2_diff_integral + h1_diff_integral) h1_error_semi_norm = np.sqrt(h1_diff_integral) Error = namedtuple("Error", ["L2_error", "H1_error", "H1_semi_error", "h"]) print("L2", l2_error) print("H1", h1_error) print("H1-semi", h1_error_semi_norm) print("h", self.h) return Error(L2_error=l2_error, H1_error=h1_error, H1_semi_error=h1_error_semi_norm, h=self.h)
def assemble_system(self): """ Set up the stiffness matrix. :return: """ print("Assemble system") gauss = QGauss(dim=self.dim, n=self.quad_degree) fe_values = FEValues(self.fe, gauss, self.points, self.edges, self.is_dirichlet, update_gradients=True) face_gauss = QGauss(dim=self.dim - 1, n=self.quad_degree) fe_face_values = FEFaceValues(self.fe, face_gauss, self.points, self.edges, self.is_dirichlet) rhs = self.RHS() # TODO only homogeneous Dirichlet supperted boundary_values = BoundaryValues() neumann_bdd_values = self.NeumannBD() for triangle in self.triangles: # Nå er vi i en celle # lar indeksen i punktlista være den globale nummereringen til # shape funksjoner # TODO build this initialization into the for-loop. cell = Cell(self.dim, triangle) fe_values.reinit(cell) for q_index in fe_values.quadrature_point_indices(): x_q = fe_values.quadrature_point(q_index) for i in fe_values.dof_indices(): for j in fe_values.dof_indices(): val = fe_values.shape_grad(i, q_index) \ @ fe_values.shape_grad(j, q_index) \ * fe_values.JxW(q_index) # (∇u_i, ∇v_j) self.system_matrix[fe_values.loc2glob_dofs[i], fe_values.loc2glob_dofs[j]] += val val = fe_values.shape_value(i, q_index) * rhs.value(x_q) \ * fe_values.JxW(q_index) # (v_i, f) self.system_rhs[fe_values.loc2glob_dofs[i]] += val for face in cell.face_iterators(): if not face.at_boundary(): continue fe_face_values.reinit(cell, face) for q_index in fe_face_values.quadrature_point_indices(): x_q = fe_face_values.quadrature_point(q_index) for j in fe_face_values.dof_indices(): g = neumann_bdd_values.value(x_q) val = fe_face_values.shape_value(j, q_index) * g \ * fe_face_values.JxW(q_index) # (g, v_j) self.system_rhs[fe_face_values.loc2glob_dofs[j]] += val # TODO this fixes so the matrix is invertible, but could just have # removed those dofs that are not a dof from the matrix, so this # wouldn't be needed. Would then have to set boundary values in a # different way after the system is solved. for i, point in enumerate(self.points): if fe_values.is_boundary[i] and self.is_dirichlet(point): self.system_matrix[i, i] = 1 # TODO this doesn't implement lifting functions, so this # only works with homogeneous dirichlet boundary conditions. self.system_rhs[i] = boundary_values.value(point)