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
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
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
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, )
def matrix_eq(p): return field.where(occupied_centered, field.divergence(field.spatial_gradient(p, type=StaggeredGrid) * accessible), p)
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)
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)
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
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
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)
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