def approximate_fraction_inside(self, other_geometry: 'Geometry') -> Tensor: """ Computes the approximate overlap between the geometry and a small other geometry. Returns 1.0 if `other_geometry` is fully enclosed in this geometry and 0.0 if there is no overlap. Close to the surface of this geometry, the fraction filled is differentiable w.r.t. the location and size of `other_geometry`. To call this method on batches of geometries of same shape, pass a batched Geometry instance. The result tensor will match the batch shape of `other_geometry`. The result may only be accurate in special cases. The given geometries may be approximated as spheres or boxes using `bounding_radius()` and `bounding_half_extent()`. The default implementation of this method approximates other_geometry as a Sphere and computes the fraction using `approximate_signed_distance()`. Args: other_geometry: batched) Geometry instance other_geometry: Geometry: Returns: fraction of cell volume lying inside the geometry. float tensor of shape (other_geometry.batch_shape, 1). """ assert isinstance(other_geometry, Geometry) radius = other_geometry.bounding_radius() location = other_geometry.center distance = self.approximate_signed_distance(location) inside_fraction = 0.5 - distance / radius inside_fraction = math.clip(inside_fraction, 0, 1) return inside_fraction
def approximate_fraction_inside(self, other_geometry: 'Geometry', balance: Tensor or Number = 0.5) -> Tensor: """ Computes the approximate overlap between the geometry and a small other geometry. Returns 1.0 if `other_geometry` is fully enclosed in this geometry and 0.0 if there is no overlap. Close to the surface of this geometry, the fraction filled is differentiable w.r.t. the location and size of `other_geometry`. To call this method on batches of geometries of same shape, pass a batched Geometry instance. The result tensor will match the batch shape of `other_geometry`. The result may only be accurate in special cases. The given geometries may be approximated as spheres or boxes using `bounding_radius()` and `bounding_half_extent()`. The default implementation of this method approximates other_geometry as a Sphere and computes the fraction using `approximate_signed_distance()`. Args: other_geometry: `Geometry` or geometry batch for which to compute the overlap with `self`. balance: Mid-level between 0 and 1, default 0.5. This value is returned when exactly half of `other_geometry` lies inside `self`. `0.5 < balance <= 1` makes `self` seem larger while `0 <= balance < 0.5`makes `self` seem smaller. Returns: fraction of cell volume lying inside the geometry. float tensor of shape (other_geometry.batch_shape, 1). """ assert isinstance(other_geometry, Geometry) radius = other_geometry.bounding_radius() location = other_geometry.center distance = self.approximate_signed_distance(location) inside_fraction = balance - distance / radius inside_fraction = math.clip(inside_fraction, 0, 1) return inside_fraction
def mac_cormack(field, velocity_field, dt, correction_strength=1.0): """ MacCormack advection uses a forward and backward lookup to determine the first-order error of semi-Lagrangian advection. It then uses that error estimate to correct the field values. To avoid overshoots, the resulting value is bounded by the neighbouring grid cells of the backward lookup. :param correction_strength: the estimated error is multiplied by this factor before being applied. The case correction_strength=0 equals semi-lagrangian advection. Set lower than 1.0 to avoid oscillations. :param field: Field to be advected :param velocity_field: vector field, need not be compatible with `field`. :param dt: time increment :return: Field compatible with input field """ try: x0 = field.points v = velocity_field.at(x0) x_bwd = x0 - v * dt x_fwd = x0 + v * dt field_semi_la = field.with_data(field.sample_at( x_bwd.data)) # semi-Lagrangian advection field_inv_semi_la = field.with_data(field_semi_la.sample_at( x_fwd.data)) # inverse semi-Lagrangian advection new_field = field_semi_la + correction_strength * 0.5 * ( field - field_inv_semi_la) field_clamped = math.clip(new_field, *field.general_sample_at( x_bwd.data, 'minmax')) # Address overshoots return field_clamped except StaggeredSamplePoints: advected = [ mac_cormack(component, velocity_field, dt) for component in field.unstack() ] return field.with_data(advected)
def mac_cormack(field: GridType, velocity: Field, dt: float, correction_strength=1.0, integrator=euler) -> GridType: """ MacCormack advection uses a forward and backward lookup to determine the first-order error of semi-Lagrangian advection. It then uses that error estimate to correct the field values. To avoid overshoots, the resulting value is bounded by the neighbouring grid cells of the backward lookup. Args: field: Field to be advected, one of `(CenteredGrid, StaggeredGrid)` velocity: Vector field, need not be sampled at same locations as `field`. dt: Time increment correction_strength: The estimated error is multiplied by this factor before being applied. The case correction_strength=0 equals semi-lagrangian advection. Set lower than 1.0 to avoid oscillations. integrator: ODE integrator for solving the movement. Returns: Advected field of type `type(field)` """ v0 = sample(velocity, field.elements) points_bwd = integrator(field.elements, velocity, -dt, v0=v0) points_fwd = integrator(field.elements, velocity, dt, v0=v0) # Semi-Lagrangian advection field_semi_la = field.with_values(reduce_sample(field, points_bwd)) # Inverse semi-Lagrangian advection field_inv_semi_la = field.with_values( reduce_sample(field_semi_la, points_fwd)) # correction new_field = field_semi_la + correction_strength * 0.5 * (field - field_inv_semi_la) # Address overshoots limits = field.closest_values(points_bwd) lower_limit = math.min( limits, [f'closest_{dim}' for dim in field.shape.spatial.names]) upper_limit = math.max( limits, [f'closest_{dim}' for dim in field.shape.spatial.names]) values_clamped = math.clip(new_field.values, lower_limit, upper_limit) return new_field.with_values(values_clamped)
def mac_cormack(field: GridType, velocity: Field, dt: float, correction_strength=1.0) -> GridType: """ MacCormack advection uses a forward and backward lookup to determine the first-order error of semi-Lagrangian advection. It then uses that error estimate to correct the field values. To avoid overshoots, the resulting value is bounded by the neighbouring grid cells of the backward lookup. Args: field: Field to be advected, one of `(CenteredGrid, StaggeredGrid)` velocity: Vector field, need not be sampled at same locations as `field`. dt: Time increment correction_strength: The estimated error is multiplied by this factor before being applied. The case correction_strength=0 equals semi-lagrangian advection. Set lower than 1.0 to avoid oscillations. (Default value = 1.0) Returns: Advected field of type `type(field)` """ v = velocity.sample_in(field.elements) x0 = field.points x_bwd = x0 - v * dt x_fwd = x0 + v * dt reduce = x0.shape.non_channel.without(field.shape).names # Semi-Lagrangian advection field_semi_la = field.with_( values=field.sample_at(x_bwd, reduce_channels=reduce)) # Inverse semi-Lagrangian advection field_inv_semi_la = field.with_( values=field_semi_la.sample_at(x_fwd, reduce_channels=reduce)) # correction new_field = field_semi_la + correction_strength * 0.5 * (field - field_inv_semi_la) # Address overshoots limits = field.closest_values(x_bwd, reduce_channels=reduce) lower_limit = math.min( limits, [f'closest_{dim}' for dim in field.shape.spatial.names]) upper_limit = math.max( limits, [f'closest_{dim}' for dim in field.shape.spatial.names]) values_clamped = math.clip(new_field.values, lower_limit, upper_limit) return new_field.with_(values=values_clamped)