Exemple #1
0
 def _test_make_incompressible(self, grid_type: type,
                               extrapolation: math.Extrapolation,
                               **batch_dims):
     result = None
     for i, backend in enumerate(BACKENDS):
         with backend:
             smoke = CenteredGrid(Sphere(
                 center=(math.random_uniform(batch(**batch_dims)) * 100,
                         10),
                 radius=5),
                                  extrapolation,
                                  x=16,
                                  y=20,
                                  bounds=Box[0:100, 0:100])
             velocity = grid_type(0,
                                  extrapolation,
                                  x=16,
                                  y=20,
                                  bounds=Box[0:100, 0:100])
             for _ in range(2):
                 velocity += smoke * (0, 0.1) @ velocity
                 velocity, _ = fluid.make_incompressible(velocity)
             math.assert_close(divergence(velocity).values,
                               0,
                               abs_tolerance=2e-5)
             if result is None:
                 result = velocity
             else:
                 field.assert_close(
                     result,
                     abs_tolerance=1e-5,
                     msg=
                     f"Simulation with {backend} does not match {BACKENDS[:i]}"
                 )
Exemple #2
0
 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
Exemple #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
Exemple #4
0
def masked_laplace(pressure: CenteredGrid, hard_bcs: Grid,
                   active: CenteredGrid):
    grad = spatial_gradient(pressure,
                            hard_bcs.extrapolation,
                            type=type(hard_bcs))
    grad *= hard_bcs
    div = divergence(grad)
    lap = where(active, div, pressure)
    return lap
Exemple #5
0
 def _test_make_incompressible_batched(self, grid_type):
     DOMAIN = Domain(x=16, y=16, boundaries=CLOSED, bounds=Box[0:100, 0:100])
     smoke = DOMAIN.scalar_grid(Sphere(center=(math.random_uniform(batch=2) * 100, 10), radius=5))
     velocity = DOMAIN.vector_grid(0, grid_type)
     for _ in range(2):
         velocity += smoke * (0, 0.1) >> velocity
         velocity, pressure, _, _ = fluid.make_incompressible(velocity, DOMAIN)
     math.assert_close(divergence(velocity).values, 0, abs_tolerance=2e-5)
     return velocity.values
Exemple #6
0
 def step(self,
          fluid,
          dt=1.0,
          obstacles=(),
          gravity=Gravity(),
          density_effects=(),
          velocity_effects=()):
     # pylint: disable-msg = arguments-differ
     gravity = gravity_tensor(gravity, fluid.rank)
     velocity = fluid.velocity
     density = fluid.density
     result: SolveInfo = None
     div = field.divergence(velocity)
     if self.make_input_divfree:
         velocity, result = make_incompressible(velocity, obstacles)
     # --- Advection ---
     density = advect.semi_lagrangian(density, velocity, dt=dt)
     velocity = advected_velocity = advect.semi_lagrangian(velocity,
                                                           velocity,
                                                           dt=dt)
     if self.conserve_density and fluid.domain.boundaries[
             'accessible'] == math.extrapolation.ZERO:  # solid boundary
         density = field.normalize(density, fluid.density)
     # --- Effects ---
     for effect in density_effects:
         density = effect_applied(effect, density, dt)
     for effect in velocity_effects:
         velocity = effect_applied(effect, velocity, dt)
     velocity += (density * -gravity * fluid.buoyancy_factor *
                  dt).at(velocity)
     divergent_velocity = velocity
     # --- Pressure solve_linear ---
     if self.make_output_divfree:
         velocity, result = make_incompressible(velocity, obstacles)
     solve_info = {
         'pressure': result.x,
         'iterations': result.iterations,
         'divergence': div,
         'advected_velocity': advected_velocity,
         'divergent_velocity': divergent_velocity,
     }
     return fluid.copied_with(density=density,
                              velocity=velocity,
                              age=fluid.age + dt,
                              solve_info=solve_info)
Exemple #7
0
 def matrix_eq(p):
     return field.where(occupied_centered, field.divergence(field.spatial_gradient(p, type=StaggeredGrid) * accessible), p)
Exemple #8
0
 def test_divergence_centered(self):
     v = CenteredGrid(1, extrapolation.ZERO, bounds=Box[0:1, 0:1], x=3,
                      y=3) * (1, 0)  # flow to the right
     div = field.divergence(v).values
     math.assert_close(div.y[0], (1.5, 0, -1.5))
Exemple #9
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
Exemple #10
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
Exemple #11
0
 def test_divergence_centered(self):
     v = field.CenteredGrid(math.ones(x=3, y=3), Box[0:1, 0:1],
                            math.extrapolation.ZERO) * (
                                1, 0)  # flow to the right
     div = field.divergence(v).values
     math.assert_close(div.y[0], (1.5, 0, -1.5))
Exemple #12
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