def map_velocity_to_particles( previous_particle_velocity: PointCloud, velocity_grid: Grid, occupation_mask: Grid, previous_velocity_grid: Grid = None) -> PointCloud: """ Maps result of velocity projection on grid back to particles. Provides option to choose between FLIP (particle velocities are updated by the change between projected and initial grid velocities) and PIC (particle velocities are replaced by the the projected velocities) method depending on the value of the `initial_v_field`. Args: previous_particle_velocity: PointCloud with particle positions as elements and their corresponding velocities as values velocity_grid: Divergence-free velocity grid occupation_mask: Binary Grid (same type as `velocity_grid`) indicating which cells hold particles previous_velocity_grid: Velocity field before projection and force update. If None, the PIC method gets applied, FLIP otherwise Returns: PointCloud with particle positions as elements and updated particle velocities as values. """ if previous_velocity_grid is not None: # --- FLIP --- v_change_field = velocity_grid - previous_velocity_grid v_change_field, _ = extrapolate_valid(v_change_field, occupation_mask, 1) v_change = v_change_field.sample_at( previous_particle_velocity.elements.center) return previous_particle_velocity.with_( values=previous_particle_velocity.values + v_change) else: # --- PIC --- v_div_free_field, _ = extrapolate_valid(velocity_grid, occupation_mask, 1) v_values = v_div_free_field.sample_at( previous_particle_velocity.elements.center) return previous_particle_velocity.with_(values=v_values)
def respect_boundaries(particles: PointCloud, domain: Domain, not_accessible: list, offset: float = 0.5) -> PointCloud: """ Enforces boundary conditions by correcting possible errors of the advection step and shifting particles out of obstacles or back into the domain. Args: particles: PointCloud holding particle positions as elements domain: Domain for which any particles outside should get shifted inwards not_accessible: List of Obstacle or Geometry objects where any particles inside should get shifted outwards offset: Minimum distance between particles and domain boundary / obstacle surface after particles have been shifted. Returns: PointCloud where all particles are inside the domain / outside of obstacles. """ new_positions = particles.elements.center for obj in not_accessible: if isinstance(obj, Obstacle): obj = obj.geometry new_positions = obj.push(new_positions, shift_amount=offset) new_positions = (~domain.bounds).push(new_positions, shift_amount=offset) return particles.with_( elements=Sphere(new_positions, math.mean(particles.bounds.size) * 0.005))
def points(field: PointCloud, velocity: PointCloud, dt): """ Advects the sample points of a point cloud using a simple Euler step. Each point moves by an amount equal to the local velocity times `dt`. Args: field: point cloud to be advected velocity: velocity sampled at the same points as the point cloud dt: Euler step time increment field: PointCloud: velocity: PointCloud: Returns: advected point cloud """ assert field.elements == velocity.elements new_points = field.elements.shifted(dt * velocity.values) return field.with_(elements=new_points)