Exemple #1
0
def _fit_dolfin(x0,
                y0,
                points,
                cells,
                lmbda: float,
                degree: int = 1,
                solver: str = "lsqr"):
    from dolfin import (
        BoundingBoxTree,
        Cell,
        EigenMatrix,
        FacetNormal,
        Function,
        FunctionSpace,
        Mesh,
        MeshEditor,
        Point,
        TestFunction,
        TrialFunction,
        assemble,
        dot,
        ds,
        dx,
        grad,
    )

    def _assemble_eigen(form):
        L = EigenMatrix()
        assemble(form, tensor=L)
        return L

    def _build_eval_matrix(V, points):
        """Build the sparse m-by-n matrix that maps a coefficient set for a function in
        V to the values of that function at m given points."""
        # See <https://www.allanswered.com/post/lkbkm/#zxqgk>
        mesh = V.mesh()

        bbt = BoundingBoxTree()
        bbt.build(mesh)
        dofmap = V.dofmap()
        el = V.element()
        sdim = el.space_dimension()

        rows = []
        cols = []
        data = []
        for i, x in enumerate(points):
            cell_id = bbt.compute_first_entity_collision(Point(*x))
            cell = Cell(mesh, cell_id)
            coordinate_dofs = cell.get_vertex_coordinates()

            rows.append(np.full(sdim, i))
            cols.append(dofmap.cell_dofs(cell_id))

            v = el.evaluate_basis_all(x, coordinate_dofs, cell_id)
            data.append(v)

        rows = np.concatenate(rows)
        cols = np.concatenate(cols)
        data = np.concatenate(data)

        m = len(points)
        n = V.dim()
        matrix = sparse.csr_matrix((data, (rows, cols)), shape=(m, n))
        return matrix

    editor = MeshEditor()
    mesh = Mesh()

    # Convert points, cells to dolfin mesh
    if cells.shape[1] == 2:
        editor.open(mesh, "interval", 1, 1, 1)
    else:
        # can only handle triangles for now
        assert cells.shape[1] == 3
        # topological and geometrical dimension 2
        editor.open(mesh, "triangle", 2, 2, 1)

    editor.init_vertices(len(points))
    editor.init_cells(len(cells))
    for k, point in enumerate(points):
        editor.add_vertex(k, point)
    for k, cell in enumerate(cells.astype(np.uintp)):
        editor.add_cell(k, cell)
    editor.close()

    V = FunctionSpace(mesh, "CG", degree)

    u = TrialFunction(V)
    v = TestFunction(V)

    mesh = V.mesh()
    n = FacetNormal(mesh)

    # omega = assemble(1 * dx(mesh))

    A = _assemble_eigen(dot(grad(u), grad(v)) * dx -
                        dot(n, grad(u)) * v * ds).sparray()
    A *= lmbda

    E = _build_eval_matrix(V, x0)

    # mass matrix
    M = _assemble_eigen(u * v * dx).sparray()

    x = _solve(A, M, E, y0, solver)
    u = Function(V)
    u.vector().set_local(x)
    return u
Exemple #2
0
class Crucible:
    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
Exemple #3
0
def solve(mesh, Eps, degree):
    V = FunctionSpace(mesh, "CG", degree)
    u = TrialFunction(V)
    v = TestFunction(V)

    n = FacetNormal(mesh)

    gdim = mesh.geometry().dim()

    A = [
        _assemble_eigen(
            Constant(Eps[i, j]) * (u.dx(i) * v.dx(j) * dx - u.dx(i) * n[j] * v * ds)
        ).sparray()
        for j in range(gdim)
        for i in range(gdim)
    ]

    assert_equality = False
    if assert_equality:
        # The sum of the `A`s is exactly that:
        n = FacetNormal(V.mesh())
        AA = _assemble_eigen(
            +dot(dot(as_tensor(Eps), grad(u)), grad(v)) * dx
            - dot(dot(as_tensor(Eps), grad(u)), n) * v * ds
        ).sparray()
        diff = AA - sum(A)
        assert numpy.all(abs(diff.data) < 1.0e-14)
        #
        # ATAsum = sum(a.T.dot(a) for a in A)
        # diff = AA.T.dot(AA) - ATAsum
        # # import betterspy
        # # betterspy.show(ATAsum)
        # # betterspy.show(AA.T.dot(AA))
        # # betterspy.show(ATAsum - AA.T.dot(AA))
        # print(diff.data)
        # assert numpy.all(abs(diff.data) < 1.0e-14)

    tol = 1.0e-10

    def lower(x, on_boundary):
        return on_boundary and x[1] < -1.0 + tol

    def upper(x, on_boundary):
        return on_boundary and x[1] > 1.0 - tol

    def left(x, on_boundary):
        return on_boundary and abs(x[0] + 1.0) < tol

    def right(x, on_boundary):
        return on_boundary and abs(x[0] - 1.0) < tol

    def upper_left(x, on_boundary):
        return on_boundary and x[1] > +1.0 - tol and x[0] < -0.8

    def lower_right(x, on_boundary):
        return on_boundary and x[1] < -1.0 + tol and x[0] > 0.8

    bcs = [
        # DirichletBC(V, Constant(0.0), lower_right),
        # DirichletBC(V, Constant(0.0), upper_left),
        DirichletBC(V, Constant(0.0), lower),
        DirichletBC(V, Constant(0.0), upper),
        # DirichletBC(V, Constant(0.0), upper_left, method='pointwise'),
        # DirichletBC(V, Constant(0.0), lower_left, method='pointwise'),
        # DirichletBC(V, Constant(0.0), lower_right, method='pointwise'),
    ]

    M = _assemble_eigen(u * v * dx).sparray()

    ATMinvAsum = sum(
        numpy.dot(a.toarray().T, numpy.linalg.solve(M.toarray(), a.toarray()))
        for a in A
    )

    AA2 = _assemble_eigen(
        +dot(dot(as_tensor(Eps), grad(u)), grad(v)) * dx
        - dot(dot(as_tensor(Eps), grad(u)), n) * v * ds,
        # bcs=[DirichletBC(V, Constant(0.0), 'on_boundary')]
        # bcs=bcs
        # bcs=[
        #     DirichletBC(V, Constant(0.0), lower),
        #     DirichletBC(V, Constant(0.0), right),
        #     ]
    ).sparray()

    ATA2 = numpy.dot(AA2.toarray().T, numpy.linalg.solve(M.toarray(), AA2.toarray()))

    # Find combination of Dirichlet points:
    if False:
        # min_val = 1.0
        max_val = 0.0
        # min_combi = []
        max_combi = []
        is_updated = False
        it = 0
        # get boundary indices
        d = DirichletBC(V, Constant(0.0), "on_boundary")
        boundary_idx = numpy.sort(list(d.get_boundary_values().keys()))
        # boundary_idx = numpy.arange(V.dim())
        # print(boundary_idx)
        # pick some at random
        # idx = numpy.sort(numpy.random.choice(boundary_idx, size=3, replace=False))
        for idx in itertools.combinations(boundary_idx, 3):
            it += 1
            print()
            print(it)

            # Replace the rows corresponding to test functions living in one cell
            # (deliberately chosen as 0) by Dirichlet rows.
            AA3 = AA2.tolil()
            for k in idx:
                AA3[k] = 0
                AA3[k, k] = 1
            n = AA3.shape[0]
            AA3 = AA3.tocsr()

            ATA2 = numpy.dot(
                AA3.toarray().T, numpy.linalg.solve(M.toarray(), AA3.toarray())
            )
            vals = numpy.sort(numpy.linalg.eigvalsh(ATA2))
            if vals[0] < 0.0:
                continue

            # op = sparse.linalg.LinearOperator(
            #     (n, n),
            #     matvec=lambda x: _spsolve(AA3, M.dot(_spsolve(AA3.T.tocsr(), x)))
            #     )
            # vals, _ = scipy.sparse.linalg.eigsh(op, k=3, which='LM')
            # vals = numpy.sort(1/vals[::-1])
            # print(vals)

            print(idx)

            # if min_val > vals[0]:
            #     min_val = vals[0]
            #     min_combi = idx
            #     is_updated = True

            if max_val < vals[0]:
                max_val = vals[0]
                max_combi = idx
                is_updated = True

            if is_updated:
                # vals, _ = scipy.sparse.linalg.eigsh(op, k=10, which='LM')
                # vals = numpy.sort(1/vals[::-1])
                # print(vals)
                is_updated = False
                # print(min_val, min_combi)
                print(max_val, max_combi)
                # plt.plot(dofs_x[:, 0], dofs_x[:, 1], 'x')
                meshzoo.plot2d(mesh.coordinates(), mesh.cells())
                dofs_x = V.tabulate_dof_coordinates().reshape((-1, gdim))
                # plt.plot(dofs_x[min_combi, 0], dofs_x[min_combi, 1], 'or')
                plt.plot(dofs_x[max_combi, 0], dofs_x[max_combi, 1], "ob")
                plt.gca().set_aspect("equal")
                plt.title(f"smallest eigenvalue: {max_val}")
                plt.show()

            # # if True:
            # if abs(vals[0]) < 1.0e-8:
            #     gdim = mesh.geometry().dim()
            #     # plt.plot(dofs_x[:, 0], dofs_x[:, 1], 'x')
            #     X = mesh.coordinates()
            #     meshzoo.plot2d(mesh.coordinates(), mesh.cells())
            #     dofs_x = V.tabulate_dof_coordinates().reshape((-1, gdim))
            #     plt.plot(dofs_x[idx, 0], dofs_x[idx, 1], 'or')
            #     plt.gca().set_aspect('equal')
            #     plt.show()

        meshzoo.plot2d(mesh.coordinates(), mesh.cells())
        dofs_x = V.tabulate_dof_coordinates().reshape((-1, gdim))
        # plt.plot(dofs_x[min_combi, 0], dofs_x[min_combi, 1], 'or')
        plt.plot(dofs_x[max_combi, 0], dofs_x[max_combi, 1], "ob")
        plt.gca().set_aspect("equal")
        plt.title(f"final smallest eigenvalue: {max_val}")
        plt.show()
        exit(1)

    # Eigenvalues of the operators
    if True:
        # import betterspy
        # betterspy.show(sum(A), colormap="viridis")

        AA = _assemble_eigen(
            +dot(grad(u), grad(v)) * dx - dot(grad(u), n) * v * ds
        ).sparray()

        eigvals, eigvecs = scipy.sparse.linalg.eigs(AA, k=5, which="SM")

        assert numpy.all(numpy.abs(eigvals.imag) < 1.0e-12)
        eigvals = eigvals.real
        assert numpy.all(numpy.abs(eigvecs.imag) < 1.0e-12)
        eigvecs = eigvecs.real

        i = numpy.argsort(eigvals)
        print(eigvals[i])

        import meshio

        for k in range(3):
            meshio.write_points_cells(
                f"eigval{k}.vtk",
                points,
                {"triangle": cells},
                point_data={"ev": eigvecs[:, i][:, k]},
            )
        exit(1)

        # import betterspy
        # betterspy.show(AA, colormap="viridis")
        # print(numpy.sort(numpy.linalg.eigvals(AA.todense())))
        exit(1)

        Asum = sum(A).todense()
        AsumT_Minv_Asum = numpy.dot(Asum.T, numpy.linalg.solve(M.toarray(), Asum))

        # print(numpy.sort(numpy.linalg.eigvalsh(Asum)))
        print(numpy.sort(numpy.linalg.eigvalsh(AsumT_Minv_Asum)))
        exit(1)

        # eigvals, eigvecs = numpy.linalg.eigh(Asum)
        # i = numpy.argsort(eigvals)
        # print(eigvals[i])
        # exit(1)
        # print(eigvals[:20])
        # eigvals[eigvals < 1.0e-15] = 1.0e-15
        #
        # eigvals = numpy.sort(numpy.linalg.eigvalsh(sum(A).todense()))
        # print(eigvals[:20])
        # plt.semilogy(eigvals, ".", label="Asum")
        # plt.legend()
        # plt.grid()
        # plt.show()
        # exit(1)

        ATMinvAsum_eigs = numpy.sort(numpy.linalg.eigvalsh(ATMinvAsum))
        print(ATMinvAsum_eigs[:20])
        ATMinvAsum_eigs[ATMinvAsum_eigs < 0.0] = 1.0e-12
        # ATA2_eigs = numpy.sort(numpy.linalg.eigvalsh(ATA2))
        # print(ATA2_eigs[:20])
        plt.semilogy(ATMinvAsum_eigs, ".", label="ATMinvAsum")
        # plt.semilogy(ATA2_eigs, ".", label="ATA2")
        plt.legend()
        plt.grid()
        plt.show()
        # # Preconditioned eigenvalues
        # # IATA_eigs = numpy.sort(scipy.linalg.eigvalsh(ATMinvAsum, ATA2))
        # # plt.semilogy(IATA_eigs, ".", label="precond eigenvalues")
        # # plt.legend()
        # # plt.show()
        exit(1)

    # # Test with A only
    # numpy.random.seed(123)
    # b = numpy.random.rand(sum(a.shape[0] for a in A))
    # MTM = M.T.dot(M)
    # MTb = M.T.dot(b)
    # sol = _gmres(
    #     MTM,
    #     # TODO linear operator
    #     # lambda x: M.T.dot(M.dot(x)),
    #     MTb,
    #     M=prec
    #     )
    # plt.semilogy(sol.resnorms)
    # plt.show()
    # exit(1)

    n = AA2.shape[0]

    # define the operator
    def matvec(x):
        # M^{-1} can be computed in O(n) with CG + diagonal preconditioning
        # or algebraic multigrid.
        # return sum([a.T.dot(a.dot(x)) for a in A])
        return numpy.sum([a.T.dot(_spsolve(M, a.dot(x))) for a in A], axis=0)

    op = sparse.linalg.LinearOperator((n, n), matvec=matvec)

    # pick a random solution and a consistent rhs
    x = numpy.random.rand(n)
    b = op.dot(x)

    linear_system = krypy.linsys.LinearSystem(op, b)
    print("unpreconditioned solve...")
    t = time.time()
    out = krypy.linsys.Gmres(linear_system, tol=1.0e-12, explicit_residual=True)
    out.xk = out.xk.reshape(b.shape)
    print("done.")
    print("  res: {}".format(out.resnorms[-1]))
    print(
        "  unprec res: {}".format(
            numpy.linalg.norm(b - op.dot(out.xk)) / numpy.linalg.norm(b)
        )
    )
    # The error isn't useful here; only with the nullspace removed
    # print('  error: {}'.format(numpy.linalg.norm(out.xk - x)))
    print("  its: {}".format(len(out.resnorms)))
    print("  duration: {}s".format(time.time() - t))

    # preconditioned solver
    ml = pyamg.smoothed_aggregation_solver(AA2)
    # res = []
    # b = numpy.random.rand(AA2.shape[0])
    # x0 = numpy.zeros(AA2.shape[1])
    # x = ml.solve(b, x0, residuals=res, tol=1.0e-12)
    # print(res)
    # plt.semilogy(res)
    # plt.show()

    mlT = pyamg.smoothed_aggregation_solver(AA2.T.tocsr())
    # res = []
    # b = numpy.random.rand(AA2.shape[0])
    # x0 = numpy.zeros(AA2.shape[1])
    # x = mlT.solve(b, x0, residuals=res, tol=1.0e-12)

    # print(res)
    def prec_matvec(b):
        x0 = numpy.zeros(n)
        b1 = mlT.solve(b, x0, tol=1.0e-12)
        b2 = M.dot(b1)
        x = ml.solve(b2, x0, tol=1.0e-12)
        return x

    prec = LinearOperator((n, n), matvec=prec_matvec)

    # TODO assert this in a test
    # x = prec_matvec(b)
    # print(b - AA2.T.dot(AA2.dot(x)))

    linear_system = krypy.linsys.LinearSystem(op, b, Ml=prec)
    print()
    print("preconditioned solve...")
    t = time.time()
    try:
        out_prec = krypy.linsys.Gmres(
            linear_system, tol=1.0e-14, maxiter=1000, explicit_residual=True
        )
    except krypy.utils.ConvergenceError:
        print("prec not converged!")
        pass
    out_prec.xk = out_prec.xk.reshape(b.shape)
    print("done.")
    print("  res: {}".format(out_prec.resnorms[-1]))
    print(
        "  unprec res: {}".format(
            numpy.linalg.norm(b - op.dot(out_prec.xk)) / numpy.linalg.norm(b)
        )
    )
    print("  its: {}".format(len(out_prec.resnorms)))
    print("  duration: {}s".format(time.time() - t))

    plt.semilogy(out.resnorms, label="original")
    plt.semilogy(out_prec.resnorms, label="preconditioned")
    plt.legend()
    plt.show()

    return out.xk
Exemple #4
0
def get_mpi_comm(V: FunctionSpace):
    mpi_comm = V.mesh().mpi_comm()
    if not has_pybind11():
        mpi_comm = mpi_comm.tompi4py()
    return mpi_comm
Exemple #5
0
class CrucibleProblem():

    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
Exemple #6
0
def test(show=False):
    problem = problems.Crucible()
    # The voltage is defined as
    #
    #     v(t) = Im(exp(i omega t) v)
    #          = Im(exp(i (omega t + arg(v)))) |v|
    #          = sin(omega t + arg(v)) |v|.
    #
    # Hence, for a lagging voltage, arg(v) needs to be negative.
    voltages = [
        38.0 * numpy.exp(-1j * 2 * pi * 2 * 70.0 / 360.0),
        38.0 * numpy.exp(-1j * 2 * pi * 1 * 70.0 / 360.0),
        38.0 * numpy.exp(-1j * 2 * pi * 0 * 70.0 / 360.0),
        25.0 * numpy.exp(-1j * 2 * pi * 0 * 70.0 / 360.0),
        25.0 * numpy.exp(-1j * 2 * pi * 1 * 70.0 / 360.0),
    ]

    lorentz, joule, Phi = get_lorentz_joule(problem, voltages, show=show)

    # Some assertions
    ref = 1.4627674791126285e-05
    assert abs(norm(Phi[0], "L2") - ref) < 1.0e-3 * ref
    ref = 3.161363929287592e-05
    assert abs(norm(Phi[1], "L2") - ref) < 1.0e-3 * ref
    #
    ref = 12.115309575057681
    assert abs(norm(lorentz, "L2") - ref) < 1.0e-3 * ref
    #
    ref = 1406.336109054347
    V = FunctionSpace(problem.submesh_workpiece, "CG", 1)
    jp = project(joule, V)
    jp.rename("s", "Joule heat source")
    assert abs(norm(jp, "L2") - ref) < 1.0e-3 * ref

    # check_currents = False
    # if check_currents:
    #     r = SpatialCoordinate(problem.mesh)[0]
    #     begin('Currents computed after the fact:')
    #     k = 0
    #     with XDMFFile('currents.xdmf') as xdmf_file:
    #         for coil in coils:
    #             for ii in coil['rings']:
    #                 J_r = sigma[ii] * (
    #                     voltages[k].real/(2*pi*r) + problem.omega * Phi[1]
    #                     )
    #                 J_i = sigma[ii] * (
    #                     voltages[k].imag/(2*pi*r) - problem.omega * Phi[0]
    #                     )
    #                 alpha = assemble(J_r * dx(ii))
    #                 beta = assemble(J_i * dx(ii))
    #                 info('J = {:e} + i {:e}'.format(alpha, beta))
    #                 info(
    #                     '|J|/sqrt(2) = {:e}'.format(
    #                         numpy.sqrt(0.5 * (alpha**2 + beta**2))
    #                     ))
    #                 submesh = SubMesh(problem.mesh, problem.subdomains, ii)
    #                 V1 = FunctionSpace(submesh, 'CG', 1)
    #                 # Those projections may take *very* long.
    #                 # TODO find out why
    #                 j_v1 = [
    #                     project(J_r, V1),
    #                     project(J_i, V1)
    #                     ]
    #                 # show=Trueplot(j_v1[0], title='j_r')
    #                 # plot(j_v1[1], title='j_i')
    #                 current = project(as_vector(j_v1), V1*V1)
    #                 current.rename('j{}'.format(ii), 'current {}'.format(ii))
    #                 xdmf_file.write(current)
    #                 k += 1
    #     end()

    filename = "./maxwell.xdmf"
    with XDMFFile(filename) as xdmf_file:
        xdmf_file.parameters["flush_output"] = True
        xdmf_file.parameters["rewrite_function_mesh"] = False

        # Store phi
        info("Writing out Phi to {}...".format(filename))
        V = FunctionSpace(problem.mesh, "CG", 1)
        phi = Function(V, name="phi")
        Phi0 = project(Phi[0], V)
        Phi1 = project(Phi[1], V)
        omega = problem.omega
        for t in numpy.linspace(0.0, 2 * pi / omega, num=100, endpoint=False):
            # Im(Phi * exp(i*omega*t))
            phi.vector().zero()
            phi.vector().axpy(sin(problem.omega * t), Phi0.vector())
            phi.vector().axpy(cos(problem.omega * t), Phi1.vector())
            xdmf_file.write(phi, t)

        # Show the resulting magnetic field
        #
        #   B_r = -dphi/dz,
        #   B_z = 1/r d(rphi)/dr.
        #
        r = SpatialCoordinate(problem.mesh)[0]
        g = 1.0 / r * grad(r * Phi[0])
        V_element = FiniteElement("CG", V.mesh().ufl_cell(), 1)
        VV = FunctionSpace(V.mesh(), V_element * V_element)

        B_r = project(as_vector((-g[1], g[0])), VV)
        g = 1 / r * grad(r * Phi[1])
        B_i = project(as_vector((-g[1], g[0])), VV)
        info("Writing out B to {}...".format(filename))
        B = Function(VV)
        B.rename("B", "magnetic field")
        if abs(problem.omega) < DOLFIN_EPS:
            B.assign(B_r)
            xdmf_file.write(B)
            # plot(B_r, title='Re(B)')
            # plot(B_i, title='Im(B)')
        else:
            # Write those out to a file.
            lspace = numpy.linspace(
                0.0, 2 * pi / problem.omega, num=100, endpoint=False
            )
            for t in lspace:
                # Im(B * exp(i*omega*t))
                B.vector().zero()
                B.vector().axpy(sin(problem.omega * t), B_r.vector())
                B.vector().axpy(cos(problem.omega * t), B_i.vector())
                xdmf_file.write(B, t)

    filename = "./lorentz-joule.xdmf"
    info("Writing out Lorentz force and Joule heat source to {}...".format(filename))
    with XDMFFile(filename) as xdmf_file:
        xdmf_file.write(lorentz, 0.0)
        # xdmf_file.write(jp, 0.0)

    return
Exemple #7
0
def get_mpi_comm(V: FunctionSpace):
    mpi_comm = V.mesh().mpi_comm()
    return mpi_comm
Exemple #8
0
def solve(M, b, mesh, Eps):
    V = FunctionSpace(mesh, 'CG', 1)
    u = TrialFunction(V)
    v = TestFunction(V)

    n = FacetNormal(mesh)

    dim = mesh.geometry().dim()

    A = [
        [
            _assemble_eigen(+Constant(Eps[i, j]) * u.dx(i) * v.dx(j) * dx
                            # pylint: disable=unsubscriptable-object
                            - Constant(Eps[i, j]) * u.dx(i) * n[j] * v *
                            ds).sparray() for j in range(dim)
        ] for i in range(dim)
    ]
    Aflat = [item for sublist in A for item in sublist]

    assert_equality = False
    if assert_equality:
        # The sum of the `A`s is exactly that:
        n = FacetNormal(V.mesh())
        AA = _assemble_eigen(+dot(dot(as_tensor(Eps), grad(u)), grad(v)) * dx -
                             dot(dot(as_tensor(Eps), grad(u)), n) * v *
                             ds).sparray()
        diff = AA - sum(Aflat)
        assert numpy.all(abs(diff.data) < 1.0e-14)
        #
        # ATAsum = sum(a.T.dot(a) for a in Aflat)
        # diff = AA.T.dot(AA) - ATAsum
        # # import betterspy
        # # betterspy.show(ATAsum)
        # # betterspy.show(AA.T.dot(AA))
        # # betterspy.show(ATAsum - AA.T.dot(AA))
        # print(diff.data)
        # assert numpy.all(abs(diff.data) < 1.0e-14)

    tol = 1.0e-13

    def lower(x, on_boundary):
        return on_boundary and abs(x[1] + 1.0) < tol

    def right(x, on_boundary):
        return on_boundary and abs(x[0] - 1.0) < tol

    def upper_left(x, on_boundary):
        return on_boundary and (abs(x[0] + 1.0) < tol) and (abs(x[1] - 1.0) <
                                                            tol)

    def lower_left(x, on_boundary):
        return on_boundary and (abs(x[0] + 1.0) < tol) and (abs(x[1] + 1.0) <
                                                            tol)

    def lower_right(x, on_boundary):
        return on_boundary and (abs(x[0] - 1.0) < tol) and (abs(x[1] + 1.0) <
                                                            tol)

    bcs = [
        DirichletBC(V, Constant(0.0), upper_left, method='pointwise'),
        DirichletBC(V, Constant(0.0), lower_left, method='pointwise'),
        DirichletBC(V, Constant(0.0), lower_right, method='pointwise'),
    ]

    # TODO THIS IS IT
    AA2 = _assemble_eigen(
        +dot(dot(as_tensor(Eps), grad(u)), grad(v)) * dx -
        dot(dot(as_tensor(Eps), grad(u)), n) * v * ds,
        # bcs=[DirichletBC(V, Constant(0.0), 'on_boundary')]
        # bcs=bcs
        bcs=[
            DirichletBC(V, Constant(0.0), lower),
            DirichletBC(V, Constant(0.0), right),
        ]).sparray()

    ATA2 = AA2.dot(AA2)
    # ATAsum = sum(a.T.dot(a) for a in Aflat)

    # ATAsum_eigs = numpy.sort(numpy.linalg.eigvalsh(ATAsum.todense()))
    # print(ATAsum_eigs)
    # print()
    ATA2_eigs = numpy.sort(numpy.linalg.eigvalsh(ATA2.todense()))
    print(ATA2_eigs)
    exit(1)
    # plt.semilogy(range(len(ATAsum_eigs)), ATAsum_eigs, '.', label='ATAsum')
    # plt.semilogy(range(len(ATA2_eigs)), ATA2_eigs, '.', label='ATA2')
    # plt.legend()
    # plt.show()

    # # invsqrtATA2 = numpy.linalg.inv(scipy.linalg.sqrtm(ATA2.todense()))
    # # IATA = numpy.dot(numpy.dot(invsqrtATA2, ATAsum), invsqrtATA2)
    # # IATA_eigs = numpy.sort(numpy.linalg.eigvals(IATA))
    # IATA_eigs = numpy.sort(scipy.linalg.eigvals(ATAsum.todense(), ATA2.todense()))
    # plt.semilogy(range(len(IATA_eigs)), IATA_eigs, '.', label='IATA')
    # # plt.plot(IATA_eigs, numpy.zeros(len(IATA_eigs)), 'x', label='IATA')
    # plt.legend()
    # plt.show()
    # # exit(1)

    # # Test with A only
    # M = sparse.vstack(Aflat)
    # numpy.random.seed(123)
    # b = numpy.random.rand(sum(a.shape[0] for a in Aflat))
    # MTM = M.T.dot(M)
    # MTb = M.T.dot(b)
    # sol = _gmres(
    #     MTM,
    #     # TODO linear operator
    #     # lambda x: M.T.dot(M.dot(x)),
    #     MTb,
    #     M=prec
    #     )
    # plt.semilogy(sol.resnorms)
    # plt.show()
    # exit(1)

    ml = pyamg.smoothed_aggregation_solver(AA2)
    # res = []
    # b = numpy.random.rand(AA2.shape[0])
    # x0 = numpy.zeros(AA2.shape[1])
    # x = ml.solve(b, x0, residuals=res, tol=1.0e-12)
    # print(res)
    # plt.semilogy(res)
    # plt.show()

    mlT = pyamg.smoothed_aggregation_solver(AA2.T.tocsr())

    # res = []
    # b = numpy.random.rand(AA2.shape[0])
    # x0 = numpy.zeros(AA2.shape[1])
    # x = mlT.solve(b, x0, residuals=res, tol=1.0e-12)

    # print(res)
    def prec_matvec(b):
        n = len(b)
        x0 = numpy.zeros(n)
        b1 = mlT.solve(b, x0, tol=1.0e-12)
        x = ml.solve(b1, x0, tol=1.0e-12)
        return x

    n = AA2.shape[0]
    prec = LinearOperator((n, n), matvec=prec_matvec)

    # TODO assert this in a test
    # x = prec_matvec(b)
    # print(b - AA2.T.dot(AA2.dot(x)))

    MTM = sparse.linalg.LinearOperator((M.shape[1], M.shape[1]),
                                       matvec=lambda x: M.T.dot(M.dot(x)))

    linear_system = krypy.linsys.LinearSystem(MTM, M.T.dot(b), M=prec)
    out = krypy.linsys.Gmres(linear_system, tol=1.0e-12)

    # plt.semilogy(out.resnorms)
    # plt.show()

    return out.xk
Exemple #9
0
def test(show=False):
    problem = problems.Crucible()
    # The voltage is defined as
    #
    #     v(t) = Im(exp(i omega t) v)
    #          = Im(exp(i (omega t + arg(v)))) |v|
    #          = sin(omega t + arg(v)) |v|.
    #
    # Hence, for a lagging voltage, arg(v) needs to be negative.
    voltages = [
        38.0 * numpy.exp(-1j * 2 * pi * 2 * 70.0 / 360.0),
        38.0 * numpy.exp(-1j * 2 * pi * 1 * 70.0 / 360.0),
        38.0 * numpy.exp(-1j * 2 * pi * 0 * 70.0 / 360.0),
        25.0 * numpy.exp(-1j * 2 * pi * 0 * 70.0 / 360.0),
        25.0 * numpy.exp(-1j * 2 * pi * 1 * 70.0 / 360.0),
    ]

    lorentz, joule, Phi = get_lorentz_joule(problem, voltages, show=show)

    # Some assertions
    ref = 1.4627674791126285e-05
    assert abs(norm(Phi[0], "L2") - ref) < 1.0e-3 * ref
    ref = 3.161363929287592e-05
    assert abs(norm(Phi[1], "L2") - ref) < 1.0e-3 * ref
    #
    ref = 12.115309575057681
    assert abs(norm(lorentz, "L2") - ref) < 1.0e-3 * ref
    #
    ref = 1406.336109054347
    V = FunctionSpace(problem.submesh_workpiece, "CG", 1)
    jp = project(joule, V)
    jp.rename("s", "Joule heat source")
    assert abs(norm(jp, "L2") - ref) < 1.0e-3 * ref

    # check_currents = False
    # if check_currents:
    #     r = SpatialCoordinate(problem.mesh)[0]
    #     begin('Currents computed after the fact:')
    #     k = 0
    #     with XDMFFile('currents.xdmf') as xdmf_file:
    #         for coil in coils:
    #             for ii in coil['rings']:
    #                 J_r = sigma[ii] * (
    #                     voltages[k].real/(2*pi*r) + problem.omega * Phi[1]
    #                     )
    #                 J_i = sigma[ii] * (
    #                     voltages[k].imag/(2*pi*r) - problem.omega * Phi[0]
    #                     )
    #                 alpha = assemble(J_r * dx(ii))
    #                 beta = assemble(J_i * dx(ii))
    #                 info('J = {:e} + i {:e}'.format(alpha, beta))
    #                 info(
    #                     '|J|/sqrt(2) = {:e}'.format(
    #                         numpy.sqrt(0.5 * (alpha**2 + beta**2))
    #                     ))
    #                 submesh = SubMesh(problem.mesh, problem.subdomains, ii)
    #                 V1 = FunctionSpace(submesh, 'CG', 1)
    #                 # Those projections may take *very* long.
    #                 # TODO find out why
    #                 j_v1 = [
    #                     project(J_r, V1),
    #                     project(J_i, V1)
    #                     ]
    #                 # show=Trueplot(j_v1[0], title='j_r')
    #                 # plot(j_v1[1], title='j_i')
    #                 current = project(as_vector(j_v1), V1*V1)
    #                 current.rename('j{}'.format(ii), 'current {}'.format(ii))
    #                 xdmf_file.write(current)
    #                 k += 1
    #     end()

    filename = "./maxwell.xdmf"
    with XDMFFile(filename) as xdmf_file:
        xdmf_file.parameters["flush_output"] = True
        xdmf_file.parameters["rewrite_function_mesh"] = False

        # Store phi
        info("Writing out Phi to {}...".format(filename))
        V = FunctionSpace(problem.mesh, "CG", 1)
        phi = Function(V, name="phi")
        Phi0 = project(Phi[0], V)
        Phi1 = project(Phi[1], V)
        omega = problem.omega
        for t in numpy.linspace(0.0, 2 * pi / omega, num=100, endpoint=False):
            # Im(Phi * exp(i*omega*t))
            phi.vector().zero()
            phi.vector().axpy(sin(problem.omega * t), Phi0.vector())
            phi.vector().axpy(cos(problem.omega * t), Phi1.vector())
            xdmf_file.write(phi, t)

        # Show the resulting magnetic field
        #
        #   B_r = -dphi/dz,
        #   B_z = 1/r d(rphi)/dr.
        #
        r = SpatialCoordinate(problem.mesh)[0]
        g = 1.0 / r * grad(r * Phi[0])
        V_element = FiniteElement("CG", V.mesh().ufl_cell(), 1)
        VV = FunctionSpace(V.mesh(), V_element * V_element)

        B_r = project(as_vector((-g[1], g[0])), VV)
        g = 1 / r * grad(r * Phi[1])
        B_i = project(as_vector((-g[1], g[0])), VV)
        info("Writing out B to {}...".format(filename))
        B = Function(VV)
        B.rename("B", "magnetic field")
        if abs(problem.omega) < DOLFIN_EPS:
            B.assign(B_r)
            xdmf_file.write(B)
            # plot(B_r, title='Re(B)')
            # plot(B_i, title='Im(B)')
        else:
            # Write those out to a file.
            lspace = numpy.linspace(0.0,
                                    2 * pi / problem.omega,
                                    num=100,
                                    endpoint=False)
            for t in lspace:
                # Im(B * exp(i*omega*t))
                B.vector().zero()
                B.vector().axpy(sin(problem.omega * t), B_r.vector())
                B.vector().axpy(cos(problem.omega * t), B_i.vector())
                xdmf_file.write(B, t)

    filename = "./lorentz-joule.xdmf"
    info("Writing out Lorentz force and Joule heat source to {}...".format(
        filename))
    with XDMFFile(filename) as xdmf_file:
        xdmf_file.write(lorentz, 0.0)
        # xdmf_file.write(jp, 0.0)

    return