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
objectExpand 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 rankgeometry.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
-dimensionalSphere
inn+1
dimensions.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
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 normalsExpand 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
containingdims
dims
- Dimensions to pack into a single dimension.
packed_dim
- The new dimension.
pos
- (Optional) Position of the new dimension.
Returns
Geometry
withpacked_dim
instead ofdims
.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
alongdim
. The size ofdim
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 points
, the distance ismax(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 dimensiond
(extent[...,d] = e
). Then, no point of the geometry lies further away from its center point thane
alongd
(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 fromGeometry.shape
,shape
as well as achannel
dimensionvector
matching the dimensionality of thisGeometry
.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 individualcenter
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 askwargs
.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 ofother_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()
andbounding_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 withself
.balance
- Mid-level between 0 and 1, default 0.5.
This value is returned when exactly half of
other_geometry
lies insideself
.0.5 < balance <= 1
makesself
seem larger while0 <= balance < 0.5
makesself
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 atcenter
. This is equal to callingself @ center
.See Also:
Geometry.shifted()
.Args
center
- New center as
Tensor
.
Returns
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 dimensiond
(extent[...,d] = e
). Then, no point of the geometry lies further away from its center point thane
alongd
(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 fromGeometry.shape
,shape
as well as achannel
dimensionvector
matching the dimensionality of thisGeometry
.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 individualcenter
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 ifother == self
. However, ifTrue
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 ifother == self
. However, ifTrue
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 dimensiond
(extent[...,d] = e
). Then, no point of the geometry lies further away from its center point thane
alongd
(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
withvector
dimension. The spatial dimension order should be specified in thevector
dimension via item names. radius
- Sphere radius as
float
orTensor
**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 dimensiond
(extent[...,d] = e
). Then, no point of the geometry lies further away from its center point thane
alongd
(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 fromGeometry.shape
,shape
as well as achannel
dimensionvector
matching the dimensionality of thisGeometry
.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 individualcenter
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)])