Module phiml.math.magic
Magic methods allow custom classes to be compatible with various functions defined in phiml.math
, analogous to how implementing __hash__
allows objects to be used with hash()
.
The magic methods are grouped into purely declarative classes (interfaces) by what functionality they provide.
Shaped
objects have aShape
.Sliceable
objects can be sliced along dimensions.Shapable
objects can additionally be reshaped.PhiTreeNode
objects can be disassembled into tensors.
All of these magic classes declared here define a custom instance checks and should not be used as superclasses.
An object implements one of the types defined here by implementing one or more of the related magic methods.
Instance checks can be performed via isinstance(obj, <MagicClass>)
.
This is analogous to interfaces defined in the built-in collections
package, such as Sized, Iterable, Hashable, Callable
.
To check whether len(obj)
can be performed, you check isinstance(obj, Sized)
.
Functions
def slicing_dict(obj, item, existing_only=True) ‑> dict
-
Creates a slicing
dict
fromitem
whereitem
is an arbitrary value passed to__getitem__()
.Sliceable
objects should call this function inside__getitem__()
, passingself
anditem
.Args
obj
- Object to be sliced.
item
- Slices.
existing_only
- Whether to include dims in the slicing dict which are present on
obj
.
Returns
dict
mapping dimension names to slices.
Classes
class BoundDim (obj, name: str)
-
Represents a dimension of a sliceable object to make slicing, renaming and retyping prettier. Any instance of
BoundDim
is bound to the sliceable object and is immutable. All operations upon the dim affect return a copy of the sliceable object.BoundDim
objects are generally created by and for objects that areSliceable
(and therefore alsoShaped
). These objects should declare the following method to support the.dim
syntax:from phiml.math.magic import BoundDim class MyClass: def __getattr__(self, name: str) -> BoundDim: return BoundDim(self, name)
Usage
obj.dim.size
returns the dimension size.obj.dim.item_names
returns the dimension item names.obj.dim.exists
checks whether a dimension is listed in the shape of the bound object.obj.dim[0]
picks the first element alongdim
. The shape of the result will not containdim
.obj.dim[1:-1]
discards the first and last element alongdim
.obj.dim.rename('new_name')
renamesdim
tonew_name
.obj.dim.as_channel()
changes the type ofdim
to channel.obj.dim.unstack()
un-stacks the bound value alongdim
.for slice in obj.dim
loops over all slices ofdim
.
Multiple dimensions can also be chained together using
obj.dim1.dim2…
. This supports the following operations:obj.dim1.dim2…volume
returns the product of the sizesobj.dim1.dim2...[0, -1]
takes the first element alongdim1
and the last element alongdim2
obj.dim1.dim2…pack(new_dim)
packs the dimensions into a new dimension with size equal to their volumeobj.dim1.dim2…unstack()
un-stacksobj
along multiple dimensionsobj.dim1.dim2…retype(type)
Changes the type of all selected dimensionsfor slice in obj.dim1.dim2…
loops over all slices as if unstacking first
Args
obj
Sliceable
bound object.name
- Dimension name as
str
.
Expand source code
class BoundDim: """ Represents a dimension of a sliceable object to make slicing, renaming and retyping prettier. Any instance of `BoundDim` is bound to the sliceable object and is immutable. All operations upon the dim affect return a copy of the sliceable object. `BoundDim` objects are generally created by and for objects that are `Sliceable` (and therefore also `Shaped`). These objects should declare the following method to support the `.dim` syntax: ```python from phiml.math.magic import BoundDim class MyClass: def __getattr__(self, name: str) -> BoundDim: return BoundDim(self, name) ``` **Usage** * `obj.dim.size` returns the dimension size. * `obj.dim.item_names` returns the dimension item names. * `obj.dim.exists` checks whether a dimension is listed in the shape of the bound object. * `obj.dim[0]` picks the first element along `dim`. The shape of the result will not contain `dim`. * `obj.dim[1:-1]` discards the first and last element along `dim`. * `obj.dim.rename('new_name')` renames `dim` to `new_name`. * `obj.dim.as_channel()` changes the type of `dim` to *channel*. * `obj.dim.unstack()` un-stacks the bound value along `dim`. * `for slice in obj.dim` loops over all slices of `dim`. Multiple dimensions can also be chained together using `obj.dim1.dim2...`. This supports the following operations: * `obj.dim1.dim2...volume` returns the product of the sizes * `obj.dim1.dim2...[0, -1]` takes the first element along `dim1` and the last element along `dim2` * `obj.dim1.dim2...pack(new_dim)` packs the dimensions into a new dimension with size equal to their volume * `obj.dim1.dim2...unstack()` un-stacks `obj` along multiple dimensions * `obj.dim1.dim2...retype(type)` Changes the type of all selected dimensions * `for slice in obj.dim1.dim2...` loops over all slices as if unstacking first """ def __init__(self, obj, name: str): """ Args: obj: `Sliceable` bound object. name: Dimension name as `str`. """ if name.startswith('_') or ',' in name or ' ' in name: raise AttributeError(f"'{type(obj)}' object has no attribute '{name}'") if name == 'shape': raise AttributeError(f"Object of type {type(obj)} has no shape") if name == 'is_tensor_like': raise AttributeError(f"Trying to access {type(obj)}.is_tensor_like which does not exist. This is likely a TensorFlow JIT compile check. This name is reserved.") assert isinstance(obj, Sliceable) and isinstance(obj, Shaped), f"Cannot create BoundDim for {type(obj).__name__}. Objects must be Sliceable and Shaped, see https://tum-pbs.github.io/PhiML/phiml/math/magic.html" self.obj = obj self.name = name @property def dual(self): return BoundDim(self.obj, '~' + self.name) @property def exists(self): """ Whether the dimension is listed in the `Shape` of the object. """ return self.name in shape(self.obj) def __repr__(self): if self.name not in shape(self.obj): return f"{type(self.obj).__name__}.{self.name} (non-existent)" items = self.item_names if items is not None: if len(items) <= 4: size_repr = ",".join(items) else: size_repr = f"{self.size}:{items[0]}..{items[-1]}" else: size_repr = self.size from ._shape import SUPERSCRIPT return f"{type(self.obj).__name__}.{self.name}{SUPERSCRIPT.get(self.type.__name__, '?')}={size_repr}" @property def size(self): """ Length of this dimension as listed in the `Shape` of the bound object. """ return shape(self.obj).get_size(self.name) if self.exists else None volume = size @property def size_or_1(self): return shape(self.obj).get_size(self.name) if self.exists else 1 @property def type(self) -> Callable: """ The dimension type of this bound dimension. Must be one of `batch`, `spatial`, `instance`, `channel`. Returns: """ return shape(self.obj).get_dim_type(self.name) @property def item_names(self): return shape(self.obj).get_item_names(self.name) def __getitem__(self, item): return self.obj[{self.name: item}] def __setitem__(self, key, value): self.obj[{self.name: key}] = value def __getattr__(self, item): return _BoundDims(self.obj, (self.name, item)) def keys(self): """ Allows unstacking with item names as `dict(**obj.dim)`. Returns: Sequence of item names or indices. """ if not self.exists: raise SyntaxError(f"Cannot get item names of nonexistent dim '{self.name}'. shape={shape(self.obj)}") if self.item_names is not None: return self.item_names else: return range(self.size) def unstack(self, size: Union[int, None] = None) -> tuple: """ Lists the slices along this dimension as a `tuple`. Args: size: (optional) If given as `int`, this dimension can be unstacked even if it is not present on the object. In that case, `size` copies of the object are returned. Returns: `tuple` of `Sliceable` """ from ._magic_ops import unstack if size is None: return unstack(self.obj, self.name) else: if self.exists: unstacked = unstack(self.obj, self.name) assert len(unstacked) == size, f"Size of dimension {self.name} does not match {size}." return unstacked else: return (self.obj,) * size def __iter__(self): """ Iterate over slices along this dim """ if self.exists: return iter(self.unstack()) else: return iter([self.obj]) def __call__(self, *args, **kwargs): raise TypeError(f"Method {type(self.obj).__name__}.{self.name}() does not exist.") def rename(self, name: str, **kwargs): """ Returns a shallow copy of the `Tensor` where this dimension has the specified name. See Also: `phiml.math.rename_dims()` """ if not self.exists: return self.obj from ._magic_ops import rename_dims return rename_dims(self.obj, self.name, name, **kwargs) def retype(self, dim_type: Callable, **kwargs): """ Returns a shallow copy of the `Tensor` where this dimension has the specified type. See Also: `phiml.math.rename_dims()` """ new_dim = dim_type(**{self.name: self.item_names or self.size}) from ._magic_ops import rename_dims return rename_dims(self.obj, self.name, new_dim, **kwargs) as_type = retype def as_batch(self, name: str = None): """ Returns a shallow copy of the `Tensor` where the type of this dimension is *batch*. """ if not self.exists: return self.obj return self.retype(batch) if name is None else self.replace(batch(**{name: self.item_names or self.size})) def as_spatial(self, name: str = None): """ Returns a shallow copy of the `Tensor` where the type of this dimension is *spatial*. """ if not self.exists: return self.obj return self.retype(spatial) if name is None else self.replace(spatial(**{name: self.item_names or self.size})) def as_channel(self, name: str = None): """ Returns a shallow copy of the `Tensor` where the type of this dimension is *channel*. """ if not self.exists: return self.obj return self.retype(channel) if name is None else self.replace(channel(**{name: self.item_names or self.size})) def as_instance(self, name: str = None): """ Returns a shallow copy of the `Tensor` where the type of this dimension is *instance*. """ if not self.exists: return self.obj return self.retype(instance) if name is None else self.replace(instance(**{name: self.item_names or self.size})) def as_dual(self, name: str = None): """ Returns a shallow copy of the `Tensor` where the type of this dimension is *instance*. """ if not self.exists: return self.obj return self.retype(dual) if name is None else self.replace(dual(**{name: self.item_names or self.size})) @property def T(self): return self.retype(dual) if self.type != dual else self.retype(channel) def replace(self, dim: Shape, **kwargs): """ Returns a shallow copy of the `Tensor` where this dimension has been replaced by `dim`. See Also: `phiml.math.rename_dims()` """ from ._magic_ops import rename_dims return rename_dims(self.obj, self.name, dim, **kwargs) def unpack(self, *dims: Shape, **kwargs): """ Returns a shallow copy of the `Tensor` where this dimension has been unpacked into `dims`. See Also: `phiml.math.unpack_dim()` """ from ._magic_ops import unpack_dim return unpack_dim(self.obj, self.name, *dims, **kwargs) def __bool__(self): raise SyntaxError(f"{self} cannot be converted to bool. The property you want to access likely does not exist or raised an error when evaluated.")
Subclasses
- phiml.math._tensors.TensorDim
Instance variables
prop T
-
Expand source code
@property def T(self): return self.retype(dual) if self.type != dual else self.retype(channel)
prop dual
-
Expand source code
@property def dual(self): return BoundDim(self.obj, '~' + self.name)
prop exists
-
Whether the dimension is listed in the
Shape
of the object.Expand source code
@property def exists(self): """ Whether the dimension is listed in the `Shape` of the object. """ return self.name in shape(self.obj)
prop item_names
-
Expand source code
@property def item_names(self): return shape(self.obj).get_item_names(self.name)
prop size
-
Length of this dimension as listed in the
Shape
of the bound object.Expand source code
@property def size(self): """ Length of this dimension as listed in the `Shape` of the bound object. """ return shape(self.obj).get_size(self.name) if self.exists else None
prop size_or_1
-
Expand source code
@property def size_or_1(self): return shape(self.obj).get_size(self.name) if self.exists else 1
prop type : Callable
-
The dimension type of this bound dimension. Must be one of
batch
,spatial
,instance
,channel
.Returns:
Expand source code
@property def type(self) -> Callable: """ The dimension type of this bound dimension. Must be one of `batch`, `spatial`, `instance`, `channel`. Returns: """ return shape(self.obj).get_dim_type(self.name)
prop volume
-
Length of this dimension as listed in the
Shape
of the bound object.Expand source code
@property def size(self): """ Length of this dimension as listed in the `Shape` of the bound object. """ return shape(self.obj).get_size(self.name) if self.exists else None
Methods
def as_batch(self, name: str = None)
-
Returns a shallow copy of the
Tensor
where the type of this dimension is batch. def as_channel(self, name: str = None)
-
Returns a shallow copy of the
Tensor
where the type of this dimension is channel. def as_dual(self, name: str = None)
-
Returns a shallow copy of the
Tensor
where the type of this dimension is instance. def as_instance(self, name: str = None)
-
Returns a shallow copy of the
Tensor
where the type of this dimension is instance. def as_spatial(self, name: str = None)
-
Returns a shallow copy of the
Tensor
where the type of this dimension is spatial. def as_type(self, dim_type: Callable, **kwargs)
-
Returns a shallow copy of the
Tensor
where this dimension has the specified type.See Also:
rename_dims()
def keys(self)
-
Allows unstacking with item names as
dict(**obj.dim)
.Returns
Sequence of item names or indices.
def rename(self, name: str, **kwargs)
-
Returns a shallow copy of the
Tensor
where this dimension has the specified name.See Also:
rename_dims()
def replace(self, dim: phiml.math._shape.Shape, **kwargs)
-
Returns a shallow copy of the
Tensor
where this dimension has been replaced bydim
.See Also:
rename_dims()
def retype(self, dim_type: Callable, **kwargs)
-
Returns a shallow copy of the
Tensor
where this dimension has the specified type.See Also:
rename_dims()
def unpack(self, *dims: phiml.math._shape.Shape, **kwargs)
-
Returns a shallow copy of the
Tensor
where this dimension has been unpacked intodims
.See Also:
unpack_dim()
def unstack(self, size: Optional[int] = None) ‑> tuple
-
Lists the slices along this dimension as a
tuple
.Args
size
- (optional) If given as
int
, this dimension can be unstacked even if it is not present on the object. In that case,size
copies of the object are returned.
Returns
tuple
ofSliceable
class OtherMagicFunctions
-
Expand source code
class OtherMagicFunctions: def __cast__(self, dtype: DType): raise NotImplementedError
class PhiTreeNode
-
Φ-tree nodes can be iterated over and disassembled or flattened into elementary objects, such as tensors.
Tensor
instances as well as PyTree nodes (tuple
,list
,dict
withstr
keys) are Φ-tree nodes. All data classes are also considered PhiTreeNodes as of version 2.3.For custom classes to be considered Φ-tree nodes, they have to be a dataclass or implement one of the following magic methods:
__variable_attrs__()
__value_attrs__()
Dataclasses may also implement these functions to specify which attributes should be considered value / variable properties.
Additionally, Φ-tree nodes must override
__eq__()
to allow comparison of data-stripped (key) instances.To check whether an object is a Φ-tree node, use
isinstance(obj, PhiTreeNode)
.Usage in
phiml.math
:Φ-tree nodes can be used as keys, for example in
jit_compile()
. They are converted to keys by stripping all variable tensors and replacing them by a placeholder object. In key mode,__eq__()
compares all non-variable properties that might invalidate a trace when changed.Disassembly and assembly of Φ-tree nodes uses
replace()
which will call__with_attrs__
if implemented.Expand source code
class PhiTreeNode(metaclass=_PhiTreeNodeType): """ Φ-tree nodes can be iterated over and disassembled or flattened into elementary objects, such as tensors. `phiml.math.Tensor` instances as well as PyTree nodes (`tuple`, `list`, `dict` with `str` keys) are Φ-tree nodes. All data classes are also considered PhiTreeNodes as of version 2.3. For custom classes to be considered Φ-tree nodes, they have to be a dataclass or implement one of the following magic methods: * `__variable_attrs__()` * `__value_attrs__()` Dataclasses may also implement these functions to specify which attributes should be considered value / variable properties. Additionally, Φ-tree nodes must override `__eq__()` to allow comparison of data-stripped (key) instances. To check whether an object is a Φ-tree node, use `isinstance(obj, PhiTreeNode)`. **Usage in `phiml.math`:** Φ-tree nodes can be used as keys, for example in `jit_compile()`. They are converted to keys by stripping all variable tensors and replacing them by a placeholder object. In key mode, `__eq__()` compares all non-variable properties that might invalidate a trace when changed. Disassembly and assembly of Φ-tree nodes uses `phiml.math.copy_with` which will call `__with_attrs__` if implemented. """ def __value_attrs__(self) -> Tuple[str, ...]: """ Returns all `Tensor` or `PhiTreeNode` attribute names of `self` that should be transformed by single-operand math operations, such as `sin()`, `exp()`. Optimization functions, such as `minimize` also work on value attributes, as well as most derivative-related function, e.g. - `jacobian()` - `custom_gradient()` Dataclasses may instead declare the field `values: Tuple[str,...]` Returns: `tuple` of `str` attributes. Calling `getattr(self, attr)` must return a `Tensor` or `PhiTreeNode` for all returned attributes. """ raise NotImplementedError def __variable_attrs__(self) -> Tuple[str, ...]: """ Returns all `Tensor` or `PhiTreeNode` attribute names of `self` whose values are variable. Variables denote values that can change from one function call to the next or for which gradients can be recorded. If this method is not implemented, all attributes returned by `__value_attrs__()` are considered variable. The returned properties are used by the following functions: - `jit_compile()` - `jit_compile_linear()` - `stop_gradient()` Dataclasses may instead declare the field `variables: Tuple[str,...]` Returns: `tuple` of `str` attributes. Calling `getattr(self, attr)` must return a `Tensor` or `PhiTreeNode` for all returned attributes. """ raise NotImplementedError def __all_attrs__(self) -> Tuple[str, ...]: """ Returns all `Tensor` or `PhiTreeNode` attribute names of `self` needed to fully describe this instance. Fields which never hold `Tensor` instances (directly or indirectly) should not be included. The returned attributes are used to extract tensors for serializing and un-serializing the object. All names returned by `__variable_attrs__` and `__value_attrs__` must be included in this list. If not implemented, the union of `__variable_attrs__` and `__value_attrs__` will be used instead, and dataclasses default to all fields possibly containing data. The only dataclass fields excluded are those of type `Shape` or primitive types `str`, `int`, `float`, etc. and collections of these. Returns: `tuple` of `str` attributes. Calling `getattr(self, attr)` must return a `Tensor` or `PhiTreeNode` for all returned attributes. """ raise NotImplementedError def __with_attrs__(self, **attrs): """ Used by `phiml.math.copy_with`. Create a copy of this object which has the `Tensor` or `PhiTreeNode` attributes contained in `attrs` replaced. If this method is not implemented, tensor attributes are replaced using `setattr()`. Args: **attrs: `dict` mapping `str` attribute names to `Tensor` or `PhiTreeNode`. Returns: Altered copy of `self` """ raise NotImplementedError def __eq__(self, other): raise NotImplementedError
Methods
def __all_attrs__(self) ‑> Tuple[str, ...]
-
Returns all
Tensor
orPhiTreeNode
attribute names ofself
needed to fully describe this instance. Fields which never holdTensor
instances (directly or indirectly) should not be included.The returned attributes are used to extract tensors for serializing and un-serializing the object.
All names returned by
__variable_attrs__
and__value_attrs__
must be included in this list. If not implemented, the union of__variable_attrs__
and__value_attrs__
will be used instead, and dataclasses default to all fields possibly containing data. The only dataclass fields excluded are those of typeShape
or primitive typesstr
,int
,float
, etc. and collections of these.Returns
tuple
ofstr
attributes. Callinggetattr(self, attr)
must return aTensor
orPhiTreeNode
for all returned attributes. def __value_attrs__(self) ‑> Tuple[str, ...]
-
Returns all
Tensor
orPhiTreeNode
attribute names ofself
that should be transformed by single-operand math operations, such assin()
,exp()
. Optimization functions, such asminimize
also work on value attributes, as well as most derivative-related function, e.g. -jacobian()
-custom_gradient()
Dataclasses may instead declare the field
values: Tuple[str,...]
Returns
tuple
ofstr
attributes. Callinggetattr(self, attr)
must return aTensor
orPhiTreeNode
for all returned attributes. def __variable_attrs__(self) ‑> Tuple[str, ...]
-
Returns all
Tensor
orPhiTreeNode
attribute names ofself
whose values are variable. Variables denote values that can change from one function call to the next or for which gradients can be recorded. If this method is not implemented, all attributes returned by__value_attrs__()
are considered variable.The returned properties are used by the following functions:
jit_compile()
jit_compile_linear()
stop_gradient()
Dataclasses may instead declare the field
variables: Tuple[str,...]
Returns
tuple
ofstr
attributes. Callinggetattr(self, attr)
must return aTensor
orPhiTreeNode
for all returned attributes. def __with_attrs__(self, **attrs)
-
Used by
replace()
. Create a copy of this object which has theTensor
orPhiTreeNode
attributes contained inattrs
replaced. If this method is not implemented, tensor attributes are replaced usingsetattr()
.Args
**attrs
dict
mappingstr
attribute names toTensor
orPhiTreeNode
.
Returns
Altered copy of
self
class Shapable
-
Shapable objects can be stacked, concatenated and reshaped.
To be considered
Shapable
, objects must beSliceable
andShaped
and implement__stack__()
or__concat__()
and__expand__()
.
Objects should additionally implement the other magic methods for performance reasons.
Usage in
phiml.math
:Shapable objects can be used with the following functions in addition to what they inherit from being
Sliceable
andShaped
:Additionally, the
phiml.math.BoundDim
syntax for dimension renaming and retyping is enabled, e.g.obj.dim.as_channel('vector')
.Expand source code
class Shapable(metaclass=_ShapableType): """ Shapable objects can be stacked, concatenated and reshaped. To be considered `Shapable`, objects must be `Sliceable` and `Shaped` and implement * `__stack__()` or * `__concat__()` and `__expand__()`. Objects should additionally implement the other magic methods for performance reasons. **Usage in `phiml.math`:** Shapable objects can be used with the following functions in addition to what they inherit from being `Sliceable` and `Shaped`: * `phiml.math.stack` * `phiml.math.concat` * `phiml.math.expand` * `phiml.math.rename_dims` * `phiml.math.pack_dims` * `phiml.math.unpack_dim` * `phiml.math.flatten` Additionally, the `phiml.math.BoundDim` syntax for dimension renaming and retyping is enabled, e.g. `obj.dim.as_channel('vector')`. """ @staticmethod def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Shapable': """ Stack all `values` into a single instance along the new dimension `dim`. This method can be implemented as a bound method or as a `staticmethod` (without the `self` argument). Args: values: `tuple` of `Shapable` objects to be stacked. `self` is included in that list at least once. dim: Single-dimension `Shape`. This dimension must not be present with any of the `values`. The dimension fulfills the condition `dim.size == len(values)`. **kwargs: Additional keyword arguments required by specific implementations. Adding spatial dimensions to fields requires the `bounds: Box` argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments. Returns: New instance of `Shapable` representing the stacked slices. Its shape includes `dim` in addition to the dimensions present in `values`. If such a representation cannot be created because some values in `values` are not supported, returns `NotImplemented`. """ raise NotImplementedError @staticmethod def __concat__(values: tuple, dim: str, **kwargs) -> 'Shapable': """ Concatenate `values` along `dim`. This method can be implemented as a bound method or as a `staticmethod` (without the `self` argument). Args: values: Values to concatenate. `self` is included in that list at least once. dim: Dimension nams as `str`, must be present in all `values`. **kwargs: Additional keyword arguments required by specific implementations. Adding spatial dimensions to fields requires the `bounds: Box` argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments. Returns: New instance of `Shapable` representing the concatenated values or `NotImplemented` to revert to default behavior for this object. When returning a valid object, the size of `dim` must be equal to the sum of all `dim` sizes in `values`. If such a representation cannot be created because some values in `values` are not supported, returns `NotImplemented`. """ raise NotImplementedError def __expand__(self, dims: Shape, **kwargs) -> 'Shapable': """ Adds new dimensions to this object. The value of this object is constant along the new dimensions. Args: dims: Dimensions to add. They are guaranteed to not already be present in `shape(self)`. **kwargs: Additional keyword arguments required by specific implementations. Adding spatial dimensions to fields requires the `bounds: Box` argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments. Returns: New instance of `Shapable` or `NotImplemented` to revert to default behavior for this object. """ raise NotImplementedError def __replace_dims__(self, dims: Tuple[str, ...], new_dims: Shape, **kwargs) -> 'Shapable': """ Exchange existing dimensions. This can be used to rename dimensions, change dimension types or change item names. Args: dims: Dimensions to be replaced. new_dims: Replacement dimensions as `Shape` with `rank == len(dims)`. **kwargs: Additional keyword arguments required by specific implementations. Adding spatial dimensions to fields requires the `bounds: Box` argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments. Returns: New instance of `Shapable` or `NotImplemented` to revert to default behavior for this object. """ raise NotImplementedError def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: Shape, pos: Union[int, None], **kwargs) -> 'Shapable': """ Compresses multiple dimensions into a single dimension by concatenating the elements. Elements along the new dimensions are laid out according to the order of `dims`. The type of the new dimension will be equal to the types of `dims`. If `dims` have varying types, the new dimension will be a batch dimension. Args: dims: Dimensions to be compressed in the specified order. packed_dim: Single-dimension `Shape`. pos: Index of new dimension. `None` for automatic, `-1` for last, `0` for first. **kwargs: Additional keyword arguments required by specific implementations. Adding spatial dimensions to fields requires the `bounds: Box` argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments. Returns: New instance of `Shapable` or `NotImplemented` to revert to default behavior for this object. """ raise NotImplementedError def __unpack_dim__(self, dim: str, unpacked_dims: Shape, **kwargs) -> 'Shapable': """ Decompresses a tensor dimension by unstacking the elements along it. The compressed dimension `dim` is assumed to contain elements laid out according to the order of `unpacked_dims`. Args: dim: Dimension to be decompressed. unpacked_dims: `Shape`: Ordered dimensions to replace `dim`, fulfilling `unpacked_dims.volume == shape(self)[dim].rank`. **kwargs: Additional keyword arguments required by specific implementations. Adding spatial dimensions to fields requires the `bounds: Box` argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments. Returns: New instance of `Shapable` or `NotImplemented` to revert to default behavior for this object. """ raise NotImplementedError def __flatten__(self, flat_dim: Shape, flatten_batch: bool, **kwargs) -> 'Shapable': """ Lays out all elements along a single dimension. This is equivalent to packing all dimensions. Args: flat_dim: Single dimension as `Shape`. flatten_batch: Whether to flatten batch dimensions as well. If `False`, batch dimensions are kept, only onn-batch dimensions are flattened. **kwargs: Additional keyword arguments required by specific implementations. Adding spatial dimensions to fields requires the `bounds: Box` argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments. Returns: New instance of `Shapable` or `NotImplemented` to revert to default behavior for this object. """ raise NotImplementedError
Static methods
def __concat__(values: tuple, dim: str, **kwargs) ‑> Shapable
-
Concatenate
values
alongdim
.This method can be implemented as a bound method or as a
staticmethod
(without theself
argument).Args
values
- Values to concatenate.
self
is included in that list at least once. dim
- Dimension nams as
str
, must be present in allvalues
. **kwargs
- Additional keyword arguments required by specific implementations.
Adding spatial dimensions to fields requires the
bounds: Box
argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments.
Returns
New instance of
Shapable
representing the concatenated values orNotImplemented
to revert to default behavior for this object. When returning a valid object, the size ofdim
must be equal to the sum of alldim
sizes invalues
. If such a representation cannot be created because some values invalues
are not supported, returnsNotImplemented
. def __stack__(values: tuple, dim: phiml.math._shape.Shape, **kwargs) ‑> Shapable
-
Stack all
values
into a single instance along the new dimensiondim
.This method can be implemented as a bound method or as a
staticmethod
(without theself
argument).Args
values
tuple
ofShapable
objects to be stacked.self
is included in that list at least once.dim
- Single-dimension
Shape
. This dimension must not be present with any of thevalues
. The dimension fulfills the conditiondim.size == len(values)
. **kwargs
- Additional keyword arguments required by specific implementations.
Adding spatial dimensions to fields requires the
bounds: Box
argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments.
Returns
New instance of
Shapable
representing the stacked slices. Its shape includesdim
in addition to the dimensions present invalues
. If such a representation cannot be created because some values invalues
are not supported, returnsNotImplemented
.
Methods
def __expand__(self, dims: phiml.math._shape.Shape, **kwargs) ‑> Shapable
-
Adds new dimensions to this object. The value of this object is constant along the new dimensions.
Args
dims
- Dimensions to add.
They are guaranteed to not already be present in
shape(self)
. **kwargs
- Additional keyword arguments required by specific implementations.
Adding spatial dimensions to fields requires the
bounds: Box
argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments.
Returns
New instance of
Shapable
orNotImplemented
to revert to default behavior for this object. def __flatten__(self, flat_dim: phiml.math._shape.Shape, flatten_batch: bool, **kwargs) ‑> Shapable
-
Lays out all elements along a single dimension. This is equivalent to packing all dimensions.
Args
flat_dim
- Single dimension as
Shape
. flatten_batch
- Whether to flatten batch dimensions as well.
- If
False
, batch dimensions are kept, only onn-batch dimensions are flattened. **kwargs
- Additional keyword arguments required by specific implementations.
Adding spatial dimensions to fields requires the
bounds: Box
argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments.
Returns
New instance of
Shapable
orNotImplemented
to revert to default behavior for this object. def __pack_dims__(self, dims: Tuple[str, ...], packed_dim: phiml.math._shape.Shape, pos: Optional[int], **kwargs) ‑> Shapable
-
Compresses multiple dimensions into a single dimension by concatenating the elements. Elements along the new dimensions are laid out according to the order of
dims
.The type of the new dimension will be equal to the types of
dims
. Ifdims
have varying types, the new dimension will be a batch dimension.Args
dims
- Dimensions to be compressed in the specified order.
packed_dim
- Single-dimension
Shape
. pos
- Index of new dimension.
None
for automatic,-1
for last,0
for first. **kwargs
- Additional keyword arguments required by specific implementations.
Adding spatial dimensions to fields requires the
bounds: Box
argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments.
Returns
New instance of
Shapable
orNotImplemented
to revert to default behavior for this object. def __replace_dims__(self, dims: Tuple[str, ...], new_dims: phiml.math._shape.Shape, **kwargs) ‑> Shapable
-
Exchange existing dimensions. This can be used to rename dimensions, change dimension types or change item names.
Args
dims
- Dimensions to be replaced.
new_dims
- Replacement dimensions as
Shape
withrank == len(dims)
. **kwargs
- Additional keyword arguments required by specific implementations.
Adding spatial dimensions to fields requires the
bounds: Box
argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments.
Returns
New instance of
Shapable
orNotImplemented
to revert to default behavior for this object. def __unpack_dim__(self, dim: str, unpacked_dims: phiml.math._shape.Shape, **kwargs) ‑> Shapable
-
Decompresses a tensor dimension by unstacking the elements along it. The compressed dimension
dim
is assumed to contain elements laid out according to the order ofunpacked_dims
.Args
dim
- Dimension to be decompressed.
unpacked_dims
Shape
: Ordered dimensions to replacedim
, fulfillingunpacked_dims.volume == shape(self)[dim].rank
.**kwargs
- Additional keyword arguments required by specific implementations.
Adding spatial dimensions to fields requires the
bounds: Box
argument specifying the physical extent of the new dimensions. Adding batch dimensions must always work without keyword arguments.
Returns
New instance of
Shapable
orNotImplemented
to revert to default behavior for this object.
class Shaped
-
To be considered shaped, an object must either implement the magic method
__shape__()
or have a validshape
property. In either case, the returned shape must be an instance ofShape
.To check whether an object is
Shaped
, useisinstance(obj, Shaped)
.Usage in
phiml.math
:The functions
shape()
as well as dimension filters, such asspatial()
ornon_batch()
can be called on all shaped objects.Expand source code
class Shaped(metaclass=_ShapedType): """ To be considered shaped, an object must either implement the magic method `__shape__()` or have a valid `shape` property. In either case, the returned shape must be an instance of `phiml.math.Shape`. To check whether an object is `Shaped`, use `isinstance(obj, Shaped)`. **Usage in `phiml.math`:** The functions `phiml.math.shape` as well as dimension filters, such as `phiml.math.spatial` or `phiml.math.non_batch` can be called on all shaped objects. See Also: `Sliceable`, `Shapable` """ def __shape__(self) -> 'Shape': """ Returns the shape of this object. Alternatively, the shape can be declared via the property `shape`. Returns: `phiml.math.Shape` """ raise NotImplementedError @property def shape(self) -> 'Shape': """ Alternative form of `__shape__()`. Implement either to be considered `Shaped`. Returns: `phiml.math.Shape` """ raise NotImplementedError
Instance variables
prop shape : Shape
-
Expand source code
@property def shape(self) -> 'Shape': """ Alternative form of `__shape__()`. Implement either to be considered `Shaped`. Returns: `phiml.math.Shape` """ raise NotImplementedError
Methods
def __shape__(self) ‑> phiml.math._shape.Shape
-
Returns the shape of this object.
Alternatively, the shape can be declared via the property
shape
.Returns
class Sliceable
-
Objects are considered sliceable if they are
Shaped
and implement__getitem__
as defined below.To enable the slicing syntax
obj.dim[slice]
, implement the__getattr__
method as defined below.Classes implementing
Sliceable
should override__getattr__
to enable the special slicing syntax defined inBoundDim
.Usage in
phiml.math
:In addition to slicing, sliceable objects can be unstacked along one or multiple dimensions using
unstack()
.Expand source code
class Sliceable(metaclass=_SliceableType): """ Objects are considered sliceable if they are `Shaped` and implement `__getitem__` as defined below. To enable the slicing syntax `obj.dim[slice]`, implement the `__getattr__` method as defined below. Classes implementing `Sliceable` should override `__getattr__` to enable the special slicing syntax defined in `BoundDim`. **Usage in `phiml.math`:** In addition to slicing, sliceable objects can be unstacked along one or multiple dimensions using `phiml.math.unstack`. See Also `Shapable`, `Shaped` """ def __getitem__(self, item) -> 'Sliceable': """ Slice this object along one or multiple existing or non-existing dimensions. When overriding this function, make sure to first call `slicing_dict(self, item)` to sort slices by dimension. Args: item: `dict` mapping dimension names to the corresponding selections. Selections can be slices, indices, tuples, item names, bool tensors, int tensors or other custom types. All Sliceable object must support indexing by `int`, `slice`, `tuple`, `list`, `str`. Returns: Instance of the same class (or a compatible class) as `self`. """ raise NotImplementedError def __unstack__(self, dims: Tuple[str, ...]) -> Tuple['Sliceable', ...]: """ Un-stack this object along one or multiple dimensions. Un-stacking along multiple dimensions is equal to first packing the dimensions and then unstacking along the packed dimension. Implementing this magic method is optional but the default implementation may be slow. Args: dims: Ordered `tuple` of dimension names along which to unstack this object. Returns: `tuple` of slices along `dims` or `NotImplemented` to revert to default behavior for this object. """ raise NotImplementedError
Methods
def __getitem__(self, item) ‑> Sliceable
-
Slice this object along one or multiple existing or non-existing dimensions.
When overriding this function, make sure to first call
slicing_dict()(self, item)
to sort slices by dimension.Args
item
dict
mapping dimension names to the corresponding selections. Selections can be slices, indices, tuples, item names, bool tensors, int tensors or other custom types. All Sliceable object must support indexing byint
,slice
,tuple
,list
,str
.
Returns
Instance of the same class (or a compatible class) as
self
. def __unstack__(self, dims: Tuple[str, ...]) ‑> Tuple[Sliceable, ...]
-
Un-stack this object along one or multiple dimensions. Un-stacking along multiple dimensions is equal to first packing the dimensions and then unstacking along the packed dimension.
Implementing this magic method is optional but the default implementation may be slow.
Args
dims
- Ordered
tuple
of dimension names along which to unstack this object.
Returns
tuple
of slices alongdims
orNotImplemented
to revert to default behavior for this object.