def test_manifold_point_search():
    # Simple two-triangle surface in 3d
    vertices = [(0.0, 0.0, 1.0), (1.0, 1.0, 1.0), (1.0, 0.0, 0.0),
                (0.0, 1.0, 0.0)]
    cells = [(0, 1, 2), (0, 1, 3)]
    mesh = Mesh(MPI.comm_world, CellType.Type.triangle,
                numpy.array(vertices, dtype=numpy.float64),
                numpy.array(cells, dtype=numpy.int32), [],
                cpp.mesh.GhostMode.none)

    bb = mesh.bounding_box_tree()
    p = Point(0.5, 0.25, 0.75)
    assert bb.compute_first_entity_collision(p, mesh) == 0

    p = Point(0.25, 0.5, 0.75)
    assert bb.compute_first_entity_collision(p, mesh) == 1
Beispiel #2
0
def create_slice(basemesh, point, normal, closest_region=False, crinkle_clip=False):
    """Create a slicemesh from a basemesh.

    :param basemesh: Mesh to slice
    :param point: Point in slicing plane
    :param normal: Normal to slicing plane
    :param closest_region: Set to True to extract disjoint region closest to specified point
    :param crinkle_clip: Set to True to return mesh of same topological dimension as basemesh

    .. note::

        Only 3D-meshes currently supported for slicing.

    .. warning::

        Slice-instances are intended for visualization only, and may produce erronous
        results if used for computations.

    """
    assert basemesh.geometry().dim() == 3, "Can only slice 3D-meshes."

    P = np.array([point[0], point[1], point[2]], dtype=np.double)

    # Create unit normal
    n = np.array([normal[0],normal[1], normal[2]])
    n = n/np.linalg.norm(n)
    #self.n = Constant((n[0], n[1], n[2]))

    # Calculate the distribution of vertices around the plane
    # (sign of np.dot(p-P, n) determines which side of the plane p is on)
    vsplit = np.dot(basemesh.coordinates()-P, n)

    # Count each cells number of vertices on the "positive" side of the plane
    # Only cells with vertices on both sides of the plane intersect the plane
    operator = np.less
    npos = np.sum(vsplit[basemesh.cells()] < 0, 1)

    intersection_cells = basemesh.cells()[(npos > 0) & (npos < 4)]

    if len(intersection_cells) == 0:
        # Try to put "zeros" on other side of plane
        # FIXME: handle cells with vertices exactly intersecting the plane in a more robust manner.
        operator = np.greater
        npos = np.sum(vsplit[basemesh.cells()] > 0, 1)
        #cell_indices = (npos > 0) & (npos < 4)
        intersection_cells = basemesh.cells()[(npos > 0) & (npos < 4)]

    if crinkle_clip:
        cf = CellFunction("size_t", basemesh)
        cf.set_all(0)
        cf.array()[(npos>0) & (npos<4)] = 1
        mesh = create_submesh(basemesh, cf, 1)
    else:
        def add_cell(cells, cell):
            # Split cell into triangles
            for i in xrange(len(cell)-2):
                cells.append(cell[i:i+3])

        cells = []
        index = 0
        indexes = {}
        for c in intersection_cells:
            a = operator(vsplit[c], 0)
            positives = c[np.where(a==True)[0]]
            negatives = c[np.where(a==False)[0]]

            cell = []
            for pp_ind in positives:
                pp = basemesh.coordinates()[pp_ind]

                for pn_ind in negatives:
                    pn = basemesh.coordinates()[pn_ind]
                    if (pp_ind, pn_ind) not in indexes:
                        # Calculate intersection point with the plane
                        d = np.dot(P-pp, n)/np.dot(pp-pn, n)
                        ip = pp+(pp-pn)*d

                        indexes[(pp_ind, pn_ind)] = (index, ip)
                        index += 1

                    cell.append(indexes[(pp_ind, pn_ind)][0])


            add_cell(cells, cell)
        MPI.barrier(mpi_comm_world())

        # Assign global indices
        # TODO: Assign global indices properly
        dist = distribution(index)
        global_idx = sum(dist[:MPI.rank(mpi_comm_world())])
        vertices = {}
        for idx, p in indexes.values():
            vertices[idx] = (global_idx, p)
            global_idx += 1


        global_num_cells = MPI.sum(mpi_comm_world(), len(cells))
        global_num_vertices = MPI.sum(mpi_comm_world(), len(vertices))

        mesh = Mesh()

        # Return empty mesh if no intersections were found
        if global_num_cells == 0:
            mesh_editor = MeshEditor()
            mesh_editor.open(mesh, "triangle", 2, 3)

            mesh_editor.init_vertices(0)
            mesh_editor.init_cells(0)

            mesh_editor.close()
        else:

            # Distribute mesh if empty on any processors
            cells, vertices = distribute_meshdata(cells, vertices)

            # Build mesh
            mesh_editor = MeshEditor()
            mesh_editor.open(mesh, "triangle", 2, 3)

            mesh_editor.init_vertices(len(vertices))
            mesh_editor.init_cells(len(cells))

            for index, cell in enumerate(cells):
                mesh_editor.add_cell(index, cell[0], cell[1], cell[2])

            for local_index, (global_index, coordinates) in vertices.items():
                mesh_editor.add_vertex_global(int(local_index), int(global_index), coordinates)

            mesh_editor.close()
            mesh.topology().init(0, len(vertices), global_num_vertices)
            mesh.topology().init(2, len(cells), global_num_cells)


    if closest_region and mesh.size_global(0) > 0:
        assert MPI.size(mpi_comm_world())==1, "Extract closest region does not work in parallel"
        regions = compute_connectivity(mesh)
        i,d = mesh.bounding_box_tree().compute_closest_entity(Point(P))

        if d == MPI.min(mesh.mpi_comm(), d):
            v = regions[int(i)]
        else:
            v = 0

        v = MPI.max(mesh.mpi_comm(), v)
        mesh = create_submesh(mesh, regions, v)

    return mesh
Beispiel #3
0
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')