def mesh2triang(mesh: df.Mesh) -> tri.Triangulation: """Return an unstructured grid with the connectivity given by matplotlib Code borrowed from Chris Richardson """ xy = mesh.coordinates() return tri.Triangulation(xy[:, 0], xy[:, 1], mesh.cells())
def vtk_ug_to_dolfin_mesh(ug): """ Create a DOLFIN Mesh from a vtkUnstructuredGrid object """ if not isinstance(ug, vtk.vtkUnstructuredGrid): raise TypeError("Expected a 'vtkUnstructuredGrid'") # Get mesh data num_cells = int(ug.GetNumberOfCells()) num_vertices = int(ug.GetNumberOfPoints()) # Get topological and geometrical dimensions cell = ug.GetCell(0) gdim = int(cell.GetCellDimension()) cell_type = cell.GetCellType() if cell_type not in [vtk.VTK_TETRA, vtk.VTK_TRIANGLE]: raise TypeError("DOLFIN only support meshes of triangles " + \ "and tetrahedrons.") tdim = 3 if cell_type == vtk.VTK_TETRA else 2 # Create empty DOLFIN Mesh mesh = Mesh() editor = MeshEditor() editor.open(mesh, tdim, gdim) editor.init_cells(num_cells) editor.init_vertices(num_vertices) editor.close() # Assign the cell and vertex informations directly from the vtk data cells_array = array_handler.vtk2array(ug.GetCells().GetData()) # Get the assumed fixed size of indices and create an index array cell_size = cell.GetPointIds().GetNumberOfIds() cellinds = np.arange(len(cells_array)) # Each cell_ids_size:th data point need to be deleted from the # index array ind_delete = slice(0, len(cells_array), cell_size+1) # Check that the removed value all have the same value which should # be the size of the data if not np.all(cells_array[ind_delete]==cell_size): raise ValueError("Expected all cells to be of the same size") cellinds = np.delete(cellinds, ind_delete) # Get cell data from mesh and make it writeable (cell data is non # writeable by default) and update the values mesh_cells = mesh.cells() mesh_cells.flags.writeable = True mesh_cells[:] = np.reshape(cells_array[cellinds], \ (num_cells , cell_size)) # Set coordinates from vtk data vertex_array = array_handler.vtk2array(ug.GetPoints().GetData()) if vertex_array.shape[1] != gdim: vertex_array = vertex_array[:,:gdim] mesh.coordinates()[:] = vertex_array return mesh
def vtk_ug_to_dolfin_mesh(ug): """ Create a DOLFIN Mesh from a vtkUnstructuredGrid object """ if not isinstance(ug, vtk.vtkUnstructuredGrid): raise TypeError("Expected a 'vtkUnstructuredGrid'") # Get mesh data num_cells = int(ug.GetNumberOfCells()) num_vertices = int(ug.GetNumberOfPoints()) # Get topological and geometrical dimensions cell = ug.GetCell(0) gdim = int(cell.GetCellDimension()) cell_type = cell.GetCellType() if cell_type not in [vtk.VTK_TETRA, vtk.VTK_TRIANGLE]: raise TypeError("DOLFIN only support meshes of triangles " + \ "and tetrahedrons.") tdim = 3 if cell_type == vtk.VTK_TETRA else 2 # Create empty DOLFIN Mesh mesh = Mesh() editor = MeshEditor() editor.open(mesh, tdim, gdim) editor.init_cells(num_cells) editor.init_vertices(num_vertices) editor.close() # Assign the cell and vertex informations directly from the vtk data cells_array = array_handler.vtk2array(ug.GetCells().GetData()) # Get the assumed fixed size of indices and create an index array cell_size = cell.GetPointIds().GetNumberOfIds() cellinds = np.arange(len(cells_array)) # Each cell_ids_size:th data point need to be deleted from the # index array ind_delete = slice(0, len(cells_array), cell_size + 1) # Check that the removed value all have the same value which should # be the size of the data if not np.all(cells_array[ind_delete] == cell_size): raise ValueError("Expected all cells to be of the same size") cellinds = np.delete(cellinds, ind_delete) # Get cell data from mesh and make it writeable (cell data is non # writeable by default) and update the values mesh_cells = mesh.cells() mesh_cells.flags.writeable = True mesh_cells[:] = np.reshape(cells_array[cellinds], \ (num_cells , cell_size)) # Set coordinates from vtk data vertex_array = array_handler.vtk2array(ug.GetPoints().GetData()) if vertex_array.shape[1] != gdim: vertex_array = vertex_array[:, :gdim] mesh.coordinates()[:] = vertex_array return mesh
import numpy from dolfin import Mesh mesh = Mesh("mesh.xml.gz") E = mesh.cells() M = numpy.fromfile('materials.np') I = -numpy.ones(mesh.num_vertices()) for i in range(6): edx = numpy.nonzero((M > 10 * (i + 1)) * (M < 10 * (i + 2)))[0] idx = (numpy.unique(E[edx, 0:3])).astype(int) I[idx] = i * 0.2 edx = numpy.nonzero(M == 7)[0] idx = (numpy.unique(E[edx, 0:3])).astype(int) I[idx] = -2 from viper import Viper pv = Viper(mesh, I) pv.interactive()
class Mesh(): def __init__(self, dimension, robot_radius): self.robot_radius = robot_radius # Correct the map size by the robot_radius self.dimension = list(map(lambda x: x - robot_radius, dimension)) self._cache_path = '/tmp/mesh.xml' # Define the base rectangle. self._map = mshr.Rectangle(Point(robot_radius, robot_radius), Point(self.dimension[0], self.dimension[1])) self._mesh2d = None def add_circle_obstacle(self, p, radius, mirror=False, accuracy=10): points = [Point(p[0], p[1])] # Replicate the circle on the other edge of the map. if mirror: points.append( Point(self.dimension[0] + self.robot_radius - p[0], p[1])) for point in points: self._map -= mshr.Circle(point, radius + self.robot_radius, accuracy) def add_rectangle_obstacle(self, p1, p2, mirror=False): self._map -= mshr.Rectangle( self.__correct_point(Point(p1[0], p1[1]), inverse=-1), self.__correct_point(Point(p2[0], p2[1]))) # Replicate the data the other side of the map. if mirror: p1bis = Point( self.dimension[0] + self.robot_radius - p1[0] + self.robot_radius, p1[1] - self.robot_radius) p2bis = Point( self.dimension[0] + self.robot_radius - p2[0] - self.robot_radius, p2[1] + self.robot_radius) self._map -= mshr.Rectangle(p2bis, p1bis) # As dolfin and mshr don't support leaning rectangle, we build the # rectangle giving him the point with the minimum y data, # the absolute y size, the width, the angle with the 0 starting # with the x axis. # The accuracy give the number of rectangles we want to cut the # leaning rectangle. def add_leaning_rectangle_obstacle(self, p, height, width, angle, mirror=False, accuracy=10): height_chunck_size = height / (accuracy) radian_angle = math.radians(angle) for chunck in range(accuracy): pos = chunck * height_chunck_size + height_chunck_size / 2 # Get the center of the little rectangle. center_point = (pos * math.cos(radian_angle), pos * math.sin(radian_angle)) self.__build_rectangle_by_center(p, center_point, height_chunck_size, width, mirror) def __build_rectangle_by_center(self, base_point, p, height, width, mirror=False): p1 = (base_point[0] + p[0] + width / 2, base_point[1] + p[1] + height / 2) p2 = (base_point[0] + p[0] - width / 2, base_point[1] + p[1] - height / 2) self.add_rectangle_obstacle(p1, p2, mirror) def __correct_point(self, p, inverse=1): return Point(p.x() + self.robot_radius * inverse, p.y() + self.robot_radius * inverse) def build(self, accuracy=5, cache=False): if cache and os.path.exists(self._cache_path): self._mesh2d = DolfinMesh(self._cache_path) return self._mesh2d = mshr.generate_mesh(self._map, accuracy) if cache: f = File(self._cache_path) f << self._mesh2d def get_connectivity_cells(self): return self._mesh2d.cells() def get_nodes(self): return self._mesh2d.coordinates() def display(self, accuracy=10): if self._mesh2d is None: self.build(accuracy) plot(self._mesh2d, interactive=True)
import numpy from dolfin import Mesh mesh = Mesh("mesh.xml.gz") E = mesh.cells() M = numpy.fromfile('materials.np'); I = -numpy.ones(mesh.num_vertices()) for i in range(6): edx = numpy.nonzero((M>10*(i+1))*(M<10*(i+2)))[0] idx = (numpy.unique(E[edx,0:3])).astype(int) I[idx] = i*0.2 edx = numpy.nonzero(M==7)[0] idx = (numpy.unique(E[edx,0:3])).astype(int) I[idx] = -2; from viper import Viper pv = Viper(mesh, I) pv.interactive()
class FBVP: def __init__(self, mesh_name, parameters, alpha_range=None): # LOAD MESH AND PARAMETERS self.parameters = parameters self.mesh_name = mesh_name if not alpha_range: self.alpha_range = parameters.alpha_range else: self.alpha_range = alpha_range mesh_path = self.parameters.get_mesh_path() self.mesh = Mesh() with XDMFFile(mesh_path) as f: f.read(self.mesh) print(f'Mesh size= {self.mesh.hmax()}') # dimension of approximation space self.dim = len(self.mesh.coordinates()) print(f'Dimension of solution space is {self.dim}') self.V = FunctionSpace(self.mesh, 'CG', 1) # CG = P1 self.coords = self.mesh.coordinates()[dof_to_vertex_map(self.V)] self.T = self.parameters.T # final time self.w = TrialFunction(self.V) self.u = TestFunction(self.V) ####################################################################### # CONTROL SET CREATION self.control_set = np.linspace(self.alpha_range[0], self.alpha_range[1], self.parameters.control_set_size) self.control_set_size = len(self.control_set) print(f'Discretized control set has size {self.control_set_size}') ####################################################################### # BOUNDARY CONDITIONS parameters.set_boundary_conditions(self.mesh) self.boundary_markers = MeshFunction('size_t', self.mesh, 1) self.boundary_markers.set_all(4) # pylint: disable=no-member for i, omega in self.parameters.omegas.items(): omega.mark(self.boundary_markers, i) self.ds = Measure('ds', domain=self.mesh, subdomain_data=self.boundary_markers) self.dirichlet_bcs = [ DirichletBC(self.V, parameters.RHS_bound[j], self.boundary_markers, j) for j in self.parameters.regions["Dirichlet"] ] # Get indices of dirichlet and robin dofs self.dirichlet_nodes_list = set() self.dirichlet_nodes_dict = {} for j in self.parameters.regions["Dirichlet"]: bc = DirichletBC(self.V, Constant(0), self.boundary_markers, j) self.dirichlet_nodes_list |= set(bc.get_boundary_values().keys()) self.dirichlet_nodes_dict[j] = list( bc.get_boundary_values().keys()) self.robin_nodes_list = set() self.robin_nodes_dict = {} for j in self.parameters.regions["Robin"]: bc = DirichletBC(self.V, Constant(0), self.boundary_markers, j) self.robin_nodes_list |= set(bc.get_boundary_values().keys()) self.robin_nodes_dict[j] = list(bc.get_boundary_values().keys()) bc = DirichletBC(self.V, Constant(0), 'on_boundary') self.boundary_nodes_list = bc.get_boundary_values().keys() self.robint_nodes_list = set() self.robint_nodes_dict = {} for j in self.parameters.regions["RobinTime"]: bc = DirichletBC(self.V, Constant(0), self.boundary_markers, j) self.robint_nodes_list |= set(bc.get_boundary_values().keys()) self.robint_nodes_dict[j] = list(bc.get_boundary_values().keys()) ####################################################################### # ASSEMBLY time_start = time.process_time() self.assemble_diagonal_matrix() # auxilliary generic diagonal matrix # used for vector*matrix multiplication of dolfin matrices self.assemble_lumpedmm() # lumped mass matrix # which serves the role of identity operator self.assemble_laplacian() # discrete laplacian self.ad_data_path = f'out/{self.parameters.experiment}' Path(self.ad_data_path).mkdir(parents=True, exist_ok=True) self.timesteps = self.parameters.get_number_of_timesteps() self.assemble_HJBe() # assembly of explicit operators self.assemble_HJBi() # assembly of implicit operators self.assemble_RHS() # assembly of forcing term print('Final time assembly complete') print(f'Assembly took {time.process_time() - time_start} seconds') print('===========================================================') def assemble_diagonal_matrix(self): print("Assembling auxilliary diagonal matrix") """ Operator assembled to get the right sparameterssity pattern (non-zero terms can only exist on diagonal) """ mesh3 = UnitIntervalMesh(self.dim) V3 = FunctionSpace(mesh3, "DG", 0) wid = TrialFunction(V3) uid = TestFunction(V3) self.diag_matrix = assemble(uid * wid * dx) self.diag_matrix.zero() def assemble_lumpedmm(self): """ Assembly lumped mass matrix - equal to 0 on robin boundary since there is no time derivative there. """ print("Assembling lumped mass matrix") mass_form = self.w * self.u * dx mass_action_form = action(mass_form, Constant(1)) self.MM_terms = assemble(mass_action_form) for n in self.robint_nodes_list: self.MM_terms[n] = 1.0 for n in self.robin_nodes_list: self.MM_terms[n] = 0.0 self.mass_matrix = assemble(mass_form) self.mass_matrix.zero() self.mass_matrix.set_diagonal(self.MM_terms) self.scipy_mass_matrix = toscipy(self.mass_matrix) def assemble_laplacian(self): print("Assembling laplacians") # laplacian discretisation self.laplacian = assemble(dot(grad(self.w), grad(self.u)) * dx) def assemble_HJBe(self, t=None): """ Assembly explicit operator for every control in the control set, then apply artificial diffusion to all of them. Note that artificial diffusion is calculated differently on the boundary nodes. """ self.explicit_matrices = np.empty(self.control_set_size, dtype=object) self.explicit_diffusion = np.empty([self.control_set_size, self.dim]) diagonal_vector = Vector(self.mesh.mpi_comm(), self.dim) global_min_timestep = float('inf') # Create explicit operator for each control in control set for k, alpha in enumerate(self.control_set): if k % 10 == 0: print(f'Assembling explicit matrix under control {k}' f' out of {self.control_set_size}') # coefficients in the interior advection_x = self.parameters.adv_x(alpha) advection_y = self.parameters.adv_y(alpha) reaction = self.parameters.lin(alpha) if t is not None: advection_x.t = t advection_y.t = t reaction.t = t # Discretize PDE (set values on boundary rows to zero) b = np.array([advection_x, advection_y]) E_interior_form = (np.dot(b, grad(self.w)) + reaction * self.w) * self.u * dx explicit_matrix = assemble(E_interior_form) set_zero_rows(explicit_matrix, self.boundary_nodes_list) # Calculate diffusion necessary to make explicit operator monotone min_diffusion = np.zeros(explicit_matrix.size(0)) for rows, row_num in getrows(explicit_matrix, self.laplacian, ignore=self.boundary_nodes_list): min_diffusion[row_num] = calc_ad(rows, row_num) self.explicit_diffusion[k] = min_diffusion discrete_diffusion = apply_ad(self.laplacian, min_diffusion) explicit_matrix += discrete_diffusion for j in self.parameters.regions['RobinTime']: self.set_directional_derivative( explicit_matrix, region=j, nodes=self.robint_nodes_dict[j], control=alpha, time=t) explicit_matrix.get_diagonal(diagonal_vector) current_min_timestep = get_min_timestep( diagonal_vector, self.MM_terms, self.dirichlet_nodes_list | self.robin_nodes_list) global_min_timestep = min(current_min_timestep, global_min_timestep) self.explicit_matrices[k] = toscipy(explicit_matrix) ####################################################################### min_timesteps = int(self.T / global_min_timestep) + 1 if not self.timesteps or self.timesteps < min_timesteps: self.timesteps = min_timesteps try: filename = (f'meshes/{self.parameters.domain}/' f'Mvalues-{self.parameters.experiment}.json') with open(filename, 'r') as f: min_timesteps_dict = json.load(f) except FileNotFoundError: min_timesteps_dict = {} min_timesteps_dict[self.parameters.mesh_name] = self.timesteps with open(filename, 'w') as f: json.dump(min_timesteps_dict, f) self.timestep_size = self.T / self.timesteps # time step size self.parameters.calculate_save_interval() self.explicit_matrices = self.timestep_size * self.explicit_matrices - \ np.repeat(self.scipy_mass_matrix, self.control_set_size) print('Checking if the explicit operators satisfy' ' monotonicity conditions') for explicit_matrix in self.explicit_matrices: explicit_check(explicit_matrix, self.dirichlet_nodes_list) def assemble_RHS(self, t=None): """Assemble right hand side of the FBVP """ print('Assembling RHS') self.forcing_terms = np.empty([self.control_set_size, self.dim]) for i, alpha in enumerate(self.control_set): rhs = self.parameters.RHSt(alpha) if t is not None: rhs.t = t # Initialise forcing term F = np.array(assemble(rhs * self.u * dx)[:]) for j in self.parameters.regions['RobinTime']: rhs = self.parameters.RHS_bound[j](alpha) if t is not None: rhs.t = t bc = DirichletBC(self.V, rhs, self.boundary_markers, j) vals = bc.get_boundary_values() F[list(vals.keys())] = list(vals.values()) for j in self.parameters.regions['Robin']: rhs = self.parameters.RHS_bound[j](alpha) if t is not None: rhs.t = t bc = DirichletBC(self.V, rhs, self.boundary_markers, j) vals = bc.get_boundary_values() F[list(vals.keys())] = list(vals.values()) self.forcing_terms[i] = self.timestep_size * F def assemble_HJBi(self, t=None): """ Assembly matrix discretizing second order terms after diffusion moved to the explicit operator is subtracted. Whenever amount of diffusion used to make some row of an explicit operator monotonic exceeds the amount of natural diffusion at that node we call it artificial diffusion. In such case, this row of the implicit operator is multiplied by zero """ print('Assembling implicit operators') self.implicit_matrices = [] remaining_diffusion = Function(self.V) max_art_dif = 0.0 max_art_dif_loc = None diffusion_matrix = self.diag_matrix.copy() for explicit_diffusion, alpha in \ zip(self.explicit_diffusion, self.control_set): diffusion = self.parameters.diffusion(alpha) if t is not None: diffusion.t = t diff = interpolate(diffusion, self.V).vector() if not np.all(diff >= 0): raise Exception("Choose non-negative diffusion") diff_vec = np.array([ diff[i] if i not in self.boundary_nodes_list else 0.0 for i in range(self.dim) ]) artificial_diffusion = explicit_diffusion - diff_vec if np.amax(artificial_diffusion) > max_art_dif: max_art_dif = np.amax(artificial_diffusion) max_art_dif_loc = self.coords[np.argmax(artificial_diffusion)] # discretise second order terms remaining_diffusion.vector()[:] = np.maximum( -artificial_diffusion, [0] * self.dim) diffusion_matrix.set_diagonal(remaining_diffusion.vector()) implicit_matrix = matmult(diffusion_matrix, self.laplacian) for j in self.parameters.regions['Robin']: self.set_directional_derivative(implicit_matrix, region=j, nodes=self.robin_nodes_dict[j], control=alpha, time=t) self.implicit_matrices.append(self.timestep_size * implicit_matrix + self.mass_matrix) self.scipy_implicit_matrices = [ toscipy(mat) for mat in self.implicit_matrices ] print('Checking if the implicit operators satisfy' ' monotonicity conditions') for implicit_matrix in self.scipy_implicit_matrices: implicit_check(implicit_matrix) with open(self.ad_data_path + '/ad.txt', 'a') as f: time_str = f' at time {t}\n' if t is not None else '\n' f.write(f'For mesh {self.mesh_name} max value of artificial' ' diffusion coefficient was' f' {max_art_dif} at {max_art_dif_loc}' + time_str) def set_directional_derivative(self, operator, region, nodes, control, time=None): if region in self.parameters.regions['Robin']: adv_x = self.parameters.robin_adv_x[region](control) adv_y = self.parameters.robin_adv_y[region](control) lin = self.parameters.robin_lin[region](control) elif region in self.parameters.regions['RobinTime']: adv_x = self.parameters.robint_adv_x[region](control) adv_y = self.parameters.robint_adv_y[region](control) lin = self.parameters.robint_lin[region](control) if time is not None: adv_x.t = time adv_y.t = time lin.t = time b = (interpolate(adv_x, self.V), interpolate(adv_y, self.V)) c = interpolate(lin, self.V) for n in nodes: # node coordinates x = self.coords[n] # evaluate advection at robin node b_x = np.array([b[0].vector()[n], b[1].vector()[n]]) # denominator used to calculate directional derivative if np.linalg.norm(b_x) > 1: lamb = 0.1 * self.mesh.hmin() / np.linalg.norm(b_x) else: lamb = 0.1 * self.mesh.hmin() # position of first node of the stencil x_prev = x - lamb * b_x # Find cell containing first node of stencil and get its # dof/vertex coordinates try: cell_ind = self.mesh.bounding_box_tree( ).compute_entity_collisions(Point(x_prev))[0] except IndexError: i = 16 while i > 2: # sometimes Fenics does not detect nodes if boundary is # parallel to the boundary advection due to rounding errors # so try different precisions just to be sure try: cell_ind = self.mesh.bounding_box_tree( ).compute_entity_collisions(Point(np.round(x_prev, i)))[0] break except IndexError: i -= 1 else: raise Exception( "Boundary advection outside tangential cone") cell_vertices = self.mesh.cells()[cell_ind] cell_dofs = vertex_to_dof_map(self.V)[cell_vertices] cell_coords = self.mesh.coordinates()[cell_vertices] # calculate weigth of each vertex in the cell (using # barycentric coordinates) A = np.vstack((cell_coords.T, np.ones(3))) rhs = np.append(x_prev, np.ones(1)) weights = np.linalg.solve(A, rhs) weights = [w if w > 1e-14 else 0 for w in weights] dof_to_weight = dict(zip(cell_dofs, weights)) # calculate directional derivative at each node using # weights to interpolate value of numerical solution at # x_prev row = operator.getrow(n) indices = row[0] data = row[1] for dof in cell_dofs: pos = np.where(indices == dof)[0][0] if dof != n: data[pos] = -dof_to_weight[dof] / lamb else: c_n = c.vector()[dof] # make sure reaction term is positive adding artificial # constant if necessary if region in self.parameters.regions['Robin']: c_n = max(c_n, min(lamb, 1E-8)) data[pos] = (1 - dof_to_weight[dof]) / lamb + c_n operator.set([data], [n], indices) operator.apply('insert')