Module phi.geom

Differentiable geometry package.

Classes:

See the phi.geom module documentation at https://tum-pbs.github.io/PhiFlow/Geometry.html

Expand source code
"""
Differentiable geometry package.

Classes:

* `Geometry` (base type)
* `Box`
* `Sphere`

See the `phi.geom` module documentation at https://tum-pbs.github.io/PhiFlow/Geometry.html
"""

from ._geom import Geometry, Point, assert_same_rank
from ._union import union
from ._box import Box, GridCell, BaseBox, Cuboid
from ._sphere import Sphere
from ._stack import stack
from ._geom_math import concat, invert, pack_dims
from ._transform import embed, infinite_cylinder

__all__ = [key for key in globals().keys() if not key.startswith('_')]

Functions

def assert_same_rank(rank1, rank2, error_message)

Tests that two objects have the same spatial rank. Objects can be of types: int, None (no check), Geometry, Shape, Tensor

Expand source code
def assert_same_rank(rank1, rank2, error_message):
    """ Tests that two objects have the same spatial rank. Objects can be of types: `int`, `None` (no check), `Geometry`, `Shape`, `Tensor` """
    rank1_, rank2_ = _rank(rank1), _rank(rank2)
    if rank1_ is not None and rank2_ is not None:
        assert rank1_ == rank2_, 'Ranks do not match: %s and %s. %s' % (rank1_, rank2_, error_message)
def concat(geometries: tuple, dim: phi.math._shape.Shape, sizes: tuple = None)

Concatenates multiple geometries of the same type.

Args

geometries
sequence of Geometry objects of the same type
sizes
implicit
dim
dimension to concatenate

Returns

New Geometry object

Expand source code
def concat(geometries: tuple or list,
           dim: Shape,
           sizes: tuple or list or None = None):
    """
    Concatenates multiple geometries of the same type.

    Args:
        geometries: sequence of `phi.geom.Geometry` objects of the same type
        sizes: implicit
        dim: dimension to concatenate

    Returns:
        New `phi.geom.Geometry` object
    """
    if all(isinstance(g, type(geometries[0])) for g in geometries):
        characteristics = [{a: getattr(g, a) for a in variable_attributes(g)} for g in geometries]
        new_attributes = {}
        for c in characteristics[0].keys():
            if any([item[c].shape.volume > 1 for item in characteristics]) or any([not math.close(item[c], characteristics[0][c]) for item in characteristics]):
                for item, size in zip(characteristics, sizes):
                    item[c] = math.expand(item[c], dim.with_size(size))
                concatenated = math.concat([item[c] for item in characteristics], dim)
                new_attributes[c] = concatenated
        return copy_with(geometries[0], **new_attributes)
    else:
        raise NotImplementedError()
def embed(geometry: phi.geom._geom.Geometry, projected_dims: phi.math._shape.Shape) ‑> phi.geom._geom.Geometry

Adds fake spatial dimensions to a geometry. The geometry value will be constant along the added dimensions, as if it had infinite length in these directions.

Args

geometry
Geometry
projected_dims
Additional dimensions

Returns

Geometry with spatial rank geometry.spatial_rank + projected_dims.rank.

Expand source code
def embed(geometry: Geometry, projected_dims: math.Shape or str or tuple or list) -> Geometry:
    """
    Adds fake spatial dimensions to a geometry.
    The geometry value will be constant along the added dimensions, as if it had infinite length in these directions.

    Args:
        geometry: `Geometry`
        projected_dims: Additional dimensions

    Returns:
        `Geometry` with spatial rank `geometry.spatial_rank + projected_dims.rank`.
    """
    if projected_dims is None:
        return geometry
    if not isinstance(projected_dims, Shape):
        projected_dims = math.spatial(*parse_dim_order(projected_dims))
    return _EmbeddedGeometry(geometry, projected_dims) if projected_dims.rank > 0 else geometry
def infinite_cylinder(center=None, radius=None, inf_dim: str = None, **center_) ‑> phi.geom._geom.Geometry

Creates an infinite cylinder. This is equal to embedding an n-dimensional Sphere in n+1 dimensions.

See Also: Sphere, embed()

Args

center
Center coordinates without inf_dim. Alternatively use keyword arguments.
radius
Cylinder radius.
inf_dim
Dimension along which the cylinder is infinite. Use Geometry.rotated() if the direction does not align with an axis.
**center_
Alternatively specify center coordinates without inf_dim as keyword arguments.

Returns

Geometry

Expand source code
def infinite_cylinder(center=None, radius=None, inf_dim: str or Shape or tuple or list = None, **center_) -> Geometry:
    """
    Creates an infinite cylinder.
    This is equal to embedding an `n`-dimensional `Sphere` in `n+1` dimensions.

    See Also:
        `Sphere`, `embed`

    Args:
        center: Center coordinates without `inf_dim`. Alternatively use keyword arguments.
        radius: Cylinder radius.
        inf_dim: Dimension along which the cylinder is infinite.
            Use `Geometry.rotated()` if the direction does not align with an axis.
        **center_: Alternatively specify center coordinates without `inf_dim` as keyword arguments.

    Returns:
        `Geometry`
    """
    sphere = Sphere(center, radius, **center_)
    return embed(sphere, inf_dim)
def invert(geometry: phi.geom._geom.Geometry)

Swaps inside and outside.

Args

geometry
Geometry to swap

Returns

New Geometry object with same surface but swapped normals

Expand source code
def invert(geometry: Geometry):
    """
    Swaps inside and outside.

    Args:
        geometry: `phi.geom.Geometry` to swap

    Returns:
        New `phi.geom.Geometry` object with same surface but swapped normals
    """
    return ~geometry
def pack_dims(value: phi.geom._geom.Geometry, dims: phi.math._shape.Shape, packed_dim: phi.math._shape.Shape, pos: int = None) ‑> phi.geom._geom.Geometry

Reshapes dimensions of a geometry.

See Also: pack_dims().

Args

value
Geometry containing dims
dims
Dimensions to pack into a single dimension.
packed_dim
The new dimension.
pos
(Optional) Position of the new dimension.

Returns

Geometry with packed_dim instead of dims.

Expand source code
def pack_dims(value: Geometry,
              dims: Shape or tuple or list or str,
              packed_dim: Shape,
              pos: int or None = None) -> Geometry:
    """
    Reshapes dimensions of a geometry.

    See Also:
        `phi.math.pack_dims()`.

    Args:
        value: `Geometry` containing `dims`
        dims: Dimensions to pack into a single dimension.
        packed_dim: The new dimension.
        pos: (Optional) Position of the new dimension.

    Returns:
        `Geometry` with `packed_dim` instead of `dims`.
    """
    if isinstance(value, GridCell):
        value = value.corner_representation()
    dims = parse_dim_order(dims)
    new_attrs = {}
    for a in variable_attributes(value):
        v = getattr(value, a)
        if all(d in v.shape for d in dims):
            new_attrs[a] = math.pack_dims(v, dims, packed_dim, pos=pos)
        else:
            assert all(d not in v.shape for d in dims), f"Cannot pack_dims for attribute {a} of {value}"
    return copy_with(value, **new_attrs)
def stack(geometries: List[phi.geom._geom.Geometry], dim: phi.math._shape.Shape)

Stacks geometries along dim. The size of dim is ignored.

Expand source code
def stack(geometries: List[Geometry], dim: Shape):
    """ Stacks `geometries` along `dim`. The size of `dim` is ignored. """
    if all(type(g) == type(geometries[0]) and not isinstance(g, GridCell) for g in geometries):
        attrs = variable_attributes(geometries[0])
        new_attributes = {a: math.stack([getattr(g, a) for g in geometries], dim) for a in attrs}
        return copy_with(geometries[0], **new_attributes)
    return GeometryStack(math.layout(geometries, dim))
def union(*geometries) ‑> phi.geom._geom.Geometry

Union of the given geometries. A point lies inside the union if it lies within at least one of the geometries.

Args

geometries
arbitrary geometries with same spatial dims. Arbitrary batch dims are allowed.
*geometries
 

Returns

union Geometry

Expand source code
def union(*geometries) -> Geometry:
    """
    Union of the given geometries.
    A point lies inside the union if it lies within at least one of the geometries.

    Args:
      geometries: arbitrary geometries with same spatial dims. Arbitrary batch dims are allowed.
      *geometries: 

    Returns:
      union Geometry

    """
    if len(geometries) == 1 and isinstance(geometries[0], (tuple, list)):
        geometries = geometries[0]
    if len(geometries) == 0:
        return NO_GEOMETRY
    elif len(geometries) == 1:
        return geometries[0]
    elif all(type(g) == type(geometries[0]) for g in geometries):
        attrs = variable_attributes(geometries[0])
        values = {a: math.stack([getattr(g, a) for g in geometries], math.instance('union')) for a in attrs}
        return copy_with(geometries[0], **values)
    else:
        base_geometries = ()
        for geometry in geometries:
            base_geometries += geometry.geometries if isinstance(geometry, Union) else (geometry,)
        return Union(base_geometries)

Classes

class BaseBox

Abstract base type for box-like geometries.

Expand source code
class BaseBox(Geometry):  # not a Subwoofer
    """
    Abstract base type for box-like geometries.
    """

    def __eq__(self, other):
        raise NotImplementedError()

    def __hash__(self):
        raise NotImplementedError()

    def __ne__(self, other):
        return not self == other

    @property
    def shape(self):
        raise NotImplementedError()

    @property
    def center(self) -> Tensor:
        raise NotImplementedError()

    def shifted(self, delta, **delta_by_dim) -> 'BaseBox':
        raise NotImplementedError()

    @property
    def size(self) -> Tensor:
        raise NotImplementedError(self)

    @property
    def half_size(self) -> Tensor:
        raise NotImplementedError(self)

    @property
    def lower(self) -> Tensor:
        raise NotImplementedError(self)

    @property
    def upper(self) -> Tensor:
        raise NotImplementedError(self)

    @property
    def volume(self) -> Tensor:
        return math.prod(self.size, 'vector')

    @property
    def shape_type(self) -> Tensor:
        return math.tensor('B')

    def bounding_radius(self):
        return math.max(self.size, 'vector') * 1.414214

    def bounding_half_extent(self):
        return self.size * 0.5

    def global_to_local(self, global_position: Tensor) -> Tensor:
        if math.close(self.lower, 0):
            return global_position / self.size
        else:
            return (global_position - self.lower) / self.size

    def local_to_global(self, local_position):
        return local_position * self.size + self.lower

    def lies_inside(self, location):
        bool_inside = (location >= self.lower) & (location <= self.upper)
        bool_inside = math.all(bool_inside, 'vector')
        bool_inside = math.any(bool_inside, self.shape.instance)  # union for instance dimensions
        return bool_inside

    def approximate_signed_distance(self, location):
        """
        Computes the signed L-infinity norm (manhattan distance) from the location to the nearest side of the box.
        For an outside location `l` with the closest surface point `s`, the distance is `max(abs(l - s))`.
        For inside locations it is `-max(abs(l - s))`.

        Args:
          location: float tensor of shape (batch_size, ..., rank)

        Returns:
          float tensor of shape (*location.shape[:-1], 1).

        """
        center = 0.5 * (self.lower + self.upper)
        extent = self.upper - self.lower
        distance = math.abs(location - center) - extent * 0.5
        distance = math.max(distance, 'vector')
        distance = math.min(distance, self.shape.instance)  # union for instance dimensions
        return distance

    def push(self, positions: Tensor, outward: bool = True, shift_amount: float = 0) -> Tensor:
        loc_to_center = positions - self.center
        sgn_dist_from_surface = math.abs(loc_to_center) - self.half_size
        if outward:
            # --- get negative distances (particles are inside) towards the nearest boundary and add shift_amount ---
            distances_of_interest = (sgn_dist_from_surface == math.max(sgn_dist_from_surface, 'vector')) & (sgn_dist_from_surface < 0)
            shift = distances_of_interest * (sgn_dist_from_surface - shift_amount)
        else:
            shift = (sgn_dist_from_surface + shift_amount) * (sgn_dist_from_surface > 0)  # get positive distances (particles are outside) and add shift_amount
            shift = math.where(math.abs(shift) > math.abs(loc_to_center), math.abs(loc_to_center), shift)  # ensure inward shift ends at center
        return positions + math.where(loc_to_center < 0, 1, -1) * shift

    def project(self, *dimensions: str):
        """ Project this box into a lower-dimensional space. """
        if self.size.vector.item_names is None:
            assert len(dimensions) == self.spatial_rank, "Cannot project a Box if item names not available"
            return self
        lower = self.lower.vector[dimensions]
        upper = self.upper.vector[dimensions]
        return Box(lower, upper)

    def sample_uniform(self, *shape: math.Shape) -> Tensor:
        uniform = math.random_uniform(self.shape.non_singleton, *shape, math.channel(vector=self.spatial_rank))
        return self.lower + uniform * self.size

    def corner_representation(self) -> 'Box':
        return Box(self.lower, self.upper)

    def center_representation(self) -> 'Cuboid':
        return Cuboid(self.center, self.half_size)

    def contains(self, other: 'BaseBox'):
        """ Tests if the other box lies fully inside this box. """
        return np.all(other.lower >= self.lower) and np.all(other.upper <= self.upper)

    def rotated(self, angle) -> Geometry:
        return rotate(self, angle)

    def scaled(self, factor: float or Tensor) -> 'Geometry':
        return Cuboid(self.center, self.half_size * factor)

Ancestors

  • phi.geom._geom.Geometry

Subclasses

  • phi.geom._box.Box
  • phi.geom._box.Cuboid
  • phi.geom._box.GridCell

Instance variables

var center : phi.math._tensors.Tensor

Center location in single channel dimension, ordered according to GLOBAL_AXIS_ORDER

Expand source code
@property
def center(self) -> Tensor:
    raise NotImplementedError()
var half_size : phi.math._tensors.Tensor
Expand source code
@property
def half_size(self) -> Tensor:
    raise NotImplementedError(self)
var lower : phi.math._tensors.Tensor
Expand source code
@property
def lower(self) -> Tensor:
    raise NotImplementedError(self)
var shape

Specifies the number of copies of the geometry as batch and spatial dimensions.

Expand source code
@property
def shape(self):
    raise NotImplementedError()
var shape_type : phi.math._tensors.Tensor

Returns the type (or types) of this geometry as a string Tensor Boxes return 'B' and spheres return 'S'. Returns '?' for unknown types, e.g. a union over multiple types. Custom types can return their own identifiers.

Returns

String Tensor

Expand source code
@property
def shape_type(self) -> Tensor:
    return math.tensor('B')
var size : phi.math._tensors.Tensor
Expand source code
@property
def size(self) -> Tensor:
    raise NotImplementedError(self)
var upper : phi.math._tensors.Tensor
Expand source code
@property
def upper(self) -> Tensor:
    raise NotImplementedError(self)
var volume : phi.math._tensors.Tensor

Volume of the geometry as Tensor. The result retains all batch dimensions while instance dimensions are summed over.

Expand source code
@property
def volume(self) -> Tensor:
    return math.prod(self.size, 'vector')

Methods

def approximate_signed_distance(self, location)

Computes the signed L-infinity norm (manhattan distance) from the location to the nearest side of the box. For an outside location l with the closest surface point s, the distance is max(abs(l - s)). For inside locations it is -max(abs(l - s)).

Args

location
float tensor of shape (batch_size, …, rank)

Returns

float tensor of shape (*location.shape[:-1], 1).

Expand source code
def approximate_signed_distance(self, location):
    """
    Computes the signed L-infinity norm (manhattan distance) from the location to the nearest side of the box.
    For an outside location `l` with the closest surface point `s`, the distance is `max(abs(l - s))`.
    For inside locations it is `-max(abs(l - s))`.

    Args:
      location: float tensor of shape (batch_size, ..., rank)

    Returns:
      float tensor of shape (*location.shape[:-1], 1).

    """
    center = 0.5 * (self.lower + self.upper)
    extent = self.upper - self.lower
    distance = math.abs(location - center) - extent * 0.5
    distance = math.max(distance, 'vector')
    distance = math.min(distance, self.shape.instance)  # union for instance dimensions
    return distance
def bounding_half_extent(self)

The bounding half-extent sets a limit on the outer-most point for each coordinate axis. Each component is non-negative.

Let the bounding half-extent have value e in dimension d (extent[...,d] = e). Then, no point of the geometry lies further away from its center point than e along d (in both axis directions).

:return: float vector

Args:

Returns:

Expand source code
def bounding_half_extent(self):
    return self.size * 0.5
def bounding_radius(self)

Returns the radius of a Sphere object that fully encloses this geometry. The sphere is centered at the center of this geometry.

:return: radius of type float

Args:

Returns:

Expand source code
def bounding_radius(self):
    return math.max(self.size, 'vector') * 1.414214
def center_representation(self) ‑> phi.geom._box.Cuboid
Expand source code
def center_representation(self) -> 'Cuboid':
    return Cuboid(self.center, self.half_size)
def contains(self, other: BaseBox)

Tests if the other box lies fully inside this box.

Expand source code
def contains(self, other: 'BaseBox'):
    """ Tests if the other box lies fully inside this box. """
    return np.all(other.lower >= self.lower) and np.all(other.upper <= self.upper)
def corner_representation(self) ‑> phi.geom._box.Box
Expand source code
def corner_representation(self) -> 'Box':
    return Box(self.lower, self.upper)
def global_to_local(self, global_position: phi.math._tensors.Tensor) ‑> phi.math._tensors.Tensor
Expand source code
def global_to_local(self, global_position: Tensor) -> Tensor:
    if math.close(self.lower, 0):
        return global_position / self.size
    else:
        return (global_position - self.lower) / self.size
def lies_inside(self, location)

Tests whether the given location lies inside or outside of the geometry. Locations on the surface count as inside.

When dealing with unions or collections of geometries (instance dimensions), a point lies inside the geometry if it lies inside any instance.

Args

location
float tensor of shape (batch_size, …, rank)

Returns

bool tensor of shape (*location.shape[:-1], 1).

Expand source code
def lies_inside(self, location):
    bool_inside = (location >= self.lower) & (location <= self.upper)
    bool_inside = math.all(bool_inside, 'vector')
    bool_inside = math.any(bool_inside, self.shape.instance)  # union for instance dimensions
    return bool_inside
def local_to_global(self, local_position)
Expand source code
def local_to_global(self, local_position):
    return local_position * self.size + self.lower
def project(self, *dimensions: str)

Project this box into a lower-dimensional space.

Expand source code
def project(self, *dimensions: str):
    """ Project this box into a lower-dimensional space. """
    if self.size.vector.item_names is None:
        assert len(dimensions) == self.spatial_rank, "Cannot project a Box if item names not available"
        return self
    lower = self.lower.vector[dimensions]
    upper = self.upper.vector[dimensions]
    return Box(lower, upper)
def push(self, positions: phi.math._tensors.Tensor, outward: bool = True, shift_amount: float = 0) ‑> phi.math._tensors.Tensor

Shifts positions either into or out of geometry.

Args

positions
Tensor holding positions to shift
outward
Flag for indicating inward (False) or outward (True) shift
shift_amount
Minimum distance between positions and box boundaries after shifting

Returns

Tensor holding shifted positions

Expand source code
def push(self, positions: Tensor, outward: bool = True, shift_amount: float = 0) -> Tensor:
    loc_to_center = positions - self.center
    sgn_dist_from_surface = math.abs(loc_to_center) - self.half_size
    if outward:
        # --- get negative distances (particles are inside) towards the nearest boundary and add shift_amount ---
        distances_of_interest = (sgn_dist_from_surface == math.max(sgn_dist_from_surface, 'vector')) & (sgn_dist_from_surface < 0)
        shift = distances_of_interest * (sgn_dist_from_surface - shift_amount)
    else:
        shift = (sgn_dist_from_surface + shift_amount) * (sgn_dist_from_surface > 0)  # get positive distances (particles are outside) and add shift_amount
        shift = math.where(math.abs(shift) > math.abs(loc_to_center), math.abs(loc_to_center), shift)  # ensure inward shift ends at center
    return positions + math.where(loc_to_center < 0, 1, -1) * shift
def rotated(self, angle) ‑> phi.geom._geom.Geometry

Returns a rotated version of this geometry. The geometry is rotated about its center point.

Args

angle
scalar (2d) or vector (3D+) representing delta angle

Returns

Rotated Geometry

Expand source code
def rotated(self, angle) -> Geometry:
    return rotate(self, angle)
def sample_uniform(self, *shape: phi.math._shape.Shape) ‑> phi.math._tensors.Tensor

Samples uniformly distributed random points inside this volume.

Args

*shape
How many points to sample per individual geometry.

Returns

Tensor containing all dimensions from Geometry.shape, shape as well as a channel dimension vector matching the dimensionality of this Geometry.

Expand source code
def sample_uniform(self, *shape: math.Shape) -> Tensor:
    uniform = math.random_uniform(self.shape.non_singleton, *shape, math.channel(vector=self.spatial_rank))
    return self.lower + uniform * self.size
def scaled(self, factor: float) ‑> phi.geom._geom.Geometry

Scales each individual geometry by factor. The individual center points act as pivots for the operation.

Args

factor: Returns:

Expand source code
def scaled(self, factor: float or Tensor) -> 'Geometry':
    return Cuboid(self.center, self.half_size * factor)
def shifted(self, delta, **delta_by_dim) ‑> phi.geom._box.BaseBox

Returns a translated version of this geometry.

See Also: Geometry.at().

Args

delta
direction vector
delta
Tensor:

Returns

Geometry
shifted geometry
Expand source code
def shifted(self, delta, **delta_by_dim) -> 'BaseBox':
    raise NotImplementedError()
class Box (lower: phi.math._tensors.Tensor = None, upper: phi.math._tensors.Tensor = None, **size: int)

Simple cuboid defined by location of lower and upper corner in physical space.

Boxes can be constructed either from two positional vector arguments (lower, upper) or by specifying the limits by dimension name as kwargs.

Examples:

Box(x=1, y=1)  # creates a two-dimensional unit box with `lower=(0, 0)` and `upper=(1, 1)`.
Box(x=(None, 1), y=(0, None)  # creates a Box with `lower=(-inf, 0)` and `upper=(1, inf)`.

Args

lower
physical location of lower corner
upper
physical location of upper corner
**size
Upper l
Expand source code
class Box(BaseBox, metaclass=BoxType):
    """
    Simple cuboid defined by location of lower and upper corner in physical space.

    Boxes can be constructed either from two positional vector arguments `(lower, upper)` or by specifying the limits by dimension name as `kwargs`.

    **Examples**:

    ```python
    Box(x=1, y=1)  # creates a two-dimensional unit box with `lower=(0, 0)` and `upper=(1, 1)`.
    Box(x=(None, 1), y=(0, None)  # creates a Box with `lower=(-inf, 0)` and `upper=(1, inf)`.
    ```
    """

    def __init__(self,
                 lower: Tensor or float or int = None,
                 upper: Tensor or float or int = None,
                 **size: int or Tensor):
        """
        Args:
          lower: physical location of lower corner
          upper: physical location of upper corner
          **size: Upper l
        """
        if lower is not None:
            self._lower = wrap(lower)
        if upper is not None:
            self._upper = wrap(upper)
        else:
            lower = []
            upper = []
            for item in size.values():
                if isinstance(item, (tuple, list)):
                    assert len(item) == 2, f"Box kwargs must be either dim=upper or dim=(lower,upper) but got {item}"
                    lo, up = item
                    lower.append(lo)
                    upper.append(up)
                elif item is None:
                    lower.append(-INF)
                    upper.append(INF)
                else:
                    lower.append(0)
                    upper.append(item)
            lower = [-INF if l is None else l for l in lower]
            upper = [INF if u is None else u for u in upper]
            self._upper = math.wrap(upper, math.channel(vector=tuple(size.keys())))
            self._lower = math.wrap(lower, math.channel(vector=tuple(size.keys())))
        vector_shape = self._lower.shape & self._upper.shape
        self._lower = math.expand(self._lower, vector_shape)
        self._upper = math.expand(self._upper, vector_shape)
        if self.size.vector.item_names is None:
            warnings.warn("Creating a Box without item names prevents certain operations like project()", DeprecationWarning, stacklevel=2)

    def unstack(self, dimension):
        size = combined_dim(self._lower.shape.get_size(dimension), self._upper.shape.get_size(dimension))
        lowers = self._lower.dimension(dimension).unstack(size)
        uppers = self._upper.dimension(dimension).unstack(size)
        return tuple(Box(lo, up) for lo, up in zip(lowers, uppers))

    def __eq__(self, other):
        return isinstance(other, BaseBox)\
               and set(self.shape) == set(other.shape)\
               and self.size.shape.get_size('vector') == other.size.shape.get_size('vector')\
               and math.close(self._lower, other.lower)\
               and math.close(self._upper, other.upper)

    def __hash__(self):
        return hash(self._upper)

    def __variable_attrs__(self):
        return '_lower', '_upper'

    @property
    def shape(self):
        if self._lower is None or self._upper is None:
            return None
        return (self._lower.shape & self._upper.shape).non_channel

    @property
    def lower(self):
        return self._lower

    @property
    def upper(self):
        return self._upper

    @property
    def size(self):
        return self.upper - self.lower

    @property
    def center(self):
        return 0.5 * (self.lower + self.upper)

    @property
    def half_size(self):
        return self.size * 0.5

    def shifted(self, delta, **delta_by_dim):
        return Box(self.lower + delta, self.upper + delta)

    def __mul__(self, other):
        if not isinstance(other, Box):
            return NotImplemented
        lower = self._lower.vector.unstack(self.spatial_rank) + other._lower.vector.unstack(self.spatial_rank)
        upper = self._upper.vector.unstack(self.spatial_rank) + other._upper.vector.unstack(self.spatial_rank)
        names = self._upper.vector.item_names + other._upper.vector.item_names
        lower = math.stack(lower, math.channel(vector=names))
        upper = math.stack(upper, math.channel(vector=names))
        return Box(lower, upper)

    def __repr__(self):
        if self.shape.non_channel.volume == 1:
            item_names = self.size.vector.item_names
            if item_names:
                return f"Box({', '.join([f'{dim}=({lo}, {up})' for dim, lo, up in zip(item_names, self._lower, self._upper)])})"
            else:  # deprecated
                return 'Box[%s at %s]' % ('x'.join([str(x) for x in self.size.numpy().flatten()]), ','.join([str(x) for x in self.lower.numpy().flatten()]))
        else:
            return 'Box[shape=%s]' % self.shape

Ancestors

  • phi.geom._box.BaseBox
  • phi.geom._geom.Geometry

Instance variables

var center

Center location in single channel dimension, ordered according to GLOBAL_AXIS_ORDER

Expand source code
@property
def center(self):
    return 0.5 * (self.lower + self.upper)
var half_size
Expand source code
@property
def half_size(self):
    return self.size * 0.5
var lower
Expand source code
@property
def lower(self):
    return self._lower
var shape

Specifies the number of copies of the geometry as batch and spatial dimensions.

Expand source code
@property
def shape(self):
    if self._lower is None or self._upper is None:
        return None
    return (self._lower.shape & self._upper.shape).non_channel
var size
Expand source code
@property
def size(self):
    return self.upper - self.lower
var upper
Expand source code
@property
def upper(self):
    return self._upper

Methods

def shifted(self, delta, **delta_by_dim)

Returns a translated version of this geometry.

See Also: Geometry.at().

Args

delta
direction vector
delta
Tensor:

Returns

Geometry
shifted geometry
Expand source code
def shifted(self, delta, **delta_by_dim):
    return Box(self.lower + delta, self.upper + delta)
def unstack(self, dimension)

Unstacks this Geometry along the given dimension. The shapes of the returned geometries are reduced by dimension.

Args

dimension
dimension along which to unstack

Returns

geometries
tuple of length equal to geometry.shape.get_size(dimension)
Expand source code
def unstack(self, dimension):
    size = combined_dim(self._lower.shape.get_size(dimension), self._upper.shape.get_size(dimension))
    lowers = self._lower.dimension(dimension).unstack(size)
    uppers = self._upper.dimension(dimension).unstack(size)
    return tuple(Box(lo, up) for lo, up in zip(lowers, uppers))
class Cuboid (center: phi.math._tensors.Tensor = 0, half_size: float = None, **size: float)

Box specified by center position and half size.

Expand source code
class Cuboid(BaseBox):
    """
    Box specified by center position and half size.
    """

    def __init__(self,
                 center: Tensor = 0,
                 half_size: float or Tensor = None,
                 **size: float or Tensor):
        self._center = wrap(center)
        if half_size is not None:
            self._half_size = wrap(half_size)
        else:
            self._half_size = math.wrap(tuple(size.values()), math.channel(vector=tuple(size.keys()))) * 0.5

    def __eq__(self, other):
        return isinstance(other, BaseBox)\
               and set(self.shape) == set(other.shape)\
               and math.close(self._center, other.center)\
               and math.close(self._half_size, other.half_size)

    def __hash__(self):
        return hash(self._center)

    def __variable_attrs__(self):
        return '_center', '_half_size'

    @property
    def center(self):
        return self._center

    @property
    def half_size(self):
        return self._half_size

    @property
    def shape(self):
        if self._center is None or self._half_size is None:
            return None
        return (self._center.shape & self._half_size.shape).without('vector')

    @property
    def size(self):
        return 2 * self.half_size

    @property
    def lower(self):
        return self.center - self.half_size

    @property
    def upper(self):
        return self.center + self.half_size

    def shifted(self, delta, **delta_by_dim) -> 'Cuboid':
        return Cuboid(self._center + delta, self._half_size)

Ancestors

  • phi.geom._box.BaseBox
  • phi.geom._geom.Geometry

Instance variables

var center

Center location in single channel dimension, ordered according to GLOBAL_AXIS_ORDER

Expand source code
@property
def center(self):
    return self._center
var half_size
Expand source code
@property
def half_size(self):
    return self._half_size
var lower
Expand source code
@property
def lower(self):
    return self.center - self.half_size
var shape

Specifies the number of copies of the geometry as batch and spatial dimensions.

Expand source code
@property
def shape(self):
    if self._center is None or self._half_size is None:
        return None
    return (self._center.shape & self._half_size.shape).without('vector')
var size
Expand source code
@property
def size(self):
    return 2 * self.half_size
var upper
Expand source code
@property
def upper(self):
    return self.center + self.half_size

Methods

def shifted(self, delta, **delta_by_dim) ‑> phi.geom._box.Cuboid

Returns a translated version of this geometry.

See Also: Geometry.at().

Args

delta
direction vector
delta
Tensor:

Returns

Geometry
shifted geometry
Expand source code
def shifted(self, delta, **delta_by_dim) -> 'Cuboid':
    return Cuboid(self._center + delta, self._half_size)
class Geometry

Abstract base class for N-dimensional shapes.

Main implementing classes:

  • Sphere
  • box family: box (generator), Box, Cuboid, BaseBox

All geometry objects support batching. Thereby any parameter defining the geometry can be varied along arbitrary batch dims. All batch dimensions are listed in Geometry.shape.

Expand source code
class Geometry:
    """
    Abstract base class for N-dimensional shapes.

    Main implementing classes:

    * Sphere
    * box family: box (generator), Box, Cuboid, BaseBox

    All geometry objects support batching.
    Thereby any parameter defining the geometry can be varied along arbitrary batch dims.
    All batch dimensions are listed in Geometry.shape.
    """

    @property
    def center(self) -> Tensor:
        """
        Center location in single channel dimension, ordered according to GLOBAL_AXIS_ORDER
        """
        raise NotImplementedError(self)

    @property
    def shape(self) -> Shape:
        """
        Specifies the number of copies of the geometry as batch and spatial dimensions.
        """
        raise NotImplementedError()

    @property
    def volume(self) -> Tensor:
        """
        Volume of the geometry as `phi.math.Tensor`.
        The result retains all batch dimensions while instance dimensions are summed over.
        """
        raise NotImplementedError()

    @property
    def shape_type(self) -> Tensor:
        """
        Returns the type (or types) of this geometry as a string `Tensor`
        Boxes return `'B'` and spheres return `'S'`.
        Returns `'?'` for unknown types, e.g. a union over multiple types.
        Custom types can return their own identifiers.

        Returns:
            String `Tensor`
        """
        raise NotImplementedError()

    def unstack(self, dimension: str) -> tuple:
        """
        Unstacks this Geometry along the given dimension.
        The shapes of the returned geometries are reduced by `dimension`.

        Args:
            dimension: dimension along which to unstack

        Returns:
            geometries: tuple of length equal to `geometry.shape.get_size(dimension)`
        """
        attrs = {a: getattr(self, a) for a in variable_attributes(self)}
        return tuple([copy_with(self, **{a: v[{dimension: i}] for a, v in attrs.items() if dimension in v.shape}) for i in range(self.shape.get_size(dimension))])

    @property
    def spatial_rank(self) -> int:
        """ Number of spatial dimensions of the geometry, 1 = 1D, 2 = 2D, 3 = 3D, etc. """
        return self.center.shape.get_size('vector')

    def lies_inside(self, location: Tensor) -> Tensor:
        """
        Tests whether the given location lies inside or outside of the geometry. Locations on the surface count as inside.

        When dealing with unions or collections of geometries (instance dimensions), a point lies inside the geometry if it lies inside any instance.

        Args:
          location: float tensor of shape (batch_size, ..., rank)

        Returns:
          bool tensor of shape (*location.shape[:-1], 1).

        """
        raise NotImplementedError(self.__class__)

    def approximate_signed_distance(self, location: Tensor) -> Tensor:
        """
        Computes the approximate distance from location to the surface of the geometry.
        Locations outside return positive values, inside negative values and zero exactly at the boundary.

        The exact distance metric used depends on the geometry.
        The approximation holds close to the surface and the distance grows to infinity as the location is moved infinitely far from the geometry.
        The distance metric is differentiable and its gradients are bounded at every point in space.

        When dealing with unions or collections of geometries (instance dimensions), the shortest distance to any instance is returned.
        This also holds for negative distances.

        Args:
          location: float tensor of shape (batch_size, ..., rank)
          location: Tensor:

        Returns:
          float tensor of shape (*location.shape[:-1], 1).

        """
        raise NotImplementedError(self.__class__)

    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 push(self, positions: Tensor, outward: bool = True, shift_amount: float = 0) -> Tensor:
        """
        Shifts positions either into or out of geometry.

        Args:
            positions: Tensor holding positions to shift
            outward: Flag for indicating inward (False) or outward (True) shift
            shift_amount: Minimum distance between positions and box boundaries after shifting

        Returns:
            Tensor holding shifted positions
        """
        raise NotImplementedError(self.__class__)

    def sample_uniform(self, *shape: math.Shape) -> Tensor:
        """
        Samples uniformly distributed random points inside this volume.

        Args:
            *shape: How many points to sample per individual geometry.

        Returns:
            `Tensor` containing all dimensions from `Geometry.shape`, `shape` as well as a `channel` dimension `vector` matching the dimensionality of this `Geometry`.
        """
        raise NotImplementedError(self.__class__)

    def bounding_radius(self) -> Tensor:
        """
        Returns the radius of a Sphere object that fully encloses this geometry.
        The sphere is centered at the center of this geometry.

        :return: radius of type float

        Args:

        Returns:

        """
        raise NotImplementedError(self.__class__)

    def bounding_half_extent(self) -> Tensor:
        """
        The bounding half-extent sets a limit on the outer-most point for each coordinate axis.
        Each component is non-negative.

        Let the bounding half-extent have value `e` in dimension `d` (`extent[...,d] = e`).
        Then, no point of the geometry lies further away from its center point than `e` along `d` (in both axis directions).

        :return: float vector

        Args:

        Returns:

        """
        raise NotImplementedError(self.__class__)

    def shifted(self, delta: Tensor) -> 'Geometry':
        """
        Returns a translated version of this geometry.

        See Also:
            `Geometry.at()`.

        Args:
          delta: direction vector
          delta: Tensor:

        Returns:
          Geometry: shifted geometry

        """
        raise NotImplementedError(self.__class__)

    def at(self, center: Tensor):
        """
        Returns a copy of this `Geometry` with the center at `center`.
        This is equal to calling `self @ center`.

        See Also:
            `Geometry.shifted()`.

        Args:
            center: New center as `Tensor`.

        Returns:
            `Geometry`.
        """
        return self.shifted(center - self.center)

    def __matmul__(self, other):
        return self.at(other)

    def rotated(self, angle: float or Tensor) -> 'Geometry':
        """
        Returns a rotated version of this geometry.
        The geometry is rotated about its center point.

        Args:
          angle: scalar (2d) or vector (3D+) representing delta angle

        Returns:
            Rotated `Geometry`
        """
        raise NotImplementedError(self.__class__)

    def scaled(self, factor: float or Tensor) -> 'Geometry':
        """
        Scales each individual geometry by `factor`.
        The individual `center` points act as pivots for the operation.

        Args:
            factor:

        Returns:

        """
        raise NotImplementedError(self.__class__)

    def __invert__(self):
        return _InvertedGeometry(self)

    def __eq__(self, other):
        """
        Slow equality check.
        Unlike `==`, this method compares all tensor elements to check whether they are equal.
        Use `==` for a faster check which only checks whether the referenced tensors are the same.

        See Also:
            `shallow_equals()`
        """
        if self is other:
            return True
        if not isinstance(other, type(self)):
            return False
        if self.shape != other.shape:
            return False
        c1 = {a: getattr(self, a) for a in variable_attributes(self)}
        c2 = {a: getattr(other, a) for a in variable_attributes(self)}
        for c in c1.keys():
            if c1[c] is not c2[c] and math.any(c1[c] != c2[c]):
                return False
        return True

    def shallow_equals(self, other):
        """
        Quick equality check.
        May return `False` even if `other == self`.
        However, if `True` is returned, the geometries are guaranteed to be equal.

        The `shallow_equals()` check does not compare all tensor elements but merely checks whether the same tensors are referenced.
        """
        if self is other:
            return True
        if not isinstance(other, type(self)):
            return False
        if self.shape != other.shape:
            return False
        c1 = {a: getattr(self, a) for a in variable_attributes(self)}
        c2 = {a: getattr(other, a) for a in variable_attributes(self)}
        for c in c1.keys():
            if c1[c] is not c2[c]:
                return False
        return True

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        raise NotImplementedError(self.__class__)

    def __repr__(self):
        return f"{self.__class__.__name__}{self.shape}"

    def __getitem__(self, item: dict):
        assert isinstance(item, dict), "Index must be dict of type {dim: slice/int}."
        item = {dim: sel for dim, sel in item.items() if dim != 'vector'}
        attrs = {a: getattr(self, a)[item] for a in variable_attributes(self)}
        return copy_with(self, **attrs)

Subclasses

  • phi.geom._box.BaseBox
  • phi.geom._geom.Point
  • phi.geom._geom._InvertedGeometry
  • phi.geom._geom._NoGeometry
  • phi.geom._sphere.Sphere
  • phi.geom._stack.GeometryStack
  • phi.geom._transform.RotatedGeometry
  • phi.geom._transform._EmbeddedGeometry
  • phi.geom._union.Union

Instance variables

var center : phi.math._tensors.Tensor

Center location in single channel dimension, ordered according to GLOBAL_AXIS_ORDER

Expand source code
@property
def center(self) -> Tensor:
    """
    Center location in single channel dimension, ordered according to GLOBAL_AXIS_ORDER
    """
    raise NotImplementedError(self)
var shape : phi.math._shape.Shape

Specifies the number of copies of the geometry as batch and spatial dimensions.

Expand source code
@property
def shape(self) -> Shape:
    """
    Specifies the number of copies of the geometry as batch and spatial dimensions.
    """
    raise NotImplementedError()
var shape_type : phi.math._tensors.Tensor

Returns the type (or types) of this geometry as a string Tensor Boxes return 'B' and spheres return 'S'. Returns '?' for unknown types, e.g. a union over multiple types. Custom types can return their own identifiers.

Returns

String Tensor

Expand source code
@property
def shape_type(self) -> Tensor:
    """
    Returns the type (or types) of this geometry as a string `Tensor`
    Boxes return `'B'` and spheres return `'S'`.
    Returns `'?'` for unknown types, e.g. a union over multiple types.
    Custom types can return their own identifiers.

    Returns:
        String `Tensor`
    """
    raise NotImplementedError()
var spatial_rank : int

Number of spatial dimensions of the geometry, 1 = 1D, 2 = 2D, 3 = 3D, etc.

Expand source code
@property
def spatial_rank(self) -> int:
    """ Number of spatial dimensions of the geometry, 1 = 1D, 2 = 2D, 3 = 3D, etc. """
    return self.center.shape.get_size('vector')
var volume : phi.math._tensors.Tensor

Volume of the geometry as Tensor. The result retains all batch dimensions while instance dimensions are summed over.

Expand source code
@property
def volume(self) -> Tensor:
    """
    Volume of the geometry as `phi.math.Tensor`.
    The result retains all batch dimensions while instance dimensions are summed over.
    """
    raise NotImplementedError()

Methods

def approximate_fraction_inside(self, other_geometry: Geometry, balance: phi.math._tensors.Tensor = 0.5) ‑> phi.math._tensors.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.5makes self seem smaller.

Returns

fraction of cell volume lying inside the geometry. float tensor of shape (other_geometry.batch_shape, 1).

Expand source code
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 approximate_signed_distance(self, location: phi.math._tensors.Tensor) ‑> phi.math._tensors.Tensor

Computes the approximate distance from location to the surface of the geometry. Locations outside return positive values, inside negative values and zero exactly at the boundary.

The exact distance metric used depends on the geometry. The approximation holds close to the surface and the distance grows to infinity as the location is moved infinitely far from the geometry. The distance metric is differentiable and its gradients are bounded at every point in space.

When dealing with unions or collections of geometries (instance dimensions), the shortest distance to any instance is returned. This also holds for negative distances.

Args

location
float tensor of shape (batch_size, …, rank)
location
Tensor:

Returns

float tensor of shape (*location.shape[:-1], 1).

Expand source code
def approximate_signed_distance(self, location: Tensor) -> Tensor:
    """
    Computes the approximate distance from location to the surface of the geometry.
    Locations outside return positive values, inside negative values and zero exactly at the boundary.

    The exact distance metric used depends on the geometry.
    The approximation holds close to the surface and the distance grows to infinity as the location is moved infinitely far from the geometry.
    The distance metric is differentiable and its gradients are bounded at every point in space.

    When dealing with unions or collections of geometries (instance dimensions), the shortest distance to any instance is returned.
    This also holds for negative distances.

    Args:
      location: float tensor of shape (batch_size, ..., rank)
      location: Tensor:

    Returns:
      float tensor of shape (*location.shape[:-1], 1).

    """
    raise NotImplementedError(self.__class__)
def at(self, center: phi.math._tensors.Tensor)

Returns a copy of this Geometry with the center at center. This is equal to calling self @ center.

See Also: Geometry.shifted().

Args

center
New center as Tensor.

Returns

Geometry.

Expand source code
def at(self, center: Tensor):
    """
    Returns a copy of this `Geometry` with the center at `center`.
    This is equal to calling `self @ center`.

    See Also:
        `Geometry.shifted()`.

    Args:
        center: New center as `Tensor`.

    Returns:
        `Geometry`.
    """
    return self.shifted(center - self.center)
def bounding_half_extent(self) ‑> phi.math._tensors.Tensor

The bounding half-extent sets a limit on the outer-most point for each coordinate axis. Each component is non-negative.

Let the bounding half-extent have value e in dimension d (extent[...,d] = e). Then, no point of the geometry lies further away from its center point than e along d (in both axis directions).

:return: float vector

Args:

Returns:

Expand source code
def bounding_half_extent(self) -> Tensor:
    """
    The bounding half-extent sets a limit on the outer-most point for each coordinate axis.
    Each component is non-negative.

    Let the bounding half-extent have value `e` in dimension `d` (`extent[...,d] = e`).
    Then, no point of the geometry lies further away from its center point than `e` along `d` (in both axis directions).

    :return: float vector

    Args:

    Returns:

    """
    raise NotImplementedError(self.__class__)
def bounding_radius(self) ‑> phi.math._tensors.Tensor

Returns the radius of a Sphere object that fully encloses this geometry. The sphere is centered at the center of this geometry.

:return: radius of type float

Args:

Returns:

Expand source code
def bounding_radius(self) -> Tensor:
    """
    Returns the radius of a Sphere object that fully encloses this geometry.
    The sphere is centered at the center of this geometry.

    :return: radius of type float

    Args:

    Returns:

    """
    raise NotImplementedError(self.__class__)
def lies_inside(self, location: phi.math._tensors.Tensor) ‑> phi.math._tensors.Tensor

Tests whether the given location lies inside or outside of the geometry. Locations on the surface count as inside.

When dealing with unions or collections of geometries (instance dimensions), a point lies inside the geometry if it lies inside any instance.

Args

location
float tensor of shape (batch_size, …, rank)

Returns

bool tensor of shape (*location.shape[:-1], 1).

Expand source code
def lies_inside(self, location: Tensor) -> Tensor:
    """
    Tests whether the given location lies inside or outside of the geometry. Locations on the surface count as inside.

    When dealing with unions or collections of geometries (instance dimensions), a point lies inside the geometry if it lies inside any instance.

    Args:
      location: float tensor of shape (batch_size, ..., rank)

    Returns:
      bool tensor of shape (*location.shape[:-1], 1).

    """
    raise NotImplementedError(self.__class__)
def push(self, positions: phi.math._tensors.Tensor, outward: bool = True, shift_amount: float = 0) ‑> phi.math._tensors.Tensor

Shifts positions either into or out of geometry.

Args

positions
Tensor holding positions to shift
outward
Flag for indicating inward (False) or outward (True) shift
shift_amount
Minimum distance between positions and box boundaries after shifting

Returns

Tensor holding shifted positions

Expand source code
def push(self, positions: Tensor, outward: bool = True, shift_amount: float = 0) -> Tensor:
    """
    Shifts positions either into or out of geometry.

    Args:
        positions: Tensor holding positions to shift
        outward: Flag for indicating inward (False) or outward (True) shift
        shift_amount: Minimum distance between positions and box boundaries after shifting

    Returns:
        Tensor holding shifted positions
    """
    raise NotImplementedError(self.__class__)
def rotated(self, angle: float) ‑> phi.geom._geom.Geometry

Returns a rotated version of this geometry. The geometry is rotated about its center point.

Args

angle
scalar (2d) or vector (3D+) representing delta angle

Returns

Rotated Geometry

Expand source code
def rotated(self, angle: float or Tensor) -> 'Geometry':
    """
    Returns a rotated version of this geometry.
    The geometry is rotated about its center point.

    Args:
      angle: scalar (2d) or vector (3D+) representing delta angle

    Returns:
        Rotated `Geometry`
    """
    raise NotImplementedError(self.__class__)
def sample_uniform(self, *shape: phi.math._shape.Shape) ‑> phi.math._tensors.Tensor

Samples uniformly distributed random points inside this volume.

Args

*shape
How many points to sample per individual geometry.

Returns

Tensor containing all dimensions from Geometry.shape, shape as well as a channel dimension vector matching the dimensionality of this Geometry.

Expand source code
def sample_uniform(self, *shape: math.Shape) -> Tensor:
    """
    Samples uniformly distributed random points inside this volume.

    Args:
        *shape: How many points to sample per individual geometry.

    Returns:
        `Tensor` containing all dimensions from `Geometry.shape`, `shape` as well as a `channel` dimension `vector` matching the dimensionality of this `Geometry`.
    """
    raise NotImplementedError(self.__class__)
def scaled(self, factor: float) ‑> phi.geom._geom.Geometry

Scales each individual geometry by factor. The individual center points act as pivots for the operation.

Args

factor: Returns:

Expand source code
def scaled(self, factor: float or Tensor) -> 'Geometry':
    """
    Scales each individual geometry by `factor`.
    The individual `center` points act as pivots for the operation.

    Args:
        factor:

    Returns:

    """
    raise NotImplementedError(self.__class__)
def shallow_equals(self, other)

Quick equality check. May return False even if other == self. However, if True is returned, the geometries are guaranteed to be equal.

The shallow_equals() check does not compare all tensor elements but merely checks whether the same tensors are referenced.

Expand source code
def shallow_equals(self, other):
    """
    Quick equality check.
    May return `False` even if `other == self`.
    However, if `True` is returned, the geometries are guaranteed to be equal.

    The `shallow_equals()` check does not compare all tensor elements but merely checks whether the same tensors are referenced.
    """
    if self is other:
        return True
    if not isinstance(other, type(self)):
        return False
    if self.shape != other.shape:
        return False
    c1 = {a: getattr(self, a) for a in variable_attributes(self)}
    c2 = {a: getattr(other, a) for a in variable_attributes(self)}
    for c in c1.keys():
        if c1[c] is not c2[c]:
            return False
    return True
def shifted(self, delta: phi.math._tensors.Tensor) ‑> phi.geom._geom.Geometry

Returns a translated version of this geometry.

See Also: Geometry.at().

Args

delta
direction vector
delta
Tensor:

Returns

Geometry
shifted geometry
Expand source code
def shifted(self, delta: Tensor) -> 'Geometry':
    """
    Returns a translated version of this geometry.

    See Also:
        `Geometry.at()`.

    Args:
      delta: direction vector
      delta: Tensor:

    Returns:
      Geometry: shifted geometry

    """
    raise NotImplementedError(self.__class__)
def unstack(self, dimension: str) ‑> tuple

Unstacks this Geometry along the given dimension. The shapes of the returned geometries are reduced by dimension.

Args

dimension
dimension along which to unstack

Returns

geometries
tuple of length equal to geometry.shape.get_size(dimension)
Expand source code
def unstack(self, dimension: str) -> tuple:
    """
    Unstacks this Geometry along the given dimension.
    The shapes of the returned geometries are reduced by `dimension`.

    Args:
        dimension: dimension along which to unstack

    Returns:
        geometries: tuple of length equal to `geometry.shape.get_size(dimension)`
    """
    attrs = {a: getattr(self, a) for a in variable_attributes(self)}
    return tuple([copy_with(self, **{a: v[{dimension: i}] for a, v in attrs.items() if dimension in v.shape}) for i in range(self.shape.get_size(dimension))])
class GridCell (resolution: phi.math._shape.Shape, bounds: phi.geom._box.BaseBox)

An instance of GridCell represents all cells of a regular grid as a batch of boxes.

Expand source code
class GridCell(BaseBox):
    """
    An instance of GridCell represents all cells of a regular grid as a batch of boxes.
    """

    def __init__(self, resolution: math.Shape, bounds: BaseBox):
        assert resolution.spatial_rank == resolution.rank, f"resolution must be purely spatial but got {resolution}"
        assert resolution.spatial_rank == bounds.spatial_rank, f"bounds must match dimensions of resolution but got {bounds} for resolution {resolution}"
        self._resolution = resolution
        self._bounds = bounds
        self._shape = resolution & bounds.shape.non_spatial

    @property
    def resolution(self):
        return self._resolution

    @property
    def bounds(self):
        return self._bounds

    @property
    def spatial_rank(self) -> int:
        return self._resolution.spatial_rank

    @property
    def center(self):
        local_coords = math.meshgrid(**{dim: math.linspace(0.5 / size, 1 - 0.5 / size, size) for dim, size in zip(self.resolution.names, self.resolution.sizes)})
        points = self.bounds.local_to_global(local_coords)
        return points

    @property
    def grid_size(self):
        return self._bounds.size

    @property
    def size(self):
        return self.bounds.size / math.wrap(self.resolution.sizes)

    @property
    def lower(self):
        return self.center - self.half_size

    @property
    def upper(self):
        return self.center + self.half_size

    @property
    def half_size(self):
        return self.bounds.size / self.resolution.sizes / 2

    def __getitem__(self, item: dict):
        bounds = self._bounds
        dx = self.size
        gather_dict = {}
        for dim, selection in item.items():
            if dim in self._resolution:
                if isinstance(selection, int):
                    start = selection
                    stop = selection + 1
                elif isinstance(selection, slice):
                    start = selection.start or 0
                    stop = selection.stop or self.resolution.get_size(dim)
                    if stop < 0:
                        stop += self.resolution.get_size(dim)
                    assert selection.step is None or selection.step == 1
                else:
                    raise ValueError(f"Illegal selection: {item}")
                dim_mask = math.wrap(self.resolution.mask(dim))
                lower = bounds.lower + start * dim_mask * dx
                upper = bounds.upper + (stop - self.resolution.get_size(dim)) * dim_mask * dx
                bounds = Box(lower, upper)
                gather_dict[dim] = slice(start, stop)
        resolution = self._resolution.after_gather(gather_dict)
        return GridCell(resolution, bounds)

    def list_cells(self, dim_name):
        center = math.pack_dims(self.center, self._shape.spatial.names, dim_name)
        return Cuboid(center, self.half_size)

    def stagger(self, dim: str, lower: bool, upper: bool):
        dim_mask = np.array(self.resolution.mask(dim))
        unit = self.bounds.size / self.resolution * dim_mask
        bounds = Box(self.bounds.lower + unit * (-0.5 if lower else 0.5), self.bounds.upper + unit * (0.5 if upper else -0.5))
        ext_res = self.resolution.sizes + dim_mask * (int(lower) + int(upper) - 1)
        return GridCell(self.resolution.with_sizes(ext_res), bounds)

    # def face_centers(self, staggered_name='staggered'):
    #     face_centers = [self.extend_symmetric(dim).center for dim in self.shape.spatial.names]
    #     return math.channel_stack(face_centers, staggered_name)

    @property
    def shape(self):
        return self._shape

    def shifted(self, delta: Tensor, **delta_by_dim) -> BaseBox:
        # delta += math.padded_stack()
        if delta.shape.spatial_rank == 0:
            return GridCell(self.resolution, self.bounds.shifted(delta))
        else:
            center = self.center + delta
            return Cuboid(center, self.half_size)

    def rotated(self, angle) -> Geometry:
        raise NotImplementedError()

    def unstack(self, dimension):
        raise NotImplementedError()

    def __eq__(self, other):
        return isinstance(other, GridCell) and self._bounds == other._bounds and self._resolution == other._resolution

    def shallow_equals(self, other):
        return self == other

    def __hash__(self):
        return hash(self._resolution) + hash(self._bounds)

    def __repr__(self):
        return f"{self._resolution}, bounds={self._bounds}"

    def __variable_attrs__(self):
        return '_center', '_half_size'

    def __with_attrs__(self, **attrs):
        return copy_with(self.center_representation(), **attrs)

    @property
    def _center(self):
        return self.center

    @property
    def _half_size(self):
        return self.half_size

Ancestors

  • phi.geom._box.BaseBox
  • phi.geom._geom.Geometry

Instance variables

var bounds
Expand source code
@property
def bounds(self):
    return self._bounds
var center

Center location in single channel dimension, ordered according to GLOBAL_AXIS_ORDER

Expand source code
@property
def center(self):
    local_coords = math.meshgrid(**{dim: math.linspace(0.5 / size, 1 - 0.5 / size, size) for dim, size in zip(self.resolution.names, self.resolution.sizes)})
    points = self.bounds.local_to_global(local_coords)
    return points
var grid_size
Expand source code
@property
def grid_size(self):
    return self._bounds.size
var half_size
Expand source code
@property
def half_size(self):
    return self.bounds.size / self.resolution.sizes / 2
var lower
Expand source code
@property
def lower(self):
    return self.center - self.half_size
var resolution
Expand source code
@property
def resolution(self):
    return self._resolution
var shape

Specifies the number of copies of the geometry as batch and spatial dimensions.

Expand source code
@property
def shape(self):
    return self._shape
var size
Expand source code
@property
def size(self):
    return self.bounds.size / math.wrap(self.resolution.sizes)
var spatial_rank : int

Number of spatial dimensions of the geometry, 1 = 1D, 2 = 2D, 3 = 3D, etc.

Expand source code
@property
def spatial_rank(self) -> int:
    return self._resolution.spatial_rank
var upper
Expand source code
@property
def upper(self):
    return self.center + self.half_size

Methods

def list_cells(self, dim_name)
Expand source code
def list_cells(self, dim_name):
    center = math.pack_dims(self.center, self._shape.spatial.names, dim_name)
    return Cuboid(center, self.half_size)
def rotated(self, angle) ‑> phi.geom._geom.Geometry

Returns a rotated version of this geometry. The geometry is rotated about its center point.

Args

angle
scalar (2d) or vector (3D+) representing delta angle

Returns

Rotated Geometry

Expand source code
def rotated(self, angle) -> Geometry:
    raise NotImplementedError()
def shallow_equals(self, other)

Quick equality check. May return False even if other == self. However, if True is returned, the geometries are guaranteed to be equal.

The shallow_equals() check does not compare all tensor elements but merely checks whether the same tensors are referenced.

Expand source code
def shallow_equals(self, other):
    return self == other
def shifted(self, delta: phi.math._tensors.Tensor, **delta_by_dim) ‑> phi.geom._box.BaseBox

Returns a translated version of this geometry.

See Also: Geometry.at().

Args

delta
direction vector
delta
Tensor:

Returns

Geometry
shifted geometry
Expand source code
def shifted(self, delta: Tensor, **delta_by_dim) -> BaseBox:
    # delta += math.padded_stack()
    if delta.shape.spatial_rank == 0:
        return GridCell(self.resolution, self.bounds.shifted(delta))
    else:
        center = self.center + delta
        return Cuboid(center, self.half_size)
def stagger(self, dim: str, lower: bool, upper: bool)
Expand source code
def stagger(self, dim: str, lower: bool, upper: bool):
    dim_mask = np.array(self.resolution.mask(dim))
    unit = self.bounds.size / self.resolution * dim_mask
    bounds = Box(self.bounds.lower + unit * (-0.5 if lower else 0.5), self.bounds.upper + unit * (0.5 if upper else -0.5))
    ext_res = self.resolution.sizes + dim_mask * (int(lower) + int(upper) - 1)
    return GridCell(self.resolution.with_sizes(ext_res), bounds)
def unstack(self, dimension)

Unstacks this Geometry along the given dimension. The shapes of the returned geometries are reduced by dimension.

Args

dimension
dimension along which to unstack

Returns

geometries
tuple of length equal to geometry.shape.get_size(dimension)
Expand source code
def unstack(self, dimension):
    raise NotImplementedError()
class Point (location: phi.math._tensors.Tensor)

Points have zero volume and are determined by a single location. An instance of Point represents a single n-dimensional point or a batch of points.

Expand source code
class Point(Geometry):
    """
    Points have zero volume and are determined by a single location.
    An instance of `Point` represents a single n-dimensional point or a batch of points.
    """

    def __init__(self, location: math.Tensor):
        self._location = location

    @property
    def center(self) -> Tensor:
        return self._location

    @property
    def shape(self) -> Shape:
        return self._location.shape.without('vector')

    def unstack(self, dimension: str) -> tuple:
        return tuple(Point(loc) for loc in self._location.unstack(dimension))

    def lies_inside(self, location: Tensor) -> Tensor:
        return math.wrap(False)

    def approximate_signed_distance(self, location: Tensor) -> Tensor:
        return math.vec_abs(location - self._location)

    def push(self, positions: Tensor, outward: bool = True, shift_amount: float = 0) -> Tensor:
        return positions

    def bounding_radius(self) -> Tensor:
        return math.zeros()

    def bounding_half_extent(self) -> Tensor:
        return math.zeros()

    def shifted(self, delta: Tensor) -> 'Geometry':
        return Point(self._location + delta)

    def rotated(self, angle) -> 'Geometry':
        return self

    def __hash__(self):
        return hash(self._location)

    def __variable_attrs__(self):
        return '_location',

Ancestors

  • phi.geom._geom.Geometry

Instance variables

var center : phi.math._tensors.Tensor

Center location in single channel dimension, ordered according to GLOBAL_AXIS_ORDER

Expand source code
@property
def center(self) -> Tensor:
    return self._location
var shape : phi.math._shape.Shape

Specifies the number of copies of the geometry as batch and spatial dimensions.

Expand source code
@property
def shape(self) -> Shape:
    return self._location.shape.without('vector')

Methods

def approximate_signed_distance(self, location: phi.math._tensors.Tensor) ‑> phi.math._tensors.Tensor

Computes the approximate distance from location to the surface of the geometry. Locations outside return positive values, inside negative values and zero exactly at the boundary.

The exact distance metric used depends on the geometry. The approximation holds close to the surface and the distance grows to infinity as the location is moved infinitely far from the geometry. The distance metric is differentiable and its gradients are bounded at every point in space.

When dealing with unions or collections of geometries (instance dimensions), the shortest distance to any instance is returned. This also holds for negative distances.

Args

location
float tensor of shape (batch_size, …, rank)
location
Tensor:

Returns

float tensor of shape (*location.shape[:-1], 1).

Expand source code
def approximate_signed_distance(self, location: Tensor) -> Tensor:
    return math.vec_abs(location - self._location)
def bounding_half_extent(self) ‑> phi.math._tensors.Tensor

The bounding half-extent sets a limit on the outer-most point for each coordinate axis. Each component is non-negative.

Let the bounding half-extent have value e in dimension d (extent[...,d] = e). Then, no point of the geometry lies further away from its center point than e along d (in both axis directions).

:return: float vector

Args:

Returns:

Expand source code
def bounding_half_extent(self) -> Tensor:
    return math.zeros()
def bounding_radius(self) ‑> phi.math._tensors.Tensor

Returns the radius of a Sphere object that fully encloses this geometry. The sphere is centered at the center of this geometry.

:return: radius of type float

Args:

Returns:

Expand source code
def bounding_radius(self) -> Tensor:
    return math.zeros()
def lies_inside(self, location: phi.math._tensors.Tensor) ‑> phi.math._tensors.Tensor

Tests whether the given location lies inside or outside of the geometry. Locations on the surface count as inside.

When dealing with unions or collections of geometries (instance dimensions), a point lies inside the geometry if it lies inside any instance.

Args

location
float tensor of shape (batch_size, …, rank)

Returns

bool tensor of shape (*location.shape[:-1], 1).

Expand source code
def lies_inside(self, location: Tensor) -> Tensor:
    return math.wrap(False)
def push(self, positions: phi.math._tensors.Tensor, outward: bool = True, shift_amount: float = 0) ‑> phi.math._tensors.Tensor

Shifts positions either into or out of geometry.

Args

positions
Tensor holding positions to shift
outward
Flag for indicating inward (False) or outward (True) shift
shift_amount
Minimum distance between positions and box boundaries after shifting

Returns

Tensor holding shifted positions

Expand source code
def push(self, positions: Tensor, outward: bool = True, shift_amount: float = 0) -> Tensor:
    return positions
def rotated(self, angle) ‑> phi.geom._geom.Geometry

Returns a rotated version of this geometry. The geometry is rotated about its center point.

Args

angle
scalar (2d) or vector (3D+) representing delta angle

Returns

Rotated Geometry

Expand source code
def rotated(self, angle) -> 'Geometry':
    return self
def shifted(self, delta: phi.math._tensors.Tensor) ‑> phi.geom._geom.Geometry

Returns a translated version of this geometry.

See Also: Geometry.at().

Args

delta
direction vector
delta
Tensor:

Returns

Geometry
shifted geometry
Expand source code
def shifted(self, delta: Tensor) -> 'Geometry':
    return Point(self._location + delta)
def unstack(self, dimension: str) ‑> tuple

Unstacks this Geometry along the given dimension. The shapes of the returned geometries are reduced by dimension.

Args

dimension
dimension along which to unstack

Returns

geometries
tuple of length equal to geometry.shape.get_size(dimension)
Expand source code
def unstack(self, dimension: str) -> tuple:
    return tuple(Point(loc) for loc in self._location.unstack(dimension))
class Sphere (center: phi.math._tensors.Tensor = None, radius: float = None, **center_: float)

N-dimensional sphere. Defined through center position and radius.

Args

center
Sphere center as Tensor with vector dimension. The spatial dimension order should be specified in the vector dimension via item names.
radius
Sphere radius as float or Tensor
**center_
Specifies center when the center argument is not given. Center position by dimension, e.g. x=0.5, y=0.2.
Expand source code
class Sphere(Geometry):
    """
    N-dimensional sphere.
    Defined through center position and radius.
    """

    def __init__(self,
                 center: Tensor = None,
                 radius: float or Tensor = None,
                 **center_: float or Tensor):
        """
        Args:
            center: Sphere center as `Tensor` with `vector` dimension.
                The spatial dimension order should be specified in the `vector` dimension via item names.
            radius: Sphere radius as `float` or `Tensor`
            **center_: Specifies center when the `center` argument is not given. Center position by dimension, e.g. `x=0.5, y=0.2`.
        """
        if center is not None:
            if not isinstance(center, Tensor):
                warnings.warn(f"constructor Sphere({type(center)}) is deprecated. Use Sphere(Tensor) or Sphere(**center_) instead.", DeprecationWarning)
                self._center = wrap(center)
            else:
                self._center = center
            assert 'vector' in self._center.shape, f"Sphere.center must have a 'vector' dimension. Use the syntax x=0.5."
        else:
            self._center = math.wrap(tuple(center_.values()), math.channel(vector=tuple(center_.keys())))
            # self._center = wrap(math.spatial(**center_), math.channel('vector'))
        assert radius is not None, "radius must be specified."
        self._radius = wrap(radius)

    @property
    def shape(self):
        if self._center is None or self._radius is None:
            return None
        return (self._center.shape & self._radius.shape).without('vector')

    @property
    def radius(self):
        return self._radius

    @property
    def center(self):
        return self._center

    @property
    def volume(self) -> math.Tensor:
        if self.spatial_rank == 1:
            return 2 * self._radius
        elif self.spatial_rank == 2:
            return math.PI * self._radius ** 2
        elif self.spatial_rank == 3:
            return 4 / 3 * math.PI * self._radius ** 3
        else:
            raise NotImplementedError()
            # n = self.spatial_rank
            # return math.pi ** (n // 2) / math.faculty(math.ceil(n / 2)) * self._radius ** n

    @property
    def shape_type(self) -> Tensor:
        return math.tensor('S')

    def lies_inside(self, location):
        distance_squared = math.sum((location - self.center) ** 2, dim='vector')
        return math.any(distance_squared <= self.radius ** 2, self.shape.instance)  # union for instance dimensions

    def approximate_signed_distance(self, location):
        """
        Computes the exact distance from location to the closest point on the sphere.
        Very close to the sphere center, the distance takes a constant value.

        Args:
          location: float tensor of shape (batch_size, ..., rank)

        Returns:
          float tensor of shape (*location.shape[:-1], 1).

        """
        distance_squared = math.vec_squared(location - self.center)
        distance_squared = math.maximum(distance_squared, self.radius * 1e-2)  # Prevent infinite spatial_gradient at sphere center
        distance = math.sqrt(distance_squared)
        return math.min(distance - self.radius, self.shape.instance)  # union for instance dimensions

    def sample_uniform(self, *shape: math.Shape):
        raise NotImplementedError('Not yet implemented')  # ToDo

    def bounding_radius(self):
        return self.radius

    def bounding_half_extent(self):
        return self.radius

    def shifted(self, delta):
        return Sphere(self._center + delta, self._radius)

    def rotated(self, angle):
        return self

    def scaled(self, factor: float or Tensor) -> 'Geometry':
        return Sphere(self.center, self.radius * factor)

    def __variable_attrs__(self):
        return '_radius', '_center'

    def unstack(self, dimension: str) -> tuple:
        center = self.center.dimension(dimension).unstack(self.shape.get_size(dimension))
        radius = self.radius.dimension(dimension).unstack(self.shape.get_size(dimension))
        return tuple([Sphere(c, r) for c, r in zip(center, radius)])

    def push(self, positions: Tensor, outward: bool = True, shift_amount: float = 0) -> Tensor:
        raise NotImplementedError()

    def __hash__(self):
        return hash(self._center) + hash(self._radius)

Ancestors

  • phi.geom._geom.Geometry

Instance variables

var center

Center location in single channel dimension, ordered according to GLOBAL_AXIS_ORDER

Expand source code
@property
def center(self):
    return self._center
var radius
Expand source code
@property
def radius(self):
    return self._radius
var shape

Specifies the number of copies of the geometry as batch and spatial dimensions.

Expand source code
@property
def shape(self):
    if self._center is None or self._radius is None:
        return None
    return (self._center.shape & self._radius.shape).without('vector')
var shape_type : phi.math._tensors.Tensor

Returns the type (or types) of this geometry as a string Tensor Boxes return 'B' and spheres return 'S'. Returns '?' for unknown types, e.g. a union over multiple types. Custom types can return their own identifiers.

Returns

String Tensor

Expand source code
@property
def shape_type(self) -> Tensor:
    return math.tensor('S')
var volume : phi.math._tensors.Tensor

Volume of the geometry as Tensor. The result retains all batch dimensions while instance dimensions are summed over.

Expand source code
@property
def volume(self) -> math.Tensor:
    if self.spatial_rank == 1:
        return 2 * self._radius
    elif self.spatial_rank == 2:
        return math.PI * self._radius ** 2
    elif self.spatial_rank == 3:
        return 4 / 3 * math.PI * self._radius ** 3
    else:
        raise NotImplementedError()
        # n = self.spatial_rank
        # return math.pi ** (n // 2) / math.faculty(math.ceil(n / 2)) * self._radius ** n

Methods

def approximate_signed_distance(self, location)

Computes the exact distance from location to the closest point on the sphere. Very close to the sphere center, the distance takes a constant value.

Args

location
float tensor of shape (batch_size, …, rank)

Returns

float tensor of shape (*location.shape[:-1], 1).

Expand source code
def approximate_signed_distance(self, location):
    """
    Computes the exact distance from location to the closest point on the sphere.
    Very close to the sphere center, the distance takes a constant value.

    Args:
      location: float tensor of shape (batch_size, ..., rank)

    Returns:
      float tensor of shape (*location.shape[:-1], 1).

    """
    distance_squared = math.vec_squared(location - self.center)
    distance_squared = math.maximum(distance_squared, self.radius * 1e-2)  # Prevent infinite spatial_gradient at sphere center
    distance = math.sqrt(distance_squared)
    return math.min(distance - self.radius, self.shape.instance)  # union for instance dimensions
def bounding_half_extent(self)

The bounding half-extent sets a limit on the outer-most point for each coordinate axis. Each component is non-negative.

Let the bounding half-extent have value e in dimension d (extent[...,d] = e). Then, no point of the geometry lies further away from its center point than e along d (in both axis directions).

:return: float vector

Args:

Returns:

Expand source code
def bounding_half_extent(self):
    return self.radius
def bounding_radius(self)

Returns the radius of a Sphere object that fully encloses this geometry. The sphere is centered at the center of this geometry.

:return: radius of type float

Args:

Returns:

Expand source code
def bounding_radius(self):
    return self.radius
def lies_inside(self, location)

Tests whether the given location lies inside or outside of the geometry. Locations on the surface count as inside.

When dealing with unions or collections of geometries (instance dimensions), a point lies inside the geometry if it lies inside any instance.

Args

location
float tensor of shape (batch_size, …, rank)

Returns

bool tensor of shape (*location.shape[:-1], 1).

Expand source code
def lies_inside(self, location):
    distance_squared = math.sum((location - self.center) ** 2, dim='vector')
    return math.any(distance_squared <= self.radius ** 2, self.shape.instance)  # union for instance dimensions
def push(self, positions: phi.math._tensors.Tensor, outward: bool = True, shift_amount: float = 0) ‑> phi.math._tensors.Tensor

Shifts positions either into or out of geometry.

Args

positions
Tensor holding positions to shift
outward
Flag for indicating inward (False) or outward (True) shift
shift_amount
Minimum distance between positions and box boundaries after shifting

Returns

Tensor holding shifted positions

Expand source code
def push(self, positions: Tensor, outward: bool = True, shift_amount: float = 0) -> Tensor:
    raise NotImplementedError()
def rotated(self, angle)

Returns a rotated version of this geometry. The geometry is rotated about its center point.

Args

angle
scalar (2d) or vector (3D+) representing delta angle

Returns

Rotated Geometry

Expand source code
def rotated(self, angle):
    return self
def sample_uniform(self, *shape: phi.math._shape.Shape)

Samples uniformly distributed random points inside this volume.

Args

*shape
How many points to sample per individual geometry.

Returns

Tensor containing all dimensions from Geometry.shape, shape as well as a channel dimension vector matching the dimensionality of this Geometry.

Expand source code
def sample_uniform(self, *shape: math.Shape):
    raise NotImplementedError('Not yet implemented')  # ToDo
def scaled(self, factor: float) ‑> phi.geom._geom.Geometry

Scales each individual geometry by factor. The individual center points act as pivots for the operation.

Args

factor: Returns:

Expand source code
def scaled(self, factor: float or Tensor) -> 'Geometry':
    return Sphere(self.center, self.radius * factor)
def shifted(self, delta)

Returns a translated version of this geometry.

See Also: Geometry.at().

Args

delta
direction vector
delta
Tensor:

Returns

Geometry
shifted geometry
Expand source code
def shifted(self, delta):
    return Sphere(self._center + delta, self._radius)
def unstack(self, dimension: str) ‑> tuple

Unstacks this Geometry along the given dimension. The shapes of the returned geometries are reduced by dimension.

Args

dimension
dimension along which to unstack

Returns

geometries
tuple of length equal to geometry.shape.get_size(dimension)
Expand source code
def unstack(self, dimension: str) -> tuple:
    center = self.center.dimension(dimension).unstack(self.shape.get_size(dimension))
    radius = self.radius.dimension(dimension).unstack(self.shape.get_size(dimension))
    return tuple([Sphere(c, r) for c, r in zip(center, radius)])