Example #1
0
    def accessible_mask(
            self,
            not_accessible: tuple or list,
            type: type = CenteredGrid) -> CenteredGrid or StaggeredGrid:
        """
        Unifies domain and Obstacle or Geometry objects into a binary StaggeredGrid mask which can be used
        to enforce boundary conditions.

        Args:
            not_accessible: blocked region(s) of space specified by geometries

        Returns:
            Binary mask indicating valid fields w.r.t. the boundary conditions.
            The result is of type `type` and uses the extrapolation `Domain.boundaries['accessible_extrapolation']`.
        """
        accessible_mask = self.scalar_grid(
            HardGeometryMask(~union(not_accessible)),
            extrapolation=self.boundaries['accessible_extrapolation'])
        if type is CenteredGrid:
            return accessible_mask
        elif type is StaggeredGrid:
            return field.stagger(accessible_mask, math.minimum,
                                 self.boundaries['accessible_extrapolation'])
        else:
            raise ValueError('Unknown grid type: %s' % type)
Example #2
0
def divergence_free(velocity,
                    domain=None,
                    obstacles=(),
                    pressure_solver=None,
                    return_info=False,
                    gradient='implicit'):
    """
Projects the given velocity field by solving for and subtracting the pressure.
    :param return_info: if True, returns a dict holding information about the solve as a second object
    :param velocity: StaggeredGrid
    :param domain: Domain matching the velocity field, used for boundary conditions
    :param obstacles: list of Obstacles
    :param pressure_solver: PressureSolver. Uses default solver if none provided.
    :return: divergence-free velocity as StaggeredGrid
    """
    assert isinstance(velocity, StaggeredGrid)
    # --- Set up FluidDomain ---
    if domain is None:
        domain = Domain(velocity.resolution, OPEN)
    obstacle_mask = mask(union([obstacle.geometry for obstacle in obstacles]),
                         antialias=False)
    if obstacle_mask is not None:
        obstacle_grid = obstacle_mask.at(
            velocity.center_points).copied_with(extrapolation='constant')
        active_mask = 1 - obstacle_grid
    else:
        active_mask = math.ones(
            domain.centered_shape(name='active', extrapolation='constant'))
    accessible_mask = active_mask.copied_with(
        extrapolation=Material.accessible_extrapolation_mode(
            domain.boundaries))
    fluiddomain = FluidDomain(domain,
                              active=active_mask,
                              accessible=accessible_mask)
    # --- Boundary Conditions, Pressure Solve ---
    velocity = fluiddomain.with_hard_boundary_conditions(velocity)
    for obstacle in obstacles:
        if not obstacle.is_stationary:
            obs_mask = mask(obstacle.geometry, antialias=True)
            angular_velocity = AngularVelocity(
                location=obstacle.geometry.center,
                strength=obstacle.angular_velocity,
                falloff=None)
            velocity = ((1 - obs_mask) * velocity + obs_mask *
                        (angular_velocity + obstacle.velocity)).at(velocity)
    divergence_field = velocity.divergence(physical_units=False)
    pressure, iterations = poisson_solve(divergence_field,
                                         fluiddomain,
                                         solver=pressure_solver,
                                         gradient=gradient)
    pressure *= velocity.dx[0]
    gradp = StaggeredGrid.gradient(pressure)
    velocity -= fluiddomain.with_hard_boundary_conditions(gradp)
    return velocity if not return_info else (velocity, {
        'pressure': pressure,
        'iterations': iterations,
        'divergence': divergence_field
    })
Example #3
0
def make_incompressible(velocity: StaggeredGrid,
                        domain: Domain,
                        particles: PointCloud,
                        obstacles: tuple or list or StaggeredGrid = (),
                        solve=math.Solve('auto', 1e-5, 0, gradient_solve=math.Solve('auto', 1e-5, 1e-5))):
    """
    Projects the given velocity field by solving for the pressure and subtracting its spatial_gradient.

    Args:
        velocity: Current velocity field as StaggeredGrid
        domain: Domain object
        particles: `PointCloud` holding the current positions of the particles
        obstacles: Sequence of `phi.physics.Obstacle` objects or binary StaggeredGrid marking through-flow cell faces
        solve: Parameters for the pressure solve_linear

    Returns:
      velocity: divergence-free velocity of type `type(velocity)`
      pressure: solved pressure field, `CenteredGrid`
      iterations: Number of iterations required to solve_linear for the pressure
      divergence: divergence field of input velocity, `CenteredGrid`
      occupation_mask: StaggeredGrid
    """
    points = particles.with_values(math.tensor(1., convert=True))
    occupied_centered = points @ domain.scalar_grid()
    occupied_staggered = points @ domain.staggered_grid()

    if isinstance(obstacles, StaggeredGrid):
        accessible = obstacles
    else:
        accessible = domain.accessible_mask(union(*[obstacle.geometry for obstacle in obstacles]), type=StaggeredGrid)

    # --- Extrapolation is needed to exclude border divergence from the `occupied_centered` mask and thus
    # from the pressure solve_linear. If particles are randomly distributed, the `occupied_centered` mask
    # could sometimes include the divergence at the borders (due to single particles right at the edge
    # which temporarily deform the `occupied_centered` mask when moving into a new cell). This would then
    # get compensated by the pressure. This is unwanted for falling liquids and therefore prevented by this
    # extrapolation. ---
    velocity_field, _ = extrapolate_valid(velocity * occupied_staggered, occupied_staggered, 1)
    velocity_field *= accessible  # Enforces boundary conditions after extrapolation
    div = field.divergence(velocity_field) * occupied_centered  # Multiplication with `occupied_centered` excludes border divergence from pressure solve_linear

    @field.jit_compile_linear
    def matrix_eq(p):
        return field.where(occupied_centered, field.divergence(field.spatial_gradient(p, type=StaggeredGrid) * accessible), p)

    if solve.x0 is None:
        solve = copy_with(solve, x0=domain.scalar_grid())
    pressure = field.solve_linear(matrix_eq, div, solve)

    def pressure_backward(_p, _p_, dp):
        return dp * occupied_centered.values,

    add_mask_in_gradient = math.custom_gradient(lambda p: p, pressure_backward)
    pressure = pressure.with_values(add_mask_in_gradient(pressure.values))

    gradp = field.spatial_gradient(pressure, type=type(velocity_field)) * accessible
    return velocity_field - gradp, pressure, occupied_staggered
Example #4
0
def obstacle_mask(world_or_proxy, **mask_kwargs):
    """
Builds a binary Field, masking all obstacles in the world.
    :param world_or_proxy: World or StateProxy object
    :return: Field
    """
    world = world_or_proxy.world if isinstance(world_or_proxy, StateProxy) else world_or_proxy
    assert isinstance(world, World)
    geometries = [obstacle.geometry for obstacle in world.state.all_with_tag('obstacle')]
    return mask(union(geometries), **mask_kwargs)
Example #5
0
def obstacle_mask(world_or_proxy):
    """
    Builds a binary Field, masking all obstacles in the world.

    Args:
      world_or_proxy: World or StateProxy object

    Returns:
      Field

    """
    world = world_or_proxy.world if isinstance(world_or_proxy,
                                               StateProxy) else world_or_proxy
    assert isinstance(world, World)
    geometries = [
        obstacle.geometry for obstacle in world.state.all_with_tag('obstacle')
    ]
    return GeometryMask(geom.union(*geometries))
Example #6
0
    def distribute_points(self,
                          geometries: tuple or list,
                          points_per_cell: int = 8,
                          color: str = None,
                          center: bool = False) -> PointCloud:
        """
        Transforms `Geometry` objects into a PointCloud.

        Args:
            geometries: Geometry objects marking the cells which should contain points
            points_per_cell: Number of points for each cell of `geometries`
            color (Optional): Color of PointCloud
            center: Set all points to the center of the grid cells.

        Returns:
             PointCloud representation of `geometries`.
        """
        geometries = HardGeometryMask(union(geometries)) @ self.grid()
        initial_points = _distribute_points(geometries.values, points_per_cell, center=center)
        return self.points(initial_points, color=color)
Example #7
0
    def accessible_mask(self, not_accessible: tuple or list, type: type = CenteredGrid, extrapolation='accessible') -> CenteredGrid or StaggeredGrid:
        """
        Unifies domain and Obstacle or Geometry objects into a binary StaggeredGrid mask which can be used
        to enforce boundary conditions.

        Args:
            not_accessible: blocked region(s) of space specified by geometries
            type: class of Grid to create, must be either CenteredGrid or StaggeredGrid
            extrapolation: (optional) grid extrapolation, defaults to Domain.boundaries['accessible']

        Returns:
            Binary mask indicating valid fields w.r.t. the boundary conditions.
        """
        extrapolation = extrapolation if isinstance(extrapolation, math.Extrapolation) else self.boundaries[extrapolation]
        accessible_mask = self.scalar_grid(HardGeometryMask(~union(not_accessible)), extrapolation=extrapolation)
        if type is CenteredGrid:
            return accessible_mask
        elif type is StaggeredGrid:
            return field.stagger(accessible_mask, math.minimum, extrapolation)
        else:
            raise ValueError('Unknown grid type: %s' % type)
Example #8
0
 def test_union_varying(self):
     box = Box[0:1, 0:1]
     sphere = Sphere((0, 0), radius=1)
     union = geom.union(box, sphere)
     math.assert_close(union.approximate_signed_distance((1, 1)),
                       union.approximate_signed_distance((0, -1)), 0)
Example #9
0
 def test_union_same(self):
     union = geom.union(Box[0:1, 0:1], Box[2:3, 0:1])
     self.assertIsInstance(union, Box)
     math.assert_close(union.approximate_signed_distance((0, 0)),
                       union.approximate_signed_distance((3, 1)), 0)
     math.assert_close(union.approximate_signed_distance((1.5, 0)), 0.5)
Example #10
0
def union_mask(geometries):
    warnings.warn("union_mask() is deprecated, use mask(union()) instead.", DeprecationWarning)
    return mask(geom.union(*geometries))
Example #11
0
 def geometries(self, geometry):
     """ Alias for `geometry`. """
     if isinstance(geometry, (tuple, list)):
         geometry = geom.union(geometry)
     assert isinstance(geometry, geom.Geometry)
     return geometry
Example #12
0
def make_incompressible(velocity: Grid,
                        domain: Domain,
                        obstacles: tuple or list = (),
                        solve_params: math.LinearSolve = math.LinearSolve(
                            None, 1e-3),
                        pressure_guess: CenteredGrid = None):
    """
    Projects the given velocity field by solving for the pressure and subtracting its gradient.
    
    This method is similar to :func:`field.divergence_free()` but differs in how the boundary conditions are specified.

    Args:
      velocity: Vector field sampled on a grid
      domain: Used to specify boundary conditions
      obstacles: List of Obstacles to specify boundary conditions inside the domain (Default value = ())
      pressure_guess: Initial guess for the pressure solve
      solve_params: Parameters for the pressure solve

    Returns:
      velocity: divergence-free velocity of type `type(velocity)`
      pressure: solved pressure field, `CenteredGrid`
      iterations: Number of iterations required to solve for the pressure
      divergence: divergence field of input velocity, `CenteredGrid`

    """
    input_velocity = velocity
    active = domain.grid(
        HardGeometryMask(~union(*[obstacle.geometry
                                  for obstacle in obstacles])),
        extrapolation=domain.boundaries['active_extrapolation'])
    accessible = domain.grid(
        active, extrapolation=domain.boundaries['accessible_extrapolation'])
    hard_bcs = field.stagger(accessible,
                             math.minimum,
                             domain.boundaries['accessible_extrapolation'],
                             type=type(velocity))
    velocity = layer_obstacle_velocities(velocity * hard_bcs, obstacles).with_(
        extrapolation=domain.boundaries['near_vector_extrapolation'])
    div = divergence(velocity)
    if domain.boundaries[
            'near_vector_extrapolation'] == math.extrapolation.BOUNDARY:
        div -= field.mean(div)

    # Solve pressure

    def laplace(p):
        grad = gradient(p, type(velocity))
        grad *= hard_bcs
        grad = grad.with_(
            extrapolation=domain.boundaries['near_vector_extrapolation'])
        div = divergence(grad)
        lap = where(active, div, p)
        return lap

    pressure_guess = pressure_guess if pressure_guess is not None else domain.scalar_grid(
        0)
    converged, pressure, iterations = field.solve(laplace,
                                                  y=div,
                                                  x0=pressure_guess,
                                                  solve_params=solve_params,
                                                  constants=[active, hard_bcs])
    if math.all_available(converged) and not math.all(converged):
        raise AssertionError(
            f"pressure solve did not converge after {iterations} iterations\nResult: {pressure.values}"
        )
    # Subtract grad pressure
    gradp = field.gradient(pressure, type=type(velocity)) * hard_bcs
    velocity = (velocity -
                gradp).with_(extrapolation=input_velocity.extrapolation)
    return velocity, pressure, iterations, div
Example #13
0
def make_incompressible(
    velocity: GridType,
    obstacles: tuple or list = (),
    solve=math.Solve('auto',
                     1e-5,
                     1e-5,
                     gradient_solve=math.Solve('auto', 1e-5, 1e-5))
) -> Tuple[GridType, CenteredGrid]:
    """
    Projects the given velocity field by solving for the pressure and subtracting its spatial_gradient.
    
    This method is similar to :func:`field.divergence_free()` but differs in how the boundary conditions are specified.

    Args:
        velocity: Vector field sampled on a grid
        obstacles: List of Obstacles to specify boundary conditions inside the domain (Default value = ())
        solve: Parameters for the pressure solve as.

    Returns:
        velocity: divergence-free velocity of type `type(velocity)`
        pressure: solved pressure field, `CenteredGrid`
    """
    assert isinstance(
        obstacles,
        (tuple,
         list)), f"obstacles must be a tuple or list but got {type(obstacles)}"
    input_velocity = velocity
    accessible_extrapolation = _accessible_extrapolation(
        input_velocity.extrapolation)
    active = CenteredGrid(
        HardGeometryMask(~union(*[obstacle.geometry
                                  for obstacle in obstacles])),
        resolution=velocity.resolution,
        bounds=velocity.bounds,
        extrapolation=extrapolation.NONE)
    accessible = active.with_extrapolation(accessible_extrapolation)
    hard_bcs = field.stagger(accessible,
                             math.minimum,
                             input_velocity.extrapolation,
                             type=type(velocity))
    velocity = apply_boundary_conditions(velocity, obstacles)
    div = divergence(velocity) * active
    if not input_velocity.extrapolation.connects_to_outside:
        assert solve.preprocess_y is None, "fluid.make_incompressible() does not support custom preprocessing"
        solve = copy_with(solve,
                          preprocess_y=_balance_divergence,
                          preprocess_y_args=(active, ))
    if solve.x0 is None:
        pressure_extrapolation = _pressure_extrapolation(
            input_velocity.extrapolation)
        solve = copy_with(solve,
                          x0=CenteredGrid(
                              0,
                              resolution=div.resolution,
                              bounds=div.bounds,
                              extrapolation=pressure_extrapolation))
    pressure = math.solve_linear(masked_laplace,
                                 f_args=[hard_bcs, active],
                                 y=div,
                                 solve=solve)
    grad_pressure = field.spatial_gradient(
        pressure, input_velocity.extrapolation, type=type(velocity)) * hard_bcs
    velocity = velocity - grad_pressure
    return velocity, pressure
Example #14
0
def make_incompressible(
    velocity: StaggeredGrid,
    domain: Domain,
    obstacles: tuple or list or StaggeredGrid = (),
    particles: PointCloud or None = None,
    solve_params: math.LinearSolve = math.LinearSolve(),
    pressure_guess: CenteredGrid = None
) -> Tuple[StaggeredGrid, CenteredGrid, math.Tensor, CenteredGrid,
           StaggeredGrid]:
    """
    Projects the given velocity field by solving for the pressure and subtracting its spatial_gradient.

    Args:
        velocity: Current velocity field as StaggeredGrid
        obstacles: Sequence of `phi.physics.Obstacle` objects or binary StaggeredGrid marking through-flow cell faces
        particles (Optional if occupation masks are provided): Pointcloud holding the current positions of the particles
        domain (Optional if occupation masks are provided): Domain object
        pressure_guess (Optional): Initial pressure guess as CenteredGrid
        solve_params: Parameters for the pressure solve

    Returns:
      velocity: divergence-free velocity of type `type(velocity)`
      pressure: solved pressure field, `CenteredGrid`
      iterations: Number of iterations required to solve for the pressure
      divergence: divergence field of input velocity, `CenteredGrid`
      occupation_mask: StaggeredGrid
    """
    points = particles.with_(values=math.wrap(1))
    occupied_centered = points >> domain.grid()
    occupied_staggered = points >> domain.staggered_grid()

    if isinstance(obstacles, StaggeredGrid):
        accessible = obstacles
    else:
        accessible = domain.accessible_mask(
            union(*[obstacle.geometry for obstacle in obstacles]),
            type=StaggeredGrid)

    # --- Extrapolation is needed to exclude border divergence from the `occupied_centered` mask and thus
    # from the pressure solve. If particles are randomly distributed, the `occupied_centered` mask
    # could sometimes include the divergence at the borders (due to single particles right at the edge
    # which temporarily deform the `occupied_centered` mask when moving into a new cell) which would then
    # get compensated by the pressure. This is unwanted for falling liquids and therefore prevented by this
    # extrapolation. ---
    velocity_field, _ = extrapolate_valid(velocity * occupied_staggered,
                                          occupied_staggered, 1)
    velocity_field *= accessible  # Enforces boundary conditions after extrapolation
    div = field.divergence(
        velocity_field
    ) * occupied_centered  # Multiplication with `occupied_centered` excludes border divergence from pressure solve

    def matrix_eq(p):
        return field.where(
            occupied_centered,
            field.divergence(
                field.spatial_gradient(p, type=StaggeredGrid) * accessible), p)

    converged, pressure, iterations = field.solve(matrix_eq,
                                                  div,
                                                  pressure_guess
                                                  or domain.grid(),
                                                  solve_params=solve_params)
    gradp = field.spatial_gradient(pressure,
                                   type=type(velocity_field)) * accessible
    return velocity_field - gradp, pressure, iterations, div, occupied_staggered