Ejemplo n.º 1
0
  def visualize(self, it=0):
    var = "cell_powers"
    try:
      should_vis = divmod(it, self.parameters["visualization"][var])[1] == 0
    except ZeroDivisionError:
      should_vis = False

    if should_vis:
      if not self.up_to_date[var]:
        self.calculate_cell_powers()
      else:
        qfun = Function(self.DD.V0)
        qfun.vector()[:] = self.E
        qfun.rename("q", "Power")
        self.vis_files["cell_powers"] << (qfun, float(it))

    var = "flux"
    try:
      should_vis = divmod(it, self.parameters["visualization"][var])[1] == 0
    except ZeroDivisionError:
      should_vis = False

    if should_vis:
      if not self.up_to_date[var]:
        self.update_phi()

      for g in xrange(self.DD.G):
        self.vis_files["flux"][g] << (self.phi_mg[g], float(it))
Ejemplo n.º 2
0
  def expand(self, f, target = None):
    """
    Takes a function defined on the sub mesh and returns a function
    defined on the super mesh with unknown values set to zero.

    *Arguments*
      f (:class:`dolfin.Function`)
        The function on the sub mesh.

    *Returns*
      The function on the super mesh.
    """

    mapping = self._get_mapping(f.function_space())
    if target is None:
      target = Function(mapping['Vsuper'])
      target.rename(f.name(), f.label())
    target.vector()[mapping['map']] = f.vector().array()
    return target
Ejemplo n.º 3
0
  def cut(self, f, **kwargs):
    """
    Takes a function defined on the super mesh and returns a truncated
    function defined on the sub mesh.

    *Arguments*
      f (:class:`dolfin.Function`)
        The function on the super mesh.

    *Returns*
      :class:`dolfin.Function`
        The function on the sub mesh.
    """

    mapping = self._get_mapping(f.function_space())
    result = Function(mapping['Vsub'])
    result.vector()[:] = f.vector().array()[mapping['map']]
    result.rename(f.name(), f.label())
    return result
Ejemplo n.º 4
0
    def expand(self, f, target=None):
        """
    Takes a function defined on the sub mesh and returns a function
    defined on the super mesh with unknown values set to zero.

    *Arguments*
      f (:class:`dolfin.Function`)
        The function on the sub mesh.

    *Returns*
      The function on the super mesh.
    """

        mapping = self._get_mapping(f.function_space())
        if target is None:
            target = Function(mapping['Vsuper'])
            target.rename(f.name(), f.label())
        target.vector()[mapping['map']] = f.vector().array()
        return target
Ejemplo n.º 5
0
    def cut(self, f, **kwargs):
        """
    Takes a function defined on the super mesh and returns a truncated
    function defined on the sub mesh.

    *Arguments*
      f (:class:`dolfin.Function`)
        The function on the super mesh.

    *Returns*
      :class:`dolfin.Function`
        The function on the sub mesh.
    """

        mapping = self._get_mapping(f.function_space())
        result = Function(mapping['Vsub'])
        result.vector()[:] = f.vector().array()[mapping['map']]
        result.rename(f.name(), f.label())
        return result
def expand_vp_dolfunc(PrP, vp=None, vc=None, pc=None, pdof=None):
    """expand v and p to the dolfin function representation

    pdof = pressure dof that was set zero
    """

    v = Function(PrP.V)
    p = Function(PrP.Q)

    if vp is not None:
        if vp.ndim == 1:
            vc = vp[:len(PrP.invinds)].reshape(len(PrP.invinds), 1)
            pc = vp[len(PrP.invinds):].reshape(PrP.Q.dim() - 1, 1)
        else:
            vc = vp[:len(PrP.invinds), :]
            pc = vp[len(PrP.invinds):, :]

    ve = np.zeros((PrP.V.dim(), 1))

    # fill in the boundary values
    for bc in PrP.velbcs:
        bcdict = bc.get_boundary_values()
        ve[bcdict.keys(), 0] = bcdict.values()

    ve[PrP.invinds] = vc

    if pdof is None:
        pe = pc
    elif pdof == 0:
        pe = np.vstack([[0], pc])
    elif pdof == -1:
        pe = np.vstack([pc, [0]])
    else:
        pe = np.vstack([pc[:pdof], np.vstack([[0], pc[pdof:]])])

    v.vector().set_local(ve)
    p.vector().set_local(pe)

    v.rename("v", "field")
    p.rename("p", "field")

    return v, p
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
  def __init__(self, PD, DD, verbosity):
    """
    Constructor
    :param ProblemData PD: Problem information and various mesh-region <-> xs-material mappings
    :param Discretization DD: Discretization data
    :param int verbosity: Verbosity level.
    """

    super(FluxModule, self).__init__()

    self.verb = verbosity
    self.print_prefix = ""

    self.mat_file_name = dict()

    self.PD = PD
    self.DD = DD
    self.BC = PD.bc

    try:
      self.fixed_source_problem = PD.fixed_source_problem
      self.eigenproblem = PD.eigenproblem
    except AttributeError:
      PD.distribute_material_data(DD.cell_regions, DD.M)

    self.A = PETScMatrix()

    # unused in case of an eigenvalue problem
    self.Q = PETScVector()

    # used only for saving the algebraic system
    self.rows_A = None
    self.cols_A = None
    self.vals_A = None
    self.rows_B = None
    self.cols_B = None
    self.vals_B = None

    if self.fixed_source_problem:
      self.fixed_source = Function(self.DD.V0)
      self.vals_Q = None

    # multigroup scalar fluxes
    self.phi_mg = []
    for g in range(self.DD.G):
      phig = Function(self.DD.Vphi1)
      phig.rename("phi","phi_g{}".format(g))
      self.phi_mg.append(phig)

    self.sln = Function(DD.V)
    self.sln_vec = as_backend_type(self.sln.vector())
    self.local_sln_size = self.sln_vec.local_size()

    # auxiliary function for storing various DG(0) quantities (cross sections, group-integrated reaction rates, etc.)
    self.R = Function(self.DD.V0)

    # fission spectrum
    if 'chi' in self.PD.used_xs:
      self.chi = Function(self.DD.V0)
    else:
      self.chi = None

    if 'eSf' in self.PD.used_xs:
      self.E = numpy.zeros(self.DD.local_ndof0)
    else:
      self.E = None

    self.up_to_date = {"flux" : False, "cell_powers" : False}

    self.bnd_matrix_form = None

    self.parameters = parameters["flux_module"]

    if self.eigenproblem:
      assert self.parameters.has_parameter_set("eigensolver")
      self.eigen_params = self.parameters["eigensolver"]
      self.adaptive_eig_tol_end = 0

      self.B = PETScMatrix()
      self.keff = 1
      self.prev_keff = self.keff
      self.set_initial_approximation(numpy.random.random(self.local_sln_size))
      self.update_phi()

    self.u = TrialFunction(self.DD.V)
    self.v = TestFunction(self.DD.V)

    self.v0 = TestFunction(self.DD.V0)
    self.phig = Function(self.DD.Vphi1) # single group scalar flux
    self.cell_RR_form = self.R * self.phig * self.v0 * dx
    self._cell_RRg_vector = PETScVector()

    self.vis_folder = os.path.join(self.PD.out_folder, "FLUX")

    self.vis_files = dict()
    var = "cell_powers"
    self.vis_files[var] = File(os.path.join(self.vis_folder, var+".pvd"), "compressed")
    var = "flux"
    self.vis_files[var] = [
      File(os.path.join(self.vis_folder, "{}_g{}.pvd".format(var, g)), "compressed") for g in range(self.DD.G)
    ]

    variables = self.parameters["saving"].iterkeys()
    self.save_folder = { k : os.path.join(self.PD.out_folder, k.upper()) for k in variables }
Ejemplo n.º 9
0
class BlendedAlgebraicVofModel(VOFMixin, MultiPhaseModel):
    description = 'A blended algebraic VOF scheme implementing HRIC/CICSAM type schemes'

    def __init__(self, simulation):
        """
        A blended algebraic VOF scheme works by using a specific
        convection scheme in the advection of the colour function
        that ensures a sharp interface.

        * The convection scheme should be the name of a convection
          scheme that is tailored for advection of the colour
          function, i.e "HRIC", "MHRIC", "RHRIC" etc,
        * The velocity field should be divergence free

        The colour function is unity when rho=rho0 and nu=nu0 and
        zero when rho=rho1 and nu=nu1
        """
        self.simulation = simulation
        simulation.log.info('Creating blended VOF multiphase model')

        # Define function space and solution function
        V = simulation.data['Vc']
        self.degree = V.ufl_element().degree()
        simulation.data['c'] = Function(V)
        simulation.data['cp'] = Function(V)
        simulation.data['cpp'] = Function(V)

        # The projected density and viscosity functions for the new time step can be made continuous
        self.continuous_fields = simulation.input.get_value(
            'multiphase_solver/continuous_fields', CONTINUOUS_FIELDS, 'bool')
        if self.continuous_fields:
            simulation.log.info('    Using continuous rho and nu fields')
            mesh = simulation.data['mesh']
            V_cont = dolfin.FunctionSpace(mesh, 'CG', self.degree + 1)
            self.continuous_c = dolfin.Function(V_cont)
            self.continuous_c_old = dolfin.Function(V_cont)
            self.continuous_c_oldold = dolfin.Function(V_cont)

        self.force_bounded = simulation.input.get_value(
            'multiphase_solver/force_bounded', FORCE_BOUNDED, 'bool')
        self.force_sharp = simulation.input.get_value(
            'multiphase_solver/force_sharp', FORCE_SHARP, 'bool')

        # Calculate mu from rho and nu (i.e mu is quadratic in c) or directly from c (linear in c)
        self.calculate_mu_directly_from_colour_function = simulation.input.get_value(
            'multiphase_solver/calculate_mu_directly_from_colour_function',
            CALCULATE_MU_DIRECTLY_FROM_COLOUR_FUNCTION,
            'bool',
        )

        # Get the physical properties
        self.set_physical_properties(read_input=True)

        # The convection blending function that counteracts numerical diffusion
        scheme = simulation.input.get_value('convection/c/convection_scheme',
                                            CONVECTION_SCHEME, 'string')
        simulation.log.info(
            '    Using convection scheme %s for the colour function' % scheme)
        scheme_class = get_convection_scheme(scheme)
        self.convection_scheme = scheme_class(simulation, 'c')
        self.need_gradient = scheme_class.need_alpha_gradient

        # Create the equations when the simulation starts
        simulation.hooks.add_pre_simulation_hook(
            self.on_simulation_start,
            'BlendedAlgebraicVofModel setup equations')

        # Update the rho and nu fields before each time step
        simulation.hooks.add_pre_timestep_hook(
            self.update, 'BlendedAlgebraicVofModel - update colour field')
        simulation.hooks.register_custom_hook_point('MultiPhaseModelUpdated')

        # Linear solver
        # This causes the MPI unit tests to fail in "random" places for some reason
        # Quick fix: lazy loading of the solver
        LAZY_LOAD_SOLVER = True
        if LAZY_LOAD_SOLVER:
            self.solver = None
        else:
            self.solver = linear_solver_from_input(
                self.simulation, 'solver/c', default_parameters=SOLVER_OPTIONS)

        # Subcycle the VOF calculation multiple times per Navier-Stokes time step
        self.num_subcycles = scheme = simulation.input.get_value(
            'multiphase_solver/num_subcycles', NUM_SUBCYCLES, 'int')
        if self.num_subcycles < 1:
            self.num_subcycles = 1

        # Time stepping based on the subcycled values
        if self.num_subcycles == 1:
            self.cp = simulation.data['cp']
            self.cpp = simulation.data['cpp']
        else:
            self.cp = dolfin.Function(V)
            self.cpp = dolfin.Function(V)

        # Plot density and viscosity fields for visualization
        self.plot_fields = simulation.input.get_value(
            'multiphase_solver/plot_fields', PLOT_FIELDS, 'bool')
        if self.plot_fields:
            V_plot = V if not self.continuous_fields else V_cont
            self.rho_for_plot = Function(V_plot)
            self.nu_for_plot = Function(V_plot)
            self.rho_for_plot.rename('rho', 'Density')
            self.nu_for_plot.rename('nu', 'Kinematic viscosity')
            simulation.io.add_extra_output_function(self.rho_for_plot)
            simulation.io.add_extra_output_function(self.nu_for_plot)

        # Slope limiter in case we are using DG1, not DG0
        self.slope_limiter = SlopeLimiter(simulation, 'c',
                                          simulation.data['c'])
        simulation.log.info('    Using slope limiter: %s' %
                            self.slope_limiter.limiter_method)
        self.is_first_timestep = True

    def on_simulation_start(self):
        """
        This runs when the simulation starts. It does not run in __init__
        since the solver needs the density and viscosity we define, and
        we need the velocity that is defined by the solver
        """
        sim = self.simulation
        beta = self.convection_scheme.blending_function

        # The time step (real value to be supplied later)
        self.dt = Constant(sim.dt / self.num_subcycles)

        # Setup the equation to solve
        c = sim.data['c']
        cp = self.cp
        cpp = self.cpp
        dirichlet_bcs = sim.data['dirichlet_bcs'].get('c', [])

        # Use backward Euler (BDF1) for timestep 1
        self.time_coeffs = Constant([1, -1, 0])

        if dolfin.norm(cpp.vector()) > 0 and self.num_subcycles == 1:
            # Use BDF2 from the start
            self.time_coeffs.assign(Constant([3 / 2, -2, 1 / 2]))
            sim.log.info(
                'Using second order timestepping from the start in BlendedAlgebraicVOF'
            )

        # Make sure the convection scheme has something useful in the first iteration
        c.assign(sim.data['cp'])

        if self.num_subcycles > 1:
            cp.assign(sim.data['cp'])

        # Plot density and viscosity
        self.update_plot_fields()

        # Define equation for advection of the colour function
        #    ∂c/∂t +  ∇⋅(c u) = 0
        Vc = sim.data['Vc']
        project_dgt0 = sim.input.get_value(
            'multiphase_solver/project_uconv_dgt0', True, 'bool')
        if self.degree == 0 and project_dgt0:
            self.vel_dgt0_projector = VelocityDGT0Projector(
                sim, sim.data['u_conv'])
            self.u_conv = self.vel_dgt0_projector.velocity
        else:
            self.u_conv = sim.data['u_conv']
        forcing_zones = sim.data['forcing_zones'].get('c', [])
        self.eq = AdvectionEquation(
            sim,
            Vc,
            cp,
            cpp,
            self.u_conv,
            beta,
            time_coeffs=self.time_coeffs,
            dirichlet_bcs=dirichlet_bcs,
            forcing_zones=forcing_zones,
            dt=self.dt,
        )

        if self.need_gradient:
            # Reconstruct the gradient from the colour function DG0 field
            self.convection_scheme.initialize_gradient()

        # Notify listeners that the initial values are available
        sim.hooks.run_custom_hook('MultiPhaseModelUpdated')

    def get_colour_function(self, k):
        """
        Return the colour function on timestep t^{n+k}
        """
        if k == 0:
            if self.continuous_fields:
                c = self.continuous_c
            else:
                c = self.simulation.data['c']
        elif k == -1:
            if self.continuous_fields:
                c = self.continuous_c_old
            else:
                c = self.simulation.data['cp']
        elif k == -2:
            if self.continuous_fields:
                c = self.continuous_c_oldold
            else:
                c = self.simulation.data['cpp']

        if self.force_bounded:
            c = dolfin.max_value(dolfin.min_value(c, Constant(1.0)),
                                 Constant(0.0))

        if self.force_sharp:
            c = dolfin.conditional(dolfin.ge(c, 0.5), Constant(1.0),
                                   Constant(0.0))

        return c

    def update_plot_fields(self):
        """
        These fields are only needed to visualise the rho and nu fields
        in xdmf format for Paraview or similar
        """
        if not self.plot_fields:
            return
        V = self.rho_for_plot.function_space()
        dolfin.project(self.get_density(0), V, function=self.rho_for_plot)
        dolfin.project(self.get_laminar_kinematic_viscosity(0),
                       V,
                       function=self.nu_for_plot)

    def update(self, timestep_number, t, dt):
        """
        Update the VOF field by advecting it for a time dt
        using the given divergence free velocity field
        """
        timer = dolfin.Timer('Ocellaris update VOF')
        sim = self.simulation

        # Get the functions
        c = sim.data['c']
        cp = sim.data['cp']
        cpp = sim.data['cpp']

        # Stop early if the free surface is forced to stay still
        force_static = sim.input.get_value('multiphase_solver/force_static',
                                           FORCE_STATIC, 'bool')
        if force_static:
            c.assign(cp)
            cpp.assign(cp)
            timer.stop()  # Stop timer before hook
            sim.hooks.run_custom_hook('MultiPhaseModelUpdated')
            self.is_first_timestep = False
            return

        if timestep_number != 1:
            # Update the previous values
            cpp.assign(cp)
            cp.assign(c)

            if self.degree == 0:
                self.vel_dgt0_projector.update()

        # Reconstruct the gradients
        if self.need_gradient:
            self.convection_scheme.gradient_reconstructor.reconstruct()

        # Update the convection blending factors
        is_static = isinstance(self.convection_scheme, StaticScheme)
        if not is_static:
            self.convection_scheme.update(dt / self.num_subcycles, self.u_conv)

        # Update global bounds in slope limiter
        if self.is_first_timestep:
            lo, hi = self.slope_limiter.set_global_bounds(lo=0.0, hi=1.0)
            if self.slope_limiter.has_global_bounds:
                sim.log.info(
                    'Setting global bounds [%r, %r] in BlendedAlgebraicVofModel'
                    % (lo, hi))

        # Solve the advection equations for the colour field
        if timestep_number == 1 or is_static:
            c.assign(cp)
        else:
            if self.solver is None:
                sim.log.info('Creating colour function solver', flush=True)
                self.solver = linear_solver_from_input(
                    self.simulation,
                    'solver/c',
                    default_parameters=SOLVER_OPTIONS)

            # Solve the advection equation
            A = self.eq.assemble_lhs()
            for _ in range(self.num_subcycles):
                b = self.eq.assemble_rhs()
                self.solver.inner_solve(A, c.vector(), b, 1, 0)
                self.slope_limiter.run()
                if self.num_subcycles > 1:
                    self.cpp.assign(self.cp)
                    self.cp.assign(c)

        # Optionally use a continuous predicted colour field
        if self.continuous_fields:
            Vcg = self.continuous_c.function_space()
            dolfin.project(c, Vcg, function=self.continuous_c)
            dolfin.project(cp, Vcg, function=self.continuous_c_old)
            dolfin.project(cpp, Vcg, function=self.continuous_c_oldold)

        # Report properties of the colour field
        sim.reporting.report_timestep_value('min(c)', c.vector().min())
        sim.reporting.report_timestep_value('max(c)', c.vector().max())

        # The next update should use the dt from this time step of the
        # main Navier-Stoke solver. The update just computed above uses
        # data from the previous Navier-Stokes solve with the previous dt
        self.dt.assign(dt / self.num_subcycles)

        if dt != sim.dt_prev:
            # Temporary switch to first order timestepping for the next
            # time step. This code is run before the Navier-Stokes solver
            # in each time step
            sim.log.info(
                'VOF solver is first order this time step due to change in dt')
            self.time_coeffs.assign(Constant([1.0, -1.0, 0.0]))
        else:
            # Use second order backward time difference next time step
            self.time_coeffs.assign(Constant([3 / 2, -2.0, 1 / 2]))

        self.update_plot_fields()
        timer.stop()  # Stop timer before hook
        sim.hooks.run_custom_hook('MultiPhaseModelUpdated')
        self.is_first_timestep = False
Ejemplo n.º 10
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
Ejemplo n.º 11
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