示例#1
0
def cyclic3D(u):
    """ Symmetrize with respect to (xyz) cycle. """
    try:
        nrm = np.linalg.norm(u.vector())
        V = u.function_space()
        assert V.mesh().topology().dim() == 3
        mesh1 = Mesh(V.mesh())
        mesh1.coordinates()[:, :] = mesh1.coordinates()[:, [1, 2, 0]]
        W1 = FunctionSpace(mesh1, 'CG', 1)

        # testing if symmetric
        bc = DirichletBC(V, 1, DomainBoundary())
        test = Function(V)
        bc.apply(test.vector())
        test = interpolate(Function(W1, test.vector()), V)
        assert max(test.vector()) - min(test.vector()) < 1.1

        v1 = interpolate(Function(W1, u.vector()), V)

        mesh2 = Mesh(mesh1)
        mesh2.coordinates()[:, :] = mesh2.coordinates()[:, [1, 2, 0]]
        W2 = FunctionSpace(mesh2, 'CG', 1)
        v2 = interpolate(Function(W2, u.vector()), V)
        pr = project(u + v1 + v2)
        assert np.linalg.norm(pr.vector()) / nrm > 0.01
        return pr
    except:
        print "Cyclic symmetrization failed!"
        return u
示例#2
0
def cyclic3D(u):
    """ Symmetrize with respect to (xyz) cycle. """
    try:
        nrm = np.linalg.norm(u.vector())
        V = u.function_space()
        assert V.mesh().topology().dim() == 3
        mesh1 = Mesh(V.mesh())
        mesh1.coordinates()[:, :] = mesh1.coordinates()[:, [1, 2, 0]]
        W1 = FunctionSpace(mesh1, 'CG', 1)

        # testing if symmetric
        bc = DirichletBC(V, 1, DomainBoundary())
        test = Function(V)
        bc.apply(test.vector())
        test = interpolate(Function(W1, test.vector()), V)
        assert max(test.vector()) - min(test.vector()) < 1.1

        v1 = interpolate(Function(W1, u.vector()), V)

        mesh2 = Mesh(mesh1)
        mesh2.coordinates()[:, :] = mesh2.coordinates()[:, [1, 2, 0]]
        W2 = FunctionSpace(mesh2, 'CG', 1)
        v2 = interpolate(Function(W2, u.vector()), V)
        pr = project(u+v1+v2)
        assert np.linalg.norm(pr.vector())/nrm > 0.01
        return pr
    except:
        print "Cyclic symmetrization failed!"
        return u
示例#3
0
def symmetrize(u, d, sym):
    """ Symmetrize function u. """
    if len(d) == 3:
        # three dimensions -> cycle XYZ
        return cyclic3D(u)
    elif len(d) >= 4:
        # four dimensions -> rotations in 2D
        return rotational(u, d[-1])
    nrm = np.linalg.norm(u.vector())
    V = u.function_space()
    mesh = Mesh(V.mesh())

    # test if domain is symmetric using function equal 0 inside, 1 on boundary
    # extrapolation will force large values if not symmetric since the flipped
    # domain is different
    bc = DirichletBC(V, 1, DomainBoundary())
    test = Function(V)
    bc.apply(test.vector())

    if len(d) == 2:
        # two dimensions given: swap dimensions
        mesh.coordinates()[:, d] = mesh.coordinates()[:, d[::-1]]
    else:
        # one dimension given: reflect
        mesh.coordinates()[:, d[0]] *= -1
    # FIXME functionspace takes a long time to construct, maybe copy?
    W = FunctionSpace(mesh, 'CG', 1)
    try:
        # testing
        test = interpolate(Function(W, test.vector()), V)
        # max-min should be around 1 if domain was symmetric
        # may be slightly above due to boundary approximation
        assert max(test.vector()) - min(test.vector()) < 1.1

        v = interpolate(Function(W, u.vector()), V)
        if sym:
            # symmetric
            pr = project(u+v)
        else:
            # antisymmetric
            pr = project(u-v)
        # small solution norm most likely means that symmetrization gives
        # trivial function
        assert np.linalg.norm(pr.vector())/nrm > 0.01
        return pr
    except:
        # symmetrization failed for some reason
        print "Symmetrization " + str(d) + " failed!"
        return u
示例#4
0
def symmetrize(u, d, sym):
    """ Symmetrize function u. """
    if len(d) == 3:
        # three dimensions -> cycle XYZ
        return cyclic3D(u)
    elif len(d) >= 4:
        # four dimensions -> rotations in 2D
        return rotational(u, d[-1])
    nrm = np.linalg.norm(u.vector())
    V = u.function_space()
    mesh = Mesh(V.mesh())

    # test if domain is symmetric using function equal 0 inside, 1 on boundary
    # extrapolation will force large values if not symmetric since the flipped
    # domain is different
    bc = DirichletBC(V, 1, DomainBoundary())
    test = Function(V)
    bc.apply(test.vector())

    if len(d) == 2:
        # two dimensions given: swap dimensions
        mesh.coordinates()[:, d] = mesh.coordinates()[:, d[::-1]]
    else:
        # one dimension given: reflect
        mesh.coordinates()[:, d[0]] *= -1
    # FIXME functionspace takes a long time to construct, maybe copy?
    W = FunctionSpace(mesh, 'CG', 1)
    try:
        # testing
        test = interpolate(Function(W, test.vector()), V)
        # max-min should be around 1 if domain was symmetric
        # may be slightly above due to boundary approximation
        assert max(test.vector()) - min(test.vector()) < 1.1

        v = interpolate(Function(W, u.vector()), V)
        if sym:
            # symmetric
            pr = project(u + v)
        else:
            # antisymmetric
            pr = project(u - v)
        # small solution norm most likely means that symmetrization gives
        # trivial function
        assert np.linalg.norm(pr.vector()) / nrm > 0.01
        return pr
    except:
        # symmetrization failed for some reason
        print "Symmetrization " + str(d) + " failed!"
        return u
示例#5
0
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
示例#6
0
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
示例#7
0
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())
示例#8
0
def load_h5_mesh(h5_file, scale_factor=None):
    '''Unpack to mesh, volumes and surfaces'''
    mesh = Mesh()
    h5 = HDF5File(mesh.mpi_comm(), h5_file, 'r')
    h5.read(mesh, 'mesh', False)
    # Convert units
    if scale_factor is not None:
        assert scale_factor > 0, "Scale factor must be a positive real number!"
        mesh.coordinates()[:] *= scale_factor

    surfaces = MeshFunction('size_t', mesh, mesh.topology().dim() - 1)
    h5.read(surfaces, 'surfaces')

    volumes = MeshFunction('size_t', mesh, mesh.topology().dim())
    h5.read(volumes, 'volumes')

    return mesh, volumes, surfaces
示例#9
0
def rotational(u, n):
    """ Symmetrize with respect to n-fold symmetry. """
    # TODO: test one rotation only
    V = u.function_space()
    if V.mesh().topology().dim() > 2 or n < 2:
        return u
    mesh = V.mesh()
    sum = u
    nrm = np.linalg.norm(u.vector())
    rotation = np.array([[np.cos(2*np.pi/n), np.sin(2*np.pi/n)],
                         [-np.sin(2*np.pi/n), np.cos(2*np.pi/n)]])
    for i in range(1, n):
        mesh = Mesh(mesh)
        mesh.coordinates()[:, :] = np.dot(mesh.coordinates(), rotation)
        W = FunctionSpace(mesh, 'CG', 1)
        v = interpolate(Function(W, u.vector()), V)
        sum += v
    pr = project(sum)
    if np.linalg.norm(pr.vector())/nrm > 0.01:
        return pr
    else:
        return u
示例#10
0
def rotational(u, n):
    """ Symmetrize with respect to n-fold symmetry. """
    # TODO: test one rotation only
    V = u.function_space()
    if V.mesh().topology().dim() > 2 or n < 2:
        return u
    mesh = V.mesh()
    sum = u
    nrm = np.linalg.norm(u.vector())
    rotation = np.array([[np.cos(2 * np.pi / n),
                          np.sin(2 * np.pi / n)],
                         [-np.sin(2 * np.pi / n),
                          np.cos(2 * np.pi / n)]])
    for i in range(1, n):
        mesh = Mesh(mesh)
        mesh.coordinates()[:, :] = np.dot(mesh.coordinates(), rotation)
        W = FunctionSpace(mesh, 'CG', 1)
        v = interpolate(Function(W, u.vector()), V)
        sum += v
    pr = project(sum)
    if np.linalg.norm(pr.vector()) / nrm > 0.01:
        return pr
    else:
        return u
示例#11
0
 def get_circle():
     filename = inspect.getframeinfo(inspect.currentframe()).filename
     home = os.path.dirname(os.path.abspath(filename))
     mesh = Mesh(home + '/test/circle.xml')
     mesh.coordinates()[:, 2] /= 1000.0
     return mesh
示例#12
0
class FBVP:
    def __init__(self,
                 mesh_name,
                 parameters,
                 alpha_range=None,
                 beta_range=None):
        # DEFINE 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
        if not beta_range:
            self.beta_range = parameters.beta_range
        else:
            self.beta_range = alpha_range
        meshname = \
            f'meshes/{parameters.domain}/{parameters.domain}_{self.mesh_name}.xdmf'

        self.mesh = Mesh()
        with XDMFFile(meshname) 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_V = FunctionSpace(self.t_mesh, 'CG', 1)
        self.T = self.parameters.T  # final time
        self.w = TrialFunction(self.V)
        self.u = TestFunction(self.V)
        #######################################################################
        # CONTROL SET CREATION
        self.ctrset_alpha = np.linspace(self.alpha_range[0],
                                        self.alpha_range[1],
                                        parameters.alpha_size)
        self.ctrset_beta = np.linspace(self.beta_range[0], self.beta_range[1],
                                       parameters.beta_size)
        self.csize_alpha = len(self.ctrset_alpha)
        self.csize_beta = len(self.ctrset_beta)
        print(f'Discretized control set has size'
              f' {self.csize_alpha * self.csize_beta}')
        #######################################################################
        # BOUNDARY CONDITIONS
        parameters.set_boundary_conditions(self.mesh)
        self.boundary_markers = MeshFunction('size_t', self.mesh, 1)
        self.boundary_markers.set_all(99)  # 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[i], self.boundary_markers,
                        i) for i in parameters.omegas
        ]

        # Get indices of dirichlet and dofs
        self.boundary_nodes_list = \
            DirichletBC(self.V, Constant('0.0'),
                        'on_boundary').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()  # assembly lumped mass matrix
        # which serves the role of identity operator
        self.assemble_laplacian()  # assembly of discrete laplacian

        self.ad_data_path = f'out/{parameters.experiment}'
        Path(self.ad_data_path).mkdir(parents=True, exist_ok=True)

        # FIXME: value of timestep required to impose monotonity may be
        # different at different times, at this moment code assumes that
        # it won't be smaller than in case t=T
        self.timesteps = 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 sparsity 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 - plays role of identity matrix
        """
        print("Assembling lumped mass matrix")
        mass_form = self.w * self.u * dx
        self.mass_matrix = assemble(mass_form)

        mass_action_form = action(mass_form, Constant(1))
        self.MM_terms = assemble(mass_action_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.csize_beta, self.csize_alpha],
                                          dtype=object)
        self.explicit_diffusion = np.empty([self.csize_beta, self.csize_alpha],
                                           dtype=object)
        diag_vector = Vector(self.mesh.mpi_comm(), self.dim)
        global_min_timestep = float('inf')
        # Create explicit operator for each control in control set
        for ind_a, a in enumerate(self.ctrset_alpha):
            for ind_b, b in enumerate(self.ctrset_beta):
                if (ind_a * self.csize_beta + ind_b) % 10 == 0:
                    print(f'Assembling explicit matrix under control '
                          f'{ind_a*self.csize_beta + ind_b} out of '
                          f'{self.csize_alpha*self.csize_beta}')
                # coefficients in the interior
                advection_x = self.parameters.adv_x(a, b)
                advection_y = self.parameters.adv_y(a, b)
                reaction = self.parameters.lin(a, b)

                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_int_form = (np.dot(b, grad(self.w)) +
                              reaction * self.w) * self.u * dx
                explicit_matrix = assemble(E_int_form)

                # Calculate nodewise minimum diffusion required to
                # impose monotonicity
                min_diffusion = [0] * 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[ind_b][ind_a] = min_diffusion

                diffusion_matrix = apply_ad(self.laplacian, min_diffusion)
                explicit_matrix += diffusion_matrix

                explicit_matrix.get_diagonal(diag_vector)
                current_min_timestep = get_min_timestep(
                    diag_vector, self.MM_terms, self.boundary_nodes_list)

                global_min_timestep = min(current_min_timestep,
                                          global_min_timestep)
                self.explicit_matrices[ind_b][ind_a] = 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 * E - self.scipy_mass_matrix for E in Elist
        ] for Elist in self.explicit_matrices]

        print('Checking if the explicit operators satisfy'
              ' monotonicity conditions')
        for matrix_list in self.explicit_matrices:
            for matrix in matrix_list:
                explicit_check(matrix, self.boundary_nodes_list)

    def assemble_RHS(self, t=None):
        """Assemble right hand side of the FBVP
        """
        print('Assembling RHS')
        self.forcing_terms = np.empty(
            [self.csize_beta, self.csize_alpha, self.dim])
        for ind_a, alpha in enumerate(self.ctrset_alpha):
            for ind_b, beta in enumerate(self.ctrset_beta):
                rhs = self.parameters.RHSt(alpha, beta)
                if t is not None:
                    rhs.t = t
                # Initialise forcing term
                F = np.array(assemble(rhs * self.u * dx)[:])
                self.forcing_terms[ind_b][ind_a] = 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 = np.empty([self.csize_beta, self.csize_alpha],
                                          dtype=object)
        remaining_diffusion = Function(self.V)
        max_art_dif = 0.0
        max_art_dif_loc = None
        diffusion_matrix = self.diag_matrix.copy()
        for ind_a, alpha in enumerate(self.ctrset_alpha):
            for ind_b, beta in enumerate(self.ctrset_beta):
                # TODO: Include assert making sure that diffusion is
                #  non-negative on all of the internal nodes
                diffusion = self.parameters.diffusion(alpha, beta)
                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)
                ])

                artdif = self.explicit_diffusion[ind_b][ind_a] - diff_vec
                if np.amax(artdif) > max_art_dif:
                    max_art_dif = np.amax(artdif)
                    max_art_dif_loc = self.coords[np.argmax(artdif)]

                # discretise second order terms
                remaining_diffusion.vector()[:] = np.maximum(
                    -artdif, [0] * self.dim)
                diffusion_matrix.set_diagonal(remaining_diffusion.vector())

                Iab = matmult(diffusion_matrix, self.laplacian)
                self.implicit_matrices[ind_b][ind_a] = self.timestep_size * \
                    Iab + self.mass_matrix

        self.scipy_implicit_matrices = [[toscipy(mat) for mat in Ialist]
                                        for Ialist in self.implicit_matrices]
        print('Checking if the implicit operators satisfy'
              ' monotonicity conditions')
        for matrix_list in self.scipy_implicit_matrices:
            for matrix in matrix_list:
                implicit_check(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)
示例#13
0
 def get_greenland_detailed():
   filename = inspect.getframeinfo(inspect.currentframe()).filename
   home     = os.path.dirname(os.path.abspath(filename))
   mesh     = Mesh(home + '/greenland/greenland_detailed_mesh.xml')
   mesh.coordinates()[:,2] /= 100000.0
   return mesh
示例#14
0
 def get_antarctica_coarse():
   filename = inspect.getframeinfo(inspect.currentframe()).filename
   home     = os.path.dirname(os.path.abspath(filename))
   mesh     = Mesh(home + '/antarctica/antarctica_50H_5l.xml')
   mesh.coordinates()[:,2] /= 1000.0
   return mesh
示例#15
0
 def get_circle():
   filename = inspect.getframeinfo(inspect.currentframe()).filename
   home     = os.path.dirname(os.path.abspath(filename))
   mesh     = Mesh(home + '/test/circle.xml')
   mesh.coordinates()[:,2] /= 1000.0
   return mesh
示例#16
0
V1 = FunctionSpace(mesh1, 'P', 1)
Vspace.append(V1)

mesh2 = Mesh("../level_3.xml")
V2 = FunctionSpace(mesh2, 'P', 1)
Vspace.append(V2)

mesh3 = Mesh("../level_4.xml")
V3 = FunctionSpace(mesh3, 'P', 1)
Vspace.append(V3)

# ==========================================================================

# perturb several nodes to make low quality meshes
# level one
coord = mesh.coordinates()
coord[33870] = np.array([0.84422225, 0.12003844, 0.85444908])
coord[40535] = np.array([0.84422365, 0.12005959, 0.85447413])
coord[18836] = np.array([0.4531466, 0.99313058, 0.4044442])
coord[62510] = np.array([0.4532232, 0.99307068, 0.40441478])
coord[70888] = np.array([0.76749874, 0.43333037, 0.15925556])
coord[83559] = np.array([0.76743825, 0.43337698, 0.15928119])
coord[27904] = np.array([0.85599083, 0.89423123, 0.54497891])
coord[43516] = np.array([0.85603036, 0.89426995, 0.54499929])

# level two
coord1 = mesh1.coordinates()
coord1[9475] = np.array([0.90587922, 0.01510134, 0.14508207])
coord1[5122] = np.array([0.52960123, 0.98707025, 0.69302149])
coord1[9700] = np.array([0.53421175, 0.9826492, 0.69542906])
coord1[6539] = np.array([0.2653881, 0.11901515, 0.66318406])
示例#17
0
    args = parser.parse_args()

    # Some sanity
    root, ext = os.path.splitext(args.tile)
    assert args.m > 0 and args.n > 0
    assert ext == '.h5'

    shape = (args.m, args.n)

    # Load the tile mesh
    h5 = HDF5File(get_comm_world(), args.tile, 'r')
    tile = Mesh()
    h5.read(tile, 'mesh', False)

    # Shift and so
    x = tile.coordinates()
    xmin = x.min(axis=0)
    dx = x.max(axis=0) - xmin

    if args.shift_origin: x[:] -= xmin

    x[:] *= args.scale_x

    # Did it work?
    print(dx, 'vs', x.max(axis=0) - x.min(axis=0), xmin, x.min(axis=0))

    data = {}
    cell_dim = tile.topology().dim()
    facet_dim = cell_dim - 1

    if args.facet_tags:
示例#18
0
data = MeshFunction('int', Mesh(path), 'widths.xml.gz')

out = subprocess.check_output(['gmsh', '--version'], stderr=subprocess.STDOUT)
assert out.split('.')[0] == '3', 'Gmsh 3+ is required'

ccall = 'gmsh -3 -optimize %s' % geo
subprocess.call(ccall, shell=True)

# Convert
xml_file = 'vasc_GMSH.xml'
msh_file = 'vacs_GMSH.msh'
convert2xml(msh_file, xml_file)

# Throw away the line function as it is expensive to store all the edges
meshXd = Mesh(xml_file)
# Make sure that the assumption of correspondence hold
assert np.linalg.norm(mesh.coordinates() -
                      meshXd.coordinates()[:mesh.num_vertices()]) < 1E-13

line_f = MeshFunction('size_t', meshXd, 'vasc_GMSH_edge_region.xml')

# Just a list of edges which are 1
tag_file = 'vasc_GMSH_vessel_tags.txt'
np.savetxt(tag_file, [edge.index() for edge in SubsetIterator(line_f, 1)])

# Transfer is not done here because I don't wantt to create embedded
# mesh etc
dt = timer.stop()
print 'Done in %g s' % dt, 'look for', xml_file, 'and', tag_file
示例#19
0
    parser.set_defaults(mesh_size=False)

    args = parser.parse_args()

    h5_file = convert(args.input)

    # VTK visualize tags
    if args.save_pvd or args.mesh_size:
        h5 = HDF5File(mpi_comm_world(), h5_file, 'r')
        mesh = Mesh()
        h5.read(mesh, 'mesh', False)

        info('Mesh has %d cells' % mesh.num_cells())
        info('Mesh has %d vertices' % mesh.num_vertices())
        info('Box size %s' %
             (mesh.coordinates().max(axis=0) - mesh.coordinates().min(axis=0)))

        hmin, hmax = mesh.hmin(), mesh.hmax()
        info('Mesh has sizes %g %g' % (hmin, hmax))

        root = os.path.splitext(args.input)[0]
        tdim = mesh.topology().dim()

        data_sets = ('curves', 'surfaces', 'volumes')
        dims = (1, tdim - 1, tdim)
        for ds, dim in zip(data_sets, dims):
            if h5.has_dataset(ds):
                f = MeshFunction('size_t', mesh, dim, 0)
                h5.read(f, ds)

                if args.save_pvd:
示例#20
0
def mesh_around_1d(mesh, size=1, scale=10, padding=0.05):
    '''
    From a 1d in xd (X > 1) mesh (in XML format) produce a Xd mesh where
    the 1d structure is embedded. Mesh size close to strucure should 
    be size(given as multiple of hmin(), elsewhere scale * size. Padding 
    controls size of the bounding box.
    '''
    dot = mesh.find('.')
    root, ext = mesh[:dot], mesh[dot:]
    assert ext == '.xml' or ext == '.xml.gz', ext

    mesh = Mesh(mesh)
    gdim = mesh.geometry().dim()
    assert gdim > 1 and mesh.topology().dim() == 1

    x = mesh.coordinates()
    mesh.init(1, 0)

    # Compute fall back mesh size:
    assert size > 0
    size = mesh.hmin() * size

    # Don't allow zero padding - collision of lines with bdry segfaults
    # too ofter so we prevent it
    assert padding > 0
    # Finally scale better be positive
    assert scale > 0

    point = (lambda xi: tuple(xi) + (0, ))\
            if gdim == 2 else (lambda xi: tuple(xi))

    geo = '.'.join([root, 'geo'])
    with open(geo, 'w') as outfile:
        # Setup
        outfile.write('SetFactory("OpenCASCADE");\n')
        outfile.write('size = %g;\n' % size)
        outfile.write('SIZE = %g;\n' % (size * scale))

        # Points
        fmt = 'Point(%d) = {%.16f, %.16f, %.16f, size};\n'
        for i, xi in enumerate(x, 1):
            outfile.write(fmt % ((i, ) + point(xi)))
        # Lines
        fmt = 'Line(%d) = {%d, %d};\n'
        for i, cell in enumerate(cells(mesh), 1):
            outfile.write(fmt % ((i, ) + tuple(cell.entities(0) + 1)))

        # BBox
        xmin, xmax = x.min(0), x.max(0)
        padding = (xmax - xmin) * padding / 2.
        xmin -= padding
        xmax += padding
        dx = xmax - xmin

        if gdim == 2 or dx[-1] < 1E-14:  # All points are on a plane
            rect = 'Rectangle(1) = {%g, %g, %g, %g, %g};\n' % (
                xmin[0], xmin[1], 0 if gdim == 2 else xmin[2], dx[0], dx[1])
            outfile.write(rect)
            bbox = 'Surface'
        else:
            box = 'Box(1) = {%g, %g, %g, %g, %g, %g};\n' % (
                xmin[0], xmin[1], xmin[2], dx[0], dx[1], dx[2])
            outfile.write(box)
            bbox = 'Volume'

        # Crack
        for line in xrange(1, mesh.num_cells() + 1):
            outfile.write('Line{%d} In %s{1};\n' % (line, bbox))

        # Add Physical volume/surface
        outfile.write('Physical %s(1) = {1};\n' % bbox)

        # Add Physical surface/line
        lines = ', '.join(
            map(lambda v: '%d' % v, xrange(1,
                                           mesh.num_cells() + 1)))
        outfile.write('Physical Line(1) = {%s};\n' % lines)
    return geo, gdim
示例#21
0
 def get_antarctica_coarse():
     filename = inspect.getframeinfo(inspect.currentframe()).filename
     home = os.path.dirname(os.path.abspath(filename))
     mesh = Mesh(home + '/antarctica/antarctica_50H_5l.xml')
     mesh.coordinates()[:, 2] /= 1000.0
     return mesh
示例#22
0
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)
示例#23
0
 def get_greenland_detailed():
     filename = inspect.getframeinfo(inspect.currentframe()).filename
     home = os.path.dirname(os.path.abspath(filename))
     mesh = Mesh(home + '/greenland/greenland_detailed_mesh.xml')
     mesh.coordinates()[:, 2] /= 100000.0
     return mesh
示例#24
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')