コード例 #1
0
ファイル: wrapped_mesh.py プロジェクト: getzze/magnum.fe
  def create(mesh, domain_ids, invert=False):
    """
    Creates a wrapped mesh from a super mesh for a given collection
    of domain IDs.

    *Arguments*
      mesh (:class:`dolfin.Mesh`)
        The mesh.
      domain_ids (:class:`[int]`)
        List of domain IDs
      invert (:class:`bool`)
        Invert list of domain IDs

    *Returns*
      :class:`WrappedMesh`
        The wrapped mesh
    """
    if invert or isinstance(domain_ids, list) or isinstance(domain_ids, tuple):
      if isinstance(domain_ids, int): domain_ids = (domain_ids,)

      subdomains = MeshFunction('size_t', mesh, 3, mesh.domains())
      combined_subdomains = CellFunction("size_t", mesh, 0)
      for domain_id in domain_ids:
        combined_subdomains.array()[subdomains.array() == domain_id] = 1

      submesh = SubMesh(mesh, combined_subdomains, 0 if invert else 1)
    else:
      submesh = SubMesh(mesh, domain_ids)

    submesh.__class__  = WrappedMesh
    submesh._init(mesh)

    return submesh
コード例 #2
0
ファイル: wrapped_mesh.py プロジェクト: Venkat004/magnum.fe
    def create(mesh, domain_ids, invert=False):
        """
    Creates a wrapped mesh from a super mesh for a given collection
    of domain IDs.

    *Arguments*
      mesh (:class:`dolfin.Mesh`)
        The mesh.
      domain_ids (:class:`[int]`)
        List of domain IDs
      invert (:class:`bool`)
        Invert list of domain IDs

    *Returns*
      :class:`WrappedMesh`
        The wrapped mesh
    """
        if invert or isinstance(domain_ids, list) or isinstance(
                domain_ids, tuple):
            if isinstance(domain_ids, int): domain_ids = (domain_ids, )

            subdomains = MeshFunction('size_t', mesh, 3, mesh.domains())
            combined_subdomains = CellFunction("size_t", mesh, 0)
            for domain_id in domain_ids:
                combined_subdomains.array()[subdomains.array() ==
                                            domain_id] = 1

            submesh = SubMesh(mesh, combined_subdomains, 0 if invert else 1)
        else:
            submesh = SubMesh(mesh, domain_ids)

        submesh.__class__ = WrappedMesh
        submesh._init(mesh)

        return submesh
コード例 #3
0
        def test_ale(self):

            print ""
            print "Testing ALE::move(Mesh& mesh0, const Mesh& mesh1)"

            # Create some mesh
            mesh = UnitSquareMesh(4, 5)

            # Make some cell function
            # FIXME: Initialization by array indexing is probably
            #        not a good way for parallel test
            cellfunc = CellFunction('size_t', mesh)
            cellfunc.array()[0:4] = 0
            cellfunc.array()[4:]  = 1

            # Create submeshes - this does not work in parallel
            submesh0 = SubMesh(mesh, cellfunc, 0)
            submesh1 = SubMesh(mesh, cellfunc, 1)

            # Move submesh0
            disp = Constant(("0.1", "-0.1"))
            submesh0.move(disp)

            # Move and smooth submesh1 accordignly
            submesh1.move(submesh0)

            # Move mesh accordingly
            parent_vertex_indices_0 = \
                     submesh0.data().array('parent_vertex_indices', 0)
            parent_vertex_indices_1 = \
                     submesh1.data().array('parent_vertex_indices', 0)
            mesh.coordinates()[parent_vertex_indices_0[:]] = \
                     submesh0.coordinates()[:]
            mesh.coordinates()[parent_vertex_indices_1[:]] = \
                     submesh1.coordinates()[:]

            # If test passes here then it is probably working
            # Check for cell quality for sure
            magic_number = 0.28
            rmin = MeshQuality.radius_ratio_min_max(mesh)[0]
            self.assertTrue(rmin > magic_number)
コード例 #4
0
ファイル: utils.py プロジェクト: MiroK/emi-book-fem
    def get_norm(u, uh, mesh=mesh):
        assert len(subdomains) == len(u)

        V = uh.function_space()
        assert mesh.id() == V.mesh().id()

        error = 0
        # NOTE: u is tag -> solution
        for subd, tag in subdomains:
            mesh_i = SubMesh(mesh, subd, tag)
            # Edge local function space
            Vi = FunctionSpace(mesh_i, V.ufl_element())
            # The solution on it
            uh_i = interpolate(uh, Vi)
            # And the error there
            error_i = norm(u[tag], uh_i)
            error += error_i**2
        error = sqrt(error)
        return error
コード例 #5
0
def test_ale():

    # Create some mesh
    mesh = UnitSquareMesh(4, 5)

    # Make some cell function
    # FIXME: Initialization by array indexing is probably
    #        not a good way for parallel test
    cellfunc = CellFunction('size_t', mesh)
    cellfunc.array()[0:4] = 0
    cellfunc.array()[4:] = 1

    # Create submeshes - this does not work in parallel
    submesh0 = SubMesh(mesh, cellfunc, 0)
    submesh1 = SubMesh(mesh, cellfunc, 1)

    # Move submesh0
    disp = Constant(("0.1", "-0.1"))
    ALE.move(submesh0, disp)

    # Move and smooth submesh1 accordignly
    ALE.move(submesh1, submesh0)

    # Move mesh accordingly
    parent_vertex_indices_0 = \
        submesh0.data().array('parent_vertex_indices', 0)
    parent_vertex_indices_1 = \
        submesh1.data().array('parent_vertex_indices', 0)
    mesh.coordinates()[parent_vertex_indices_0[:]] = \
        submesh0.coordinates()[:]
    mesh.coordinates()[parent_vertex_indices_1[:]] = \
        submesh1.coordinates()[:]

    # If test passes here then it is probably working
    # Check for cell quality for sure
    magic_number = 0.28
    rmin = MeshQuality.radius_ratio_min_max(mesh)[0]
    assert rmin > magic_number
コード例 #6
0
ファイル: utils.py プロジェクト: MiroK/emi-book-fem
        def get_norm(u, uh):
            assert len(subdomains) == len(u)

            V = uh.function_space()
            mesh = V.mesh()
            cell_f = MeshFunction('size_t', mesh, mesh.topology().dim(), 0)

            error = 0
            for subd_i, u_i in zip(subdomains, u):
                cell_f.set_all(0)  # Reset!
                # Pick an edge
                subd_i.mark(cell_f, 1)
                mesh_i = SubMesh(mesh, cell_f, 1)
                # Edge local function space
                Vi = FunctionSpace(mesh_i, V.ufl_element())
                # The solution on it
                uh_i = interpolate(uh, Vi)
                # And the error there
                error_i = norm(u_i, uh_i)
                error += error_i**2
            error = sqrt(error)
            return error
コード例 #7
0
ファイル: crucible.py プロジェクト: prklVIP/maelstrom
    def __init__(self):

        GMSH_EPS = 1.0e-15

        # https://fenicsproject.org/qa/12891/initialize-mesh-from-vertices-connectivities-at-once
        points, cells, point_data, cell_data, _ = meshes.crucible_with_coils.generate(
        )

        # Convert the cell data to 'uint' so we can pick a size_t MeshFunction
        # below as usual.
        for k0 in cell_data:
            for k1 in cell_data[k0]:
                cell_data[k0][k1] = numpy.array(cell_data[k0][k1],
                                                dtype=numpy.dtype("uint"))

        with TemporaryDirectory() as temp_dir:
            tmp_filename = os.path.join(temp_dir, "test.xml")
            meshio.write_points_cells(
                tmp_filename,
                points,
                cells,
                cell_data=cell_data,
                file_format="dolfin-xml",
            )
            self.mesh = Mesh(tmp_filename)
            self.subdomains = MeshFunction(
                "size_t", self.mesh,
                os.path.join(temp_dir, "test_gmsh:physical.xml"))

        self.subdomain_materials = {
            1: my_materials.porcelain,
            2: materials.argon,
            3: materials.gallium_arsenide_solid,
            4: materials.gallium_arsenide_liquid,
            27: materials.air,
        }

        # coils
        for k in range(5, 27):
            self.subdomain_materials[k] = my_materials.ek90

        # Define the subdomains which together form a single coil.
        self.coil_domains = [
            [5, 6, 7, 8, 9],
            [10, 11, 12, 13, 14],
            [15, 16, 17, 18, 19],
            [20, 21, 22, 23],
            [24, 25, 26],
        ]

        self.wpi = 4

        self.submesh_workpiece = SubMesh(self.mesh, self.subdomains, self.wpi)

        # http://fenicsproject.org/qa/2026/submesh-workaround-for-parallel-computation
        # submesh_parallel_bug_fixed = False
        # if submesh_parallel_bug_fixed:
        #     submesh_workpiece = SubMesh(self.mesh, self.subdomains, self.wpi)
        # else:
        #     # To get the mesh in parallel, we need to read it in from a file.
        #     # Writing out can only happen in serial mode, though. :/
        #     base = os.path.join(current_path,
        #                         '../../meshes/2d/crucible-with-coils-submesh'
        #                         )
        #     filename = base + '.xml'
        #     if not os.path.isfile(filename):
        #         warnings.warn(
        #             'Submesh file \'{}\' does not exist. Creating... '.format(
        #             filename
        #             ))
        #         if MPI.size(mpi_comm_world()) > 1:
        #             raise RuntimeError(
        #                 'Can only write submesh in serial mode.'
        #                 )
        #         submesh_workpiece = \
        #             SubMesh(self.mesh, self.subdomains, self.wpi)
        #         output_stream = File(filename)
        #         output_stream << submesh_workpiece
        #     # Read the mesh
        #     submesh_workpiece = Mesh(filename)

        coords = self.submesh_workpiece.coordinates()
        ymin = min(coords[:, 1])
        ymax = max(coords[:, 1])

        # Find the top right point.
        k = numpy.argmax(numpy.sum(coords, 1))
        topright = coords[k, :]

        # Initialize mesh function for boundary domains
        class Left(SubDomain):
            def inside(self, x, on_boundary):
                # Explicitly exclude the lowest and the highest point of the
                # symmetry axis.
                # It is necessary for the consistency of the pressure-Poisson
                # system in the Navier-Stokes solver that the velocity is
                # exactly 0 at the boundary r>0. Hence, at the corner points
                # (r=0, melt-crucible, melt-crystal) we must enforce u=0
                # already and cannot have a component in z-direction.
                return (on_boundary and x[0] < GMSH_EPS
                        and x[1] < ymax - GMSH_EPS and x[1] > ymin + GMSH_EPS)

        class Crucible(SubDomain):
            def inside(self, x, on_boundary):
                return on_boundary and (
                    (x[0] > GMSH_EPS and x[1] < ymax - GMSH_EPS) or
                    (x[0] > topright[0] - GMSH_EPS
                     and x[1] > topright[1] - GMSH_EPS) or
                    (x[0] < GMSH_EPS and x[1] < ymin + GMSH_EPS))

        # At the top right part (boundary melt--gas), slip is allowed, so only
        # n.u=0 is enforced. Very weirdly, the PPE is consistent if and only if
        # the end points of UpperRight are in UpperRight. This contrasts
        # Left(), where the end points must NOT belong to Left().  Judging from
        # the experiments, these settings do the right thing.
        # TODO try to better understand the PPE system/dolfin's boundary
        # settings
        class Upper(SubDomain):
            def inside(self, x, on_boundary):
                return on_boundary and x[1] > ymax - GMSH_EPS

        class UpperRight(SubDomain):
            def inside(self, x, on_boundary):
                return (on_boundary and x[1] > ymax - GMSH_EPS
                        and x[0] > 0.038 - GMSH_EPS)

        # The crystal boundary is taken to reach up to 0.038 where the
        # Dirichlet boundary data is about the melting point of the crystal,
        # 1511K. This setting gives pretty acceptable results when there is no
        # convection except the one induced by buoyancy. Is there is any more
        # stirring going on, though, the end point of the crystal with its
        # fixed temperature of 1511K might be the hottest point globally. This
        # looks rather unphysical.
        # TODO check out alternatives
        class UpperLeft(SubDomain):
            def inside(self, x, on_boundary):
                return (on_boundary and x[1] > ymax - GMSH_EPS
                        and x[0] < 0.038 + GMSH_EPS)

        left = Left()
        crucible = Crucible()
        upper_left = UpperLeft()
        upper_right = UpperRight()

        self.wp_boundaries = MeshFunction(
            "size_t",
            self.submesh_workpiece,
            self.submesh_workpiece.topology().dim() - 1,
        )
        self.wp_boundaries.set_all(0)
        left.mark(self.wp_boundaries, 1)
        crucible.mark(self.wp_boundaries, 2)
        upper_right.mark(self.wp_boundaries, 3)
        upper_left.mark(self.wp_boundaries, 4)

        if DEBUG:
            from dolfin import plot, interactive

            plot(self.wp_boundaries, title="Boundaries")
            interactive()

        submesh_boundary_indices = {
            "left": 1,
            "crucible": 2,
            "upper right": 3,
            "upper left": 4,
        }

        # Boundary conditions for the velocity.
        #
        # [1] Incompressible flow and the finite element method; volume two;
        #     Isothermal Laminar Flow;
        #     P.M. Gresho, R.L. Sani;
        #
        # For the choice of function space, [1] says:
        #     "In 2D, the triangular elements P_2^+P_1 and P_2^+P_{-1} are very
        #      good [...]. [...] If you wish to avoid bubble functions on
        #      triangular elements, P_2P_1 is not bad, and P_2(P_1+P_0) is even
        #      better [...]."
        #
        # It turns out that adding the bubble space significantly hampers the
        # convergence of the Stokes solver and also considerably increases the
        # time it takes to construct the Jacobian matrix of the Navier--Stokes
        # problem if no optimization is applied.
        V_element = FiniteElement("CG", self.submesh_workpiece.ufl_cell(), 2)
        with_bubbles = False
        if with_bubbles:
            V_element += FiniteElement("B", self.submesh_workpiece.ufl_cell(),
                                       2)
        self.W_element = MixedElement(3 * [V_element])
        self.W = FunctionSpace(self.submesh_workpiece, self.W_element)

        rot0 = Expression(("0.0", "0.0", "-2*pi*x[0] * 5.0/60.0"), degree=1)
        # rot0 = (0.0, 0.0, 0.0)
        rot1 = Expression(("0.0", "0.0", "2*pi*x[0] * 5.0/60.0"), degree=1)
        self.u_bcs = [
            DirichletBC(self.W, rot0, crucible),
            DirichletBC(self.W.sub(0), 0.0, left),
            DirichletBC(self.W.sub(2), 0.0, left),
            # Make sure that u[2] is 0 at r=0.
            DirichletBC(self.W, rot1, upper_left),
            DirichletBC(self.W.sub(1), 0.0, upper_right),
        ]
        self.p_bcs = []

        self.P_element = FiniteElement("CG", self.submesh_workpiece.ufl_cell(),
                                       1)
        self.P = FunctionSpace(self.submesh_workpiece, self.P_element)

        self.Q_element = FiniteElement("CG", self.submesh_workpiece.ufl_cell(),
                                       2)
        self.Q = FunctionSpace(self.submesh_workpiece, self.Q_element)

        # Dirichlet.
        # This is a bit of a tough call since the boundary conditions need to
        # be read from a Tecplot file here.
        filename = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                "data/crucible-boundary.dat")
        data = tecplot_reader.read(filename)
        RZ = numpy.c_[data["ZONE T"]["node data"]["r"],
                      data["ZONE T"]["node data"]["z"]]
        T_vals = data["ZONE T"]["node data"]["temp. [K]"]

        class TecplotDirichletBC(Expression):
            def eval(self, value, x):
                # Find on which edge x sits, and raise exception if it doesn't.
                edge_found = False
                for edge in data["ZONE T"]["element data"]:
                    # Given a point X and an edge X0--X1,
                    #
                    #     (1 - theta) X0 + theta X1,
                    #
                    # the minimum distance is assumed for
                    #
                    #    argmin_theta ||(1-theta) X0  + theta X1 - X||^2
                    #    = <X1 - X0, X - X0> / ||X1 - X0||^2.
                    #
                    # If the distance is 0 and 0<=theta<=1, we found the edge.
                    #
                    # Note that edges are 1-based in Tecplot.
                    X0 = RZ[edge[0] - 1]
                    X1 = RZ[edge[1] - 1]
                    theta = numpy.dot(X1 - X0, x - X0) / numpy.dot(
                        X1 - X0, X1 - X0)
                    diff = (1.0 - theta) * X0 + theta * X1 - x
                    if (numpy.dot(diff, diff) < 1.0e-10 and 0.0 <= theta
                            and theta <= 1.0):
                        # Linear interpolation of the temperature value.
                        value[0] = (1.0 - theta) * T_vals[
                            edge[0] - 1] + theta * T_vals[edge[1] - 1]
                        edge_found = True
                        break
                # This class is supposed to be used for Dirichlet boundary
                # conditions. For some reason, FEniCS also evaluates
                # DirichletBC objects at coordinates which do not sit on the
                # boundary, see
                # <http://fenicsproject.org/qa/1033/dirichletbc-expressions-evaluated-away-from-the-boundary>.
                # The assigned values have no meaning though, so not assigning
                # values[0] here is okay.
                #
                # from matplotlib import pyplot as pp
                # pp.plot(x[0], x[1], 'xg')
                if not edge_found:
                    value[0] = 0.0
                    if False:
                        warnings.warn(
                            "Coordinate ({:e}, {:e}) doesn't sit on edge.".
                            format(x[0], x[1]))
                    # pp.plot(RZ[:, 0], RZ[:, 1], '.k')
                    # pp.plot(x[0], x[1], 'xr')
                    # pp.show()
                    # raise RuntimeError('Input coordinate '
                    #                    '{} is not on boundary.'.format(x))
                return

        tecplot_dbc = TecplotDirichletBC(degree=5)
        self.theta_bcs_d = [DirichletBC(self.Q, tecplot_dbc, upper_left)]
        self.theta_bcs_d_strict = [
            DirichletBC(self.Q, tecplot_dbc, upper_right),
            DirichletBC(self.Q, tecplot_dbc, crucible),
            DirichletBC(self.Q, tecplot_dbc, upper_left),
        ]

        # Neumann
        dTdr_vals = data["ZONE T"]["node data"]["dTempdx [K/m]"]
        dTdz_vals = data["ZONE T"]["node data"]["dTempdz [K/m]"]

        class TecplotNeumannBC(Expression):
            def eval(self, value, x):
                # Same problem as above: This expression is not only evaluated
                # at boundaries.
                for edge in data["ZONE T"]["element data"]:
                    X0 = RZ[edge[0] - 1]
                    X1 = RZ[edge[1] - 1]
                    theta = numpy.dot(X1 - X0, x - X0) / numpy.dot(
                        X1 - X0, X1 - X0)
                    dist = numpy.linalg.norm((1 - theta) * X0 + theta * X1 - x)
                    if dist < 1.0e-5 and 0.0 <= theta and theta <= 1.0:
                        value[0] = (1 - theta) * dTdr_vals[
                            edge[0] - 1] + theta * dTdr_vals[edge[1] - 1]
                        value[1] = (1 - theta) * dTdz_vals[
                            edge[0] - 1] + theta * dTdz_vals[edge[1] - 1]
                        break
                return

            def value_shape(self):
                return (2, )

        tecplot_nbc = TecplotNeumannBC(degree=5)
        n = FacetNormal(self.Q.mesh())
        self.theta_bcs_n = {
            submesh_boundary_indices["upper right"]: dot(n, tecplot_nbc),
            submesh_boundary_indices["crucible"]: dot(n, tecplot_nbc),
        }
        self.theta_bcs_r = {}

        # It seems that the boundary conditions from above are inconsistent in
        # that solving with Dirichlet overall and mixed Dirichlet-Neumann give
        # different results; the value *cannot* correspond to one solution.
        # From looking at the solutions, the pure Dirichlet setting appears
        # correct, so extract the Neumann values directly from that solution.

        # Pick fixed coefficients roughly at the temperature that we expect.
        # This could be made less magic by having the coefficients depend on
        # theta and solving the quasilinear equation.
        temp_estimate = 1550.0

        # Get material parameters
        wp_material = self.subdomain_materials[self.wpi]
        if isinstance(wp_material.specific_heat_capacity, float):
            cp = wp_material.specific_heat_capacity
        else:
            cp = wp_material.specific_heat_capacity(temp_estimate)
        if isinstance(wp_material.density, float):
            rho = wp_material.density
        else:
            rho = wp_material.density(temp_estimate)
        if isinstance(wp_material.thermal_conductivity, float):
            k = wp_material.thermal_conductivity
        else:
            k = wp_material.thermal_conductivity(temp_estimate)

        reference_problem = cyl_heat.Heat(
            self.Q,
            convection=None,
            kappa=k,
            rho=rho,
            cp=cp,
            source=Constant(0.0),
            dirichlet_bcs=self.theta_bcs_d_strict,
        )
        theta_reference = reference_problem.solve_stationary()
        theta_reference.rename("theta", "temperature (Dirichlet)")

        # Create equivalent boundary conditions from theta_ref. This
        # makes sure that the potentially expensive Expression evaluation in
        # theta_bcs_* is replaced by something reasonably cheap.
        self.theta_bcs_d = [
            DirichletBC(bc.function_space(), theta_reference,
                        bc.domain_args[0]) for bc in self.theta_bcs_d
        ]
        # Adapt Neumann conditions.
        n = FacetNormal(self.Q.mesh())
        self.theta_bcs_n = {
            k: dot(n, grad(theta_reference))
            # k: Constant(1000.0)
            for k in self.theta_bcs_n
        }

        if DEBUG:
            # Solve the heat equation with the mixed Dirichlet-Neumann
            # boundary conditions and compare it to the Dirichlet-only
            # solution.
            theta_new = Function(self.Q,
                                 name="temperature (Neumann + Dirichlet)")
            from dolfin import Measure

            ds_workpiece = Measure("ds", subdomain_data=self.wp_boundaries)

            heat = cyl_heat.Heat(
                self.Q,
                convection=None,
                kappa=k,
                rho=rho,
                cp=cp,
                source=Constant(0.0),
                dirichlet_bcs=self.theta_bcs_d,
                neumann_bcs=self.theta_bcs_n,
                robin_bcs=self.theta_bcs_r,
                my_ds=ds_workpiece,
            )
            theta_new = heat.solve_stationary()
            theta_new.rename("theta", "temperature (Neumann + Dirichlet)")

            from dolfin import plot, interactive, errornorm

            print("||theta_new - theta_ref|| = {:e}".format(
                errornorm(theta_new, theta_reference)))
            plot(theta_reference)
            plot(theta_new)
            plot(theta_reference - theta_new, title="theta_ref - theta_new")
            interactive()

        self.background_temp = 1400.0

        # self.omega = 2 * pi * 10.0e3
        self.omega = 2 * pi * 300.0

        return
コード例 #8
0
        #mf = MeshFunction("size_t", mesh, mesh.ufl_cell().topological_dimension())
        #mf.set_all(0)

        #class Left(SubDomain):
        #    def inside(self, x, on_boundary):
        #        return x[0] < 0.4

        #Left().mark(mf, 1)
        cell_domains = CellFunction("size_t", mesh)
        cell_domains.set_all(0)
        subdomains = AutoSubDomain(lambda x: x[0] < 0.5)
        subdomains.mark(cell_domains, 1)

        if MPI.size(mpi_comm_world()) == 1:
            submesh = SubMesh(mesh, cell_domains, 1)
        else:
            submesh = create_submesh(mesh, cell_domains, 1)

        #MPI.barrier(mpi_comm_world())
        #continue
        V = FunctionSpace(submesh, "CG", 2)
        expr = Expression("x[0]*x[1]*x[1]+4*x[2]", degree=2)
        u = project(expr, V)

        MPI.barrier(mpi_comm_world())

        s0 = submesh.size_global(0)
        s3 = submesh.size_global(submesh.ufl_cell().topological_dimension())
        a = assemble(u * dx)
        v = assemble(Constant(1) * dx(domain=submesh))
コード例 #9
0
ファイル: si_in_air.py プロジェクト: philmain28/PySBS
em_solver.eigenmode_guess = core.em.e_r
em_solver.plot_eigenmodes = False
em_solver.assemble_matrices()
em_solver.set_electric_walls()
em_solver.setup_solver()
em_solver.compute_eigenvalues()
(E, n_eff1) = em_solver.extract_normalized_field(0)
# calculate ng
ng = n_eff1 - lam * (n_eff2 - n_eff1) / dlam
print("Group refractive index is %f " % (ng))
power_opt = em_solver.calculate_power(0, ng)
plot_transverse_field(E)
""" find the fundamental elastic mode """
q_b = 0.0
# need to extract submesh for silicon layer only
submesh = SubMesh(wg.mesh, wg.domains, 1)
core.domain = dx  # change domain to whole submesh
el_solver = ELSolver(submesh, core)
el_solver.n_modes = 10
el_solver.q_b = q_b
el_solver.plot_eigenmodes = False
el_solver.assemble_matrices()
el_solver.eigenmode_guess = 8.5
el_solver.setup_solver()
el_solver.compute_eigenvalues()
(u, freq_mech) = el_solver.extract_field(0)
plot_displacement(submesh, u)
power_mech = el_solver.calculate_power(0)
""" calculate forces and plot forces | calculate gain  """

from pysbs.gain.internal_boundary import InternalBoundary
コード例 #10
0
ファイル: crucible.py プロジェクト: garethcmurphy/maelstrom
    def __init__(self):
        import os
        from dolfin import Mesh, MeshFunction, SubMesh, SubDomain, \
            FacetFunction, DirichletBC, dot, grad, FunctionSpace, \
            MixedFunctionSpace, Expression, FacetNormal, pi, Function, \
            Constant, TestFunction, MPI, mpi_comm_world, File
        import numpy
        import warnings

        from maelstrom import heat_cylindrical as cyl_heat
        from maelstrom import materials_database as md

        GMSH_EPS = 1.0e-15

        current_path = os.path.dirname(os.path.realpath(__file__))
        base = os.path.join(
            current_path,
            '../../meshes/2d/crucible-with-coils'
            )
        self.mesh = Mesh(base + '.xml')
        self.subdomains = MeshFunction('size_t',
                                       self.mesh,
                                       base + '_physical_region.xml'
                                       )

        self.subdomain_materials = {
            1: md.get_material('porcelain'),
            2: md.get_material('argon'),
            3: md.get_material('GaAs (solid)'),
            4: md.get_material('GaAs (liquid)'),
            27: md.get_material('air')
            }

        # coils
        for k in range(5, 27):
            self.subdomain_materials[k] = md.get_material('graphite EK90')

        # Define the subdomains which together form a single coil.
        self.coil_domains = [
            [5, 6, 7, 8, 9],
            [10, 11, 12, 13, 14],
            [15, 16, 17, 18, 19],
            [20, 21, 22, 23],
            [24, 25, 26]
            ]

        self.wpi = 4
        # http://fenicsproject.org/qa/2026/submesh-workaround-for-parallel-computation
        submesh_parallel_bug_fixed = False
        if submesh_parallel_bug_fixed:
            submesh_workpiece = SubMesh(self.mesh, self.subdomains, self.wpi)
        else:
            # To get the mesh in parallel, we need to read it in from a file.
            # Writing out can only happen in serial mode, though. :/
            base = os.path.join(current_path,
                                '../../meshes/2d/crucible-with-coils-submesh'
                                )
            filename = base + '.xml'
            if not os.path.isfile(filename):
                warnings.warn(
                    'Submesh file \'%s\' does not exist. Creating... '
                    % filename
                    )
                if MPI.size(mpi_comm_world()) > 1:
                    raise RuntimeError(
                        'Can only write submesh in serial mode.'
                        )
                submesh_workpiece = \
                    SubMesh(self.mesh, self.subdomains, self.wpi)
                output_stream = File(filename)
                output_stream << submesh_workpiece
            # Read the mesh
            submesh_workpiece = Mesh(base + '.xml')

        coords = submesh_workpiece.coordinates()
        ymin = min(coords[:, 1])
        ymax = max(coords[:, 1])

        # Find the top right point.
        k = numpy.argmax(numpy.sum(coords, 1))
        topright = coords[k, :]

        # Initialize mesh function for boundary domains
        class Left(SubDomain):
            def inside(self, x, on_boundary):
                # Explicitly exclude the lowest and the highest point of the
                # symmetry axis.
                # It is necessary for the consistency of the pressure-Poisson
                # system in the Navier-Stokes solver that the velocity is
                # exactly 0 at the boundary r>0. Hence, at the corner points
                # (r=0, melt-crucible, melt-crystal) we must enforce u=0
                # already and cannot have a component in z-direction.
                return on_boundary \
                    and x[0] < GMSH_EPS \
                    and x[1] < ymax - GMSH_EPS \
                    and x[1] > ymin + GMSH_EPS

        class Crucible(SubDomain):
            def inside(self, x, on_boundary):
                return on_boundary \
                    and ((x[0] > GMSH_EPS and x[1] < ymax - GMSH_EPS)
                         or (x[0] > topright[0] - GMSH_EPS
                             and x[1] > topright[1] - GMSH_EPS)
                         or (x[0] < GMSH_EPS and x[1] < ymin + GMSH_EPS)
                         )

        # At the top right part (boundary melt--gas), slip is allowed, so only
        # n.u=0 is enforced. Very weirdly, the PPE is consistent if and only if
        # the end points of UpperRight are in UpperRight. This contrasts
        # Left(), where the end points must NOT belong to Left().  Judging from
        # the experiments, these settings do the right thing.
        # TODO try to better understand the PPE system/dolfin's boundary
        # settings
        class Upper(SubDomain):
            def inside(self, x, on_boundary):
                return on_boundary \
                    and x[1] > ymax - GMSH_EPS

        class UpperRight(SubDomain):
            def inside(self, x, on_boundary):
                return on_boundary \
                    and x[1] > ymax - GMSH_EPS \
                    and x[0] > 0.038 - GMSH_EPS

        # The crystal boundary is taken to reach up to 0.038 where the
        # Dirichlet boundary data is about the melting point of the crystal,
        # 1511K. This setting gives pretty acceptable results when there is no
        # convection except the one induced by buoyancy. Is there is any more
        # stirring going on, though, the end point of the crystal with its
        # fixed temperature of 1511K might be the hottest point globally. This
        # looks rather unphysical.
        # TODO check out alternatives
        class UpperLeft(SubDomain):
            def inside(self, x, on_boundary):
                return on_boundary \
                    and x[1] > ymax - GMSH_EPS \
                    and x[0] < 0.038 + GMSH_EPS

        left = Left()
        crucible = Crucible()
        upper_left = UpperLeft()
        upper_right = UpperRight()

        self.wp_boundaries = FacetFunction('size_t', submesh_workpiece)
        self.wp_boundaries.set_all(0)
        left.mark(self.wp_boundaries, 1)
        crucible.mark(self.wp_boundaries, 2)
        upper_right.mark(self.wp_boundaries, 3)
        upper_left.mark(self.wp_boundaries, 4)

        if DEBUG:
            from dolfin import plot, interactive
            plot(self.wp_boundaries, title='Boundaries')
            interactive()

        submesh_boundary_indices = {
            'left': 1,
            'crucible': 2,
            'upper right': 3,
            'upper left': 4
            }

        # Boundary conditions for the velocity.
        #
        # [1] Incompressible flow and the finite element method; volume two;
        #     Isothermal Laminar Flow;
        #     P.M. Gresho, R.L. Sani;
        #
        # For the choice of function space, [1] says:
        #     "In 2D, the triangular elements P_2^+P_1 and P_2^+P_{-1} are very
        #      good [...]. [...] If you wish to avoid bubble functions on
        #      triangular elements, P_2P_1 is not bad, and P_2(P_1+P_0) is even
        #      better [...]."
        #
        # It turns out that adding the bubble space significantly hampers the
        # convergence of the Stokes solver and also considerably increases the
        # time it takes to construct the Jacobian matrix of the Navier--Stokes
        # problem if no optimization is applied.
        V = FunctionSpace(submesh_workpiece, 'CG', 2)
        with_bubbles = False
        if with_bubbles:
            V += FunctionSpace(submesh_workpiece, 'B', 3)
        self.W = MixedFunctionSpace([V, V, V])

        self.u_bcs = [
            DirichletBC(self.W,
                        Expression(
                            ('0.0', '0.0', '-2*pi*x[0] * 5.0/60.0'),
                            degree=1
                            ),
                        #(0.0, 0.0, 0.0),
                        crucible),
            DirichletBC(self.W.sub(0), 0.0, left),
            DirichletBC(self.W.sub(2), 0.0, left),
            # Make sure that u[2] is 0 at r=0.
            DirichletBC(self.W,
                        Expression(
                            ('0.0', '0.0', '2*pi*x[0] * 5.0/60.0'),
                            degree=1
                            ),
                        upper_left),
            DirichletBC(self.W.sub(1), 0.0, upper_right),
            ]
        self.p_bcs = []

        self.P = FunctionSpace(submesh_workpiece, 'CG', 1)

        # Boundary conditions for heat equation.
        self.Q = FunctionSpace(submesh_workpiece, 'CG', 2)
        # Dirichlet.
        # This is a bit of a tough call since the boundary conditions need to
        # be read from a Tecplot file here.
        import tecplot_reader
        filename = os.path.join(
            os.path.dirname(os.path.realpath(__file__)),
            'data/crucible-boundary.dat'
            )
        data = tecplot_reader.read(filename)
        RZ = numpy.c_[data['ZONE T']['node data']['r'],
                      data['ZONE T']['node data']['z']
                      ]
        T_vals = data['ZONE T']['node data']['temp. [K]']

        class TecplotDirichletBC(Expression):
            # TODO specify degree
            def eval(self, value, x):
                # Find on which edge x sits, and raise exception if it doesn't.
                edge_found = False
                for edge in data['ZONE T']['element data']:
                    # Given a point X and an edge X0--X1,
                    #
                    #     (1 - theta) X0 + theta X1,
                    #
                    # the minimum distance is assumed for
                    #
                    #    argmin_theta ||(1-theta) X0  + theta X1 - X||^2
                    #    = <X1 - X0, X - X0> / ||X1 - X0||^2.
                    #
                    # If the distance is 0 and 0<=theta<=1, we found the edge.
                    #
                    # Note that edges are 1-based in Tecplot.
                    X0 = RZ[edge[0] - 1]
                    X1 = RZ[edge[1] - 1]
                    theta = numpy.dot(X1-X0, x-X0) / numpy.dot(X1-X0, X1-X0)
                    diff = (1.0-theta)*X0 + theta*X1 - x
                    if numpy.dot(diff, diff) < 1.0e-10 and \
                            0.0 <= theta and theta <= 1.0:
                        # Linear interpolation of the temperature value.
                        value[0] = (1.0-theta) * T_vals[edge[0]-1] \
                                 + theta       * T_vals[edge[1]-1]
                        edge_found = True
                        break
                # This class is supposed to be used for Dirichlet boundary
                # conditions. For some reason, FEniCS also evaluates
                # DirichletBC objects at coordinates which do not sit on the
                # boundary, see
                # <http://fenicsproject.org/qa/1033/dirichletbc-expressions-evaluated-away-from-the-boundary>.
                # The assigned values have no meaning though, so not assigning
                # values[0] here is okay.
                #
                #from matplotlib import pyplot as pp
                #pp.plot(x[0], x[1], 'xg')
                if not edge_found:
                    value[0] = 0.0
                    warnings.warn('Coordinate (%e, %e) doesn\'t sit on edge.'
                                  % (x[0], x[1]))
                    #pp.plot(RZ[:, 0], RZ[:, 1], '.k')
                    #pp.plot(x[0], x[1], 'xr')
                    #pp.show()
                    #raise RuntimeError('Input coordinate '
                    #                   '%r is not on boundary.' % x)
                return

        tecplot_dbc = TecplotDirichletBC()
        self.theta_bcs_d = [
            DirichletBC(self.Q, tecplot_dbc, upper_left)
            ]
        theta_bcs_d_strict = [
            DirichletBC(self.Q, tecplot_dbc, upper_right),
            DirichletBC(self.Q, tecplot_dbc, crucible),
            DirichletBC(self.Q, tecplot_dbc, upper_left)
            ]

        # Neumann
        dTdr_vals = data['ZONE T']['node data']['dTempdx [K/m]']
        dTdz_vals = data['ZONE T']['node data']['dTempdz [K/m]']

        class TecplotNeumannBC(Expression):
            # TODO specify degree
            def eval(self, value, x):
                # Same problem as above: This expression is not only evaluated
                # at boundaries.
                for edge in data['ZONE T']['element data']:
                    X0 = RZ[edge[0] - 1]
                    X1 = RZ[edge[1] - 1]
                    theta = numpy.dot(X1-X0, x-X0) / numpy.dot(X1-X0, X1-X0)
                    dist = numpy.linalg.norm((1-theta)*X0 + theta*X1 - x)
                    if dist < 1.0e-5 and 0.0 <= theta and theta <= 1.0:
                        value[0] = (1-theta) * dTdr_vals[edge[0]-1] \
                            + theta * dTdr_vals[edge[1]-1]
                        value[1] = (1-theta) * dTdz_vals[edge[0]-1] \
                            + theta * dTdz_vals[edge[1]-1]
                        break
                return

            def value_shape(self):
                return (2,)

        tecplot_nbc = TecplotNeumannBC()
        n = FacetNormal(self.Q.mesh())
        self.theta_bcs_n = {
            submesh_boundary_indices['upper right']: dot(n, tecplot_nbc),
            submesh_boundary_indices['crucible']: dot(n, tecplot_nbc)
            }
        self.theta_bcs_r = {}

        # It seems that the boundary conditions from above are inconsistent in
        # that solving with Dirichlet overall and mixed Dirichlet-Neumann give
        # different results; the value *cannot* correspond to one solution.
        # From looking at the solutions, the pure Dirichlet setting appears
        # correct, so extract the Neumann values directly from that solution.
        zeta = TestFunction(self.Q)

        theta_reference = Function(self.Q, name='temperature (Dirichlet)')
        theta_reference.vector()[:] = 0.0

        # Solve the *quasilinear* PDE (coefficients may depend on theta).
        # This is to avoid setting a fixed temperature for the coefficients.

        # Get material parameters
        wp_material = self.subdomain_materials[self.wpi]
        if isinstance(wp_material.specific_heat_capacity, float):
            cp = wp_material.specific_heat_capacity
        else:
            cp = wp_material.specific_heat_capacity(theta_reference)
        if isinstance(wp_material.density, float):
            rho = wp_material.density
        else:
            rho = wp_material.density(theta_reference)
        if isinstance(wp_material.thermal_conductivity, float):
            k = wp_material.thermal_conductivity
        else:
            k = wp_material.thermal_conductivity(theta_reference)

        reference_problem = cyl_heat.HeatCylindrical(
            self.Q, theta_reference,
            zeta,
            b=Constant((0.0, 0.0, 0.0)),
            kappa=k,
            rho=rho,
            cp=cp,
            source=Constant(0.0),
            dirichlet_bcs=theta_bcs_d_strict
            )

        from dolfin import solve
        solve(reference_problem.F0 == 0,
              theta_reference,
              bcs=theta_bcs_d_strict
              )

        # Create equivalent boundary conditions from theta_reference. This
        # makes sure that the potentially expensive Expression evaluation in
        # theta_bcs_* is replaced by something reasonably cheap.
        for k, bc in enumerate(self.theta_bcs_d):
            self.theta_bcs_d[k] = DirichletBC(bc.function_space(),
                                              theta_reference,
                                              bc.domain_args[0]
                                              )
        # Adapt Neumann conditions.
        n = FacetNormal(self.Q.mesh())
        for k in self.theta_bcs_n:
            self.theta_bcs_n[k] = dot(n, grad(theta_reference))

        if DEBUG:
            # Solve the heat equation with the mixed Dirichlet-Neumann
            # boundary conditions and compare it to the Dirichlet-only
            # solution.
            theta_new = Function(
                self.Q,
                name='temperature (Neumann + Dirichlet)'
                )
            from dolfin import Measure
            ds_workpiece = Measure('ds')[self.wp_boundaries]
            problem_new = cyl_heat.HeatCylindrical(
                self.Q, theta_new,
                zeta,
                b=Constant((0.0, 0.0, 0.0)),
                kappa=k,
                rho=rho,
                cp=cp,
                source=Constant(0.0),
                dirichlet_bcs=self.theta_bcs_d,
                neumann_bcs=self.theta_bcs_n,
                ds=ds_workpiece
                )

            from dolfin import solve
            solve(problem_new.F0 == 0,
                  theta_new,
                  bcs=problem_new.dirichlet_bcs
                  )
            from dolfin import plot, interactive, errornorm
            print('||theta_new - theta_ref|| = %e'
                  % errornorm(theta_new, theta_reference)
                  )
            plot(theta_reference)
            plot(theta_new)
            plot(
                theta_reference - theta_new,
                title='theta_ref - theta_new'
                )
            interactive()

        #omega = 2 * pi * 10.0e3
        self.omega = 2 * pi * 300.0

        return
コード例 #11
0
def get_lorentz_joule(problem, input_voltages, show=False):
    submesh_workpiece = problem.W.mesh()

    subdomain_indices = problem.subdomain_materials.keys()

    info("Input voltages:")
    info(repr(input_voltages))

    if input_voltages is None:
        return None, Constant(0.0)

    # Merge coil rings with voltages.
    coils = [{
        "rings": coil_domain,
        "c_type": "voltage",
        "c_value": voltage
    } for coil_domain, voltage in zip(problem.coil_domains, input_voltages)]
    # Build subdomain parameter dictionaries for Maxwell
    mu_const = {
        i: problem.subdomain_materials[i].magnetic_permeability
        for i in subdomain_indices
    }
    sigma_const = {
        i: problem.subdomain_materials[i].electrical_conductivity
        for i in subdomain_indices
    }

    # Function space for magnetic scalar potential, Lorentz force etc.
    V = FunctionSpace(problem.mesh, "CG", 1)
    # Compute the magnetic field.
    # The Maxwell equations depend on two parameters that change during the
    # computation: (a) the temperature, and (b) the velocity field u0. We
    # assume though that changes in either of the two will only marginally
    # influence the magnetic field. Consequently, we precompute all associated
    # values.
    dx_subdomains = Measure("dx", subdomain_data=problem.subdomains)
    with Message("Computing magnetic field..."):
        Phi, voltages = cmx.compute_potential(coils,
                                              V,
                                              dx_subdomains,
                                              mu_const,
                                              sigma_const,
                                              problem.omega,
                                              convections={}
                                              # io_submesh=submesh_workpiece
                                              )
        # Get resulting Lorentz force.
        lorentz = cmx.compute_lorentz(Phi, problem.omega,
                                      sigma_const[problem.wpi])

        # Show the Lorentz force in the workpiece.
        # W_element = VectorElement('CG', submesh_workpiece.ufl_cell(), 1)
        # First project onto the entire mesh, then onto the submesh; see bug
        # <https://bitbucket.org/fenics-project/dolfin/issues/869/projecting-grad-onto-submesh-error>.
        W = VectorFunctionSpace(problem.mesh, "CG", 1)
        pl = project(lorentz, W)
        W2 = VectorFunctionSpace(submesh_workpiece, "CG", 1)
        pl = project(pl, W2)
        pl.rename("Lorentz force", "Lorentz force")
        with XDMFFile(submesh_workpiece.mpi_comm(), "lorentz.xdmf") as f:
            f.parameters["flush_output"] = True
            f.write(pl)

        if show:
            tri = plot(pl, title="Lorentz force")
            plt.colorbar(tri)
            plt.show()

        # Get Joule heat source.
        joule = cmx.compute_joule(Phi, voltages, problem.omega, sigma_const,
                                  mu_const, subdomain_indices)

        if show:
            # Show Joule heat source.
            submesh = SubMesh(problem.mesh, problem.subdomains, problem.wpi)
            W_submesh = FunctionSpace(submesh, "CG", 1)
            jp = Function(W_submesh, name="Joule heat source")
            jp.assign(project(joule[problem.wpi], W_submesh))
            tri = plot(jp)
            plt.title("Joule heat source")
            plt.colorbar(tri)
            plt.show()

        joule_wpi = joule[problem.wpi]

    # To work around bug
    # <https://bitbucket.org/fenics-project/dolfin/issues/869/projecting-grad-onto-submesh-error>.
    # return the projection `pl` and not `lorentz` itself.
    # TODO remove this workaround
    return pl, joule_wpi, Phi
コード例 #12
0
def peter():
    base = "../meshes/2d/peter"
    # base = '../meshes/2d/peter-fine'
    mesh = Mesh(base + ".xml")

    subdomains = MeshFunction("size_t", mesh, base + "_physical_region.xml")

    workpiece_index = 3
    subdomain_materials = {1: "SiC", 2: "carbon steel", 3: "GaAs (liquid)"}

    submesh_workpiece = SubMesh(mesh, subdomains, workpiece_index)
    W = VectorFunctionSpace(submesh_workpiece, "CG", 2)
    Q = FunctionSpace(submesh_workpiece, "CG", 2)

    # Define melt boundaries.
    class LeftBoundary(SubDomain):
        def inside(self, x, on_boundary):
            # Be especially careful in the corners when defining the r=0 axis:
            # For the PPE to be consistent, we need to have n.u=0 *everywhere*
            # on the non-(r=0) boundaries.
            return (
                on_boundary
                and x[0] < GMSH_EPS
                and 0.005 + GMSH_EPS < x[1]
                and x[1] < 0.050 - GMSH_EPS
            )

    class SurfaceBoundary(SubDomain):
        def inside(self, x, on_boundary):
            return (
                on_boundary
                and 0.055 - GMSH_EPS < x[1]
                and 0.030 - GMSH_EPS < x[0]
                and x[0] < 0.075 + GMSH_EPS
            )

    class StampBoundary(SubDomain):
        def inside(self, x, on_boundary):
            # Well well. If we don't include the endpoints here, the PPE turns
            # out inconsistent. This is weird since the endpoints should be
            # picked up by RightBoundary and StampBoundary.
            return (
                on_boundary
                and 0.050 - GMSH_EPS < x[1]
                and x[1] < 0.055 + GMSH_EPS
                and x[0] < 0.030 + GMSH_EPS
            )

    class LowerBoundary(SubDomain):
        def inside(self, x, on_boundary):
            # Danger, danger.
            # If the rightmost point (0.075, 0.010) is excluded, the PPE will
            # end up inconsistent.
            # This is actually weird since it should be picked up by
            # RightBoundary.
            return on_boundary and (
                x[1] < 0.005 + GMSH_EPS
                or (x[0] > 0.070 - GMSH_EPS and x[1] < 0.010 + GMSH_EPS)
            )

    class RightBoundary(SubDomain):
        def inside(self, x, on_boundary):
            return on_boundary and x[0] > 0.075 - GMSH_EPS

    left_boundary = LeftBoundary()
    lower_boundary = LowerBoundary()
    right_boundary = RightBoundary()
    surface_boundary = SurfaceBoundary()
    stamp_boundary = StampBoundary()

    # Define workpiece boundaries.
    wp_boundaries = MeshFunction(
        "size_t", submesh_workpiece, self.submesh_workpiece.topology().dim() - 1
    )
    wp_boundaries.set_all(0)
    left_boundary.mark(wp_boundaries, 1)
    lower_boundary.mark(wp_boundaries, 2)
    right_boundary.mark(wp_boundaries, 3)
    surface_boundary.mark(wp_boundaries, 4)
    stamp_boundary.mark(wp_boundaries, 5)

    # For local use only:
    wp_boundary_indices = {"left": 1, "lower": 2, "right": 3, "surface": 4, "stamp": 5}

    # Boundary conditions for the velocity.
    u_bcs = [
        DirichletBC(W, (0.0, 0.0), stamp_boundary),
        DirichletBC(W, (0.0, 0.0), right_boundary),
        DirichletBC(W, (0.0, 0.0), lower_boundary),
        DirichletBC(W.sub(0), 0.0, left_boundary),
        DirichletBC(W.sub(1), 0.0, surface_boundary),
    ]
    p_bcs = []

    # Boundary conditions for the heat equation.
    # Dirichlet
    theta_bcs_d = []
    # Neumann
    theta_bcs_n = {}
    # Robin, i.e.,
    #
    #    -dtheta/dn = alpha (theta - theta0)
    #
    # (with alpha>0 to preserve coercivity of the scheme).
    #
    theta_bcs_r = {
        wp_boundary_indices["stamp"]: (100.0, 1500.0),
        wp_boundary_indices["surface"]: (100.0, 1550.0),
        wp_boundary_indices["right"]: (300.0, Expression("200*x[0] + 1600", degree=1)),
        wp_boundary_indices["lower"]: (
            300.0,
            Expression("-1200*x[1] + 1614", degree=1),
        ),
    }
    return (
        mesh,
        subdomains,
        subdomain_materials,
        workpiece_index,
        wp_boundaries,
        W,
        u_bcs,
        p_bcs,
        Q,
        theta_bcs_d,
        theta_bcs_n,
        theta_bcs_r,
    )
コード例 #13
0
def crucible_without_coils():
    base = "../meshes/2d/crucible"
    mesh = Mesh(base + ".xml")
    subdomains = MeshFunction("size_t", mesh, base + "_physical_region.xml")
    workpiece_index = 4
    subdomain_materials = {
        1: "SiC",
        2: "boron trioxide",
        3: "GaAs (solid)",
        4: "GaAs (liquid)",
    }

    submesh_workpiece = SubMesh(mesh, subdomains, workpiece_index)
    V = VectorFunctionSpace(submesh_workpiece, "CG", 2)
    P = FunctionSpace(submesh_workpiece, "CG", 1)

    Q = FunctionSpace(mesh, "CG", 2)

    # Define mesh and boundaries.
    class LeftBoundary(SubDomain):
        def inside(self, x, on_boundary):
            # Be especially careful in the corners when defining the r=0 axis:
            # For the PPE to be consistent, we need to have n.u=0 *everywhere*
            # on the non-r=0 boundaries.
            return (
                on_boundary
                and x[0] < GMSH_EPS
                and 0.366 + GMSH_EPS < x[1]
                and x[1] < 0.411 - GMSH_EPS
            )

    left_boundary = LeftBoundary()

    class SurfaceBoundary(SubDomain):
        def inside(self, x, on_boundary):
            # Make sure to catch the entire surface, so don't be too
            # restrictive about x[0].
            return (
                on_boundary
                and x[1] > 0.38 - GMSH_EPS
                and x[0] > 0.04 - GMSH_EPS
                and x[0] < 0.07 + GMSH_EPS
            )

    class OtherBoundary(SubDomain):
        def inside(self, x, on_boundary):
            return (
                on_boundary
                and not left_boundary.inside(x, on_boundary)
                # and not surface_boundary.inside(x, on_boundary)
            )

    other_boundary = OtherBoundary()

    # Boundary conditions for the velocity.
    u_bcs = [
        DirichletBC(V, (0.0, 0.0), other_boundary),
        DirichletBC(V.sub(0), 0.0, left_boundary),
        # DirichletBC(V.sub(1), 0.0, surface_boundary),
    ]
    # u_bcs = [DirichletBC(V, (0.0, 0.0), 'on_boundary')]
    p_bcs = []

    class HeaterBoundary(SubDomain):
        def inside(self, x, on_boundary):
            return on_boundary and (
                (x[1] < 0.38 and GMSH_EPS < x[0] and x[0] < 0.075 + GMSH_EPS)
                or x[0] < GMSH_EPS
                and x[1] < 0.365 + GMSH_EPS
            )

    heater_boundary = HeaterBoundary()

    background_temp = 1400.0

    # Heat with 1580K at r=0, 1590K at r=0.075.
    heater_bcs = [(heater_boundary, "1580.0 + 133.0 * x[0]")]

    return (
        mesh,
        subdomains,
        subdomain_materials,
        workpiece_index,
        V,
        Q,
        P,
        u_bcs,
        p_bcs,
        background_temp,
        heater_bcs,
    )