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)
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 })
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 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)
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))
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)
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)
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)
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)
def union_mask(geometries): warnings.warn("union_mask() is deprecated, use mask(union()) instead.", DeprecationWarning) return mask(geom.union(*geometries))
def geometries(self, geometry): """ Alias for `geometry`. """ if isinstance(geometry, (tuple, list)): geometry = geom.union(geometry) assert isinstance(geometry, geom.Geometry) return geometry
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
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