Esempio n. 1
0
 def laplace(p):
     grad = spatial_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
Esempio n. 2
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
Esempio n. 3
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
Esempio n. 4
0
    def step_gradient_2d(params, plasma, phi, dt=0):
        """time spatial_gradient of model"""
        # Diffusion function
        def diffuse(arr, N, dx):
            for i in range(N):
                arr = field.laplace(arr)  # math.fourier_laplace(arr, dx)
            return arr

        # Calculate Gradients
        dx_p, dy_p = field.spatial_gradient(phi).vector.unstack()
        # Get difference
        diff = phi - plasma.density
        # Step 2.1: New Omega.
        nu = (-1) ** (params["N"] + 1) * params["nu"]
        o = params["c1"] * diff
        if params["arakawa_coeff"]:
            o += -params[
                "arakawa_coeff"
            ] * math._nd._periodic_2d_arakawa_poisson_bracket(
                phi.values, plasma.omega.values, plasma.dx
            )
        if nu and params["N"]:
            o += nu * diffuse(plasma.omega, params["N"], plasma.dx)
        # Step 2.2: New Density.
        n = params["c1"] * diff
        if params["arakawa_coeff"]:
            n += -params[
                "arakawa_coeff"
            ] * math._nd._periodic_2d_arakawa_poisson_bracket(
                phi.values, plasma.density.values, plasma.dx
            )
        if params["kappa_coeff"]:
            n += -params["kappa_coeff"] * dy_p
        if nu:
            n += nu * diffuse(plasma.density, params["N"], plasma.dx)
        return Namespace(
            density=n,
            omega=o,
            phi=phi,  # NOTE: NOT A GRADIENT
            age=plasma.age + dt,
            dx=plasma.dx,
        )
Esempio n. 5
0
 def matrix_eq(p):
     return field.where(occupied_centered, field.divergence(field.spatial_gradient(p, type=StaggeredGrid) * accessible), p)
Esempio n. 6
0
 def test_spatial_gradient_batched(self):
     bounds = geom.stack([Box[0:1, 0:1], Box[0:10, 0:10]], batch('batch'))
     grid = CenteredGrid(0, extrapolation.ZERO, bounds, x=10, y=10)
     grad = field.spatial_gradient(grid)
     self.assertIsInstance(grad, CenteredGrid)
Esempio n. 7
0
 def test_spatial_gradient(self):
     s = CenteredGrid(1, x=4, y=3) * (1, 2)
     grad = field.spatial_gradient(s, stack_dim=channel('spatial_gradient'))
     self.assertEqual(('spatial', 'spatial', 'channel', 'channel'),
                      grad.shape.types)
Esempio n. 8
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
Esempio n. 9
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
Esempio n. 10
0
 def test_gradient(self):
     domain = Domain(x=4, y=3)
     phi = domain.grid() * (1, 2)
     grad = field.spatial_gradient(phi, stack_dim='spatial_gradient')
     self.assertEqual(('spatial', 'spatial', 'channel', 'channel'), grad.shape.types)
Esempio n. 11
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 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
      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 = spatial_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.spatial_gradient(pressure, type=type(velocity)) * hard_bcs
    velocity = (velocity -
                gradp).with_(extrapolation=input_velocity.extrapolation)
    return velocity, pressure, iterations, div