Module phi.field
The fields module provides a number of data structures and functions to represent continuous, spatially varying data.
All fields are subclasses of Field
which provides abstract functions for sampling field values at physical locations.
The most important field types are:
CenteredGrid()
embeds a tensor in the physical space. Uses linear interpolation between grid points.StaggeredGrid()
samples the vector components at face centers instead of at cell centers.Noise
is a function that produces a procedurally generated noise field
Use grid()
to create a Field
from data or by sampling another Field
or Geometry
.
Alternatively, the phi.physics.Domain
class provides convenience methods for grid creation.
All fields can be sampled at physical locations or volumes using sample()
or reduce_sample()
.
See the phi.field
module documentation at https://tum-pbs.github.io/PhiFlow/Fields.html
Functions
def CenteredGrid(values: Any = 0.0, boundary: Any = 0.0, bounds: phi.geom._box.Box = None, resolution: int = None, extrapolation: Any = None, convert=True, **resolution_: int) ‑> phi.field._field.Field
-
Create an n-dimensional grid with values sampled at the cell centers. A centered grid is defined through its
CenteredGrid.values
phi.math.Tensor
, itsCenteredGrid.bounds
Box
describing the physical size, and itsCenteredGrid.extrapolation
(phi.math.extrapolation.Extrapolation
).Centered grids support batch, spatial and channel dimensions.
See Also:
StaggeredGrid()
,Field
,Field
,Field
, module documentation at https://tum-pbs.github.io/PhiFlow/Fields.htmlArgs
values
-
Values to use for the grid. Has to be one of the following:
Geometry
: sets inside values to 1, outside to 0Field
: resamples the Field to the staggered sample pointsNumber
: uses the value for all sample pointstuple
orlist
: interprets the sequence as vector, used for all sample pointsphi.math.Tensor
compatible with grid dims: uses tensor values as grid values- Function
values(x)
wherex
is aphi.math.Tensor
representing the physical location. The spatial dimensions of the grid will be passed as batch dimensions to the function.
extrapolation
- The grid extrapolation determines the value outside the
values
tensor. Allowed types:float
,phi.math.Tensor
,phi.math.extrapolation.Extrapolation
. bounds
- Physical size and location of the grid as
Box
. If the resolution is determined throughresolution
ofvalues
, afloat
can be passed forbounds
to create a unit box. resolution
- Grid resolution as purely spatial
phi.math.Shape
. Ifbounds
is given as aBox
, the resolution may be specified as anint
to be equal along all axes. **resolution_
- Spatial dimensions as keyword arguments. Typically either
resolution
orspatial_dims
are specified. convert
- Whether to convert
values
to the default backend.
def PointCloud(elements: Union[phiml.math._tensors.Tensor, phi.geom._geom.Geometry], values: Any = 1.0, extrapolation: Union[phiml.math.extrapolation.Extrapolation, float] = 0.0, bounds: phi.geom._box.Box = None) ‑> phi.field._field.Field
-
A
PointCloud()
comprises:elements
: aGeometry
representing all points or volumesvalues
: aTensor
representing the values corresponding toelements
extrapolation
: anExtrapolation
defining the field value outside ofvalues
The points / elements of the
PointCloud()
are listed along instance or spatial dimensions ofelements
. These dimensions are automatically added tovalues
if not already present.When sampling or resampling a
PointCloud()
, the following keyword arguments can be specified.soft
: default=False. IfTrue
, interpolates smoothly from 1 to 0 between the inside and outside of elements. IfFalse
, only the center position of the new representation elements is checked against the point cloud elements.scatter
: default=False. IfTrue
, scattering will be used to sample the point cloud onto grids. Then, each element of the point cloud can only affect a single cell. This is only recommended when the points are much smaller than the cells.outside_handling
: default='discard'. One of'discard'
,'clamp'
,'undefined'
.balance
: default=0.5. Only used whensoft=True
. See the description inGeometry.approximate_fraction_inside()
.
See the
phi.field
module documentation at https://tum-pbs.github.io/PhiFlow/Fields.htmlArgs
elements
Tensor
orGeometry
object specifying the sample points and sizesvalues
- values corresponding to elements
extrapolation
- values outside elements
bounds
- Deprecated. Has no use since 2.5.
def StaggeredGrid(values: Any = 0.0, boundary: float = 0, bounds: phi.geom._box.Box = None, resolution: phiml.math._shape.Shape = None, extrapolation: float = None, convert=True, **resolution_: int) ‑> phi.field._field.Field
-
N-dimensional grid whose vector components are sampled at the respective face centers. A staggered grid is defined through its values tensor, its bounds describing the physical size, and its extrapolation.
Staggered grids support batch and spatial dimensions but only one channel dimension for the staggered vector components.
See Also:
CenteredGrid()
,Field
,Field
,Field
, module documentation at https://tum-pbs.github.io/PhiFlow/Fields.htmlArgs
values
-
Values to use for the grid. Has to be one of the following:
Geometry
: sets inside values to 1, outside to 0Field
: resamples the Field to the staggered sample pointsNumber
: uses the value for all sample pointstuple
orlist
: interprets the sequence as vector, used for all sample pointsphi.math.Tensor
with staggered shape: uses tensor values as grid values. Must contain avector
dimension with each slice consisting of one more element along the dimension they describe. Usephi.math.stack()
to manually create this non-uniform tensor.- Function
values(x)
wherex
is aphi.math.Tensor
representing the physical location. The spatial dimensions of the grid will be passed as batch dimensions to the function.
boundary
- The grid extrapolation determines the value outside the
values
tensor. Allowed types:float
,phi.math.Tensor
,phi.math.extrapolation.Extrapolation
. bounds
- Physical size and location of the grid as
Box
. If the resolution is determined throughresolution
ofvalues
, afloat
can be passed forbounds
to create a unit box. resolution
- Grid resolution as purely spatial
phi.math.Shape
. Ifbounds
is given as aBox
, the resolution may be specified as anint
to be equal along all axes. convert
- Whether to convert
values
to the default backend. **resolution_
- Spatial dimensions as keyword arguments. Typically either
resolution
orspatial_dims
are specified.
def abs(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Computes ||x||1. Complex
x
result in matching precision float values.Note: The gradient of this operation is undefined for x=0. TensorFlow and PyTorch return 0 while Jax returns 1.
Args
x
Tensor
orphiml.math.magic.PhiTreeNode
Returns
Absolute value of
x
of same type asx
. def as_boundary(obj: Union[phiml.math.extrapolation.Extrapolation, phiml.math._tensors.Tensor, float, phi.field._field.Field, ForwardRef(None)]) ‑> phiml.math.extrapolation.Extrapolation
-
Returns an
Extrapolation
representingobj
.Args
obj
-
One of
float
orTensor
: Extrapolate with a constant valueExtrapolation
: Use as-is.Field
: Sample values fromobj
, embedding another field insideobj
.
Returns
Extrapolation
def assert_close(*fields: phi.field._field.Field, rel_tolerance: float = 1e-05, abs_tolerance: float = 0, msg: str = '', verbose: bool = True)
-
Raises an AssertionError if the
values
of the given fields are not close. Seephi.math.assert_close()
. def bake_extrapolation(grid: phi.field._field.Field) ‑> phi.field._field.Field
-
Pads
grid
with its current extrapolation. ForStaggeredGrid()
s, the resulting grid will have a consistent shape, independent of the original extrapolation.Args
grid
CenteredGrid()
orStaggeredGrid()
.
Returns
Padded grid with extrapolation
phi.math.extrapolation.NONE
. def cast(x: ~MagicType, dtype: Union[phiml.backend._dtype.DType, type]) ‑> ~OtherMagicType
-
Casts
x
to a different data type.Implementations:
- NumPy:
x.astype()
- PyTorch:
x.to()
- TensorFlow:
tf.cast
- Jax:
jax.numpy.array
See Also:
to_float()
,to_int32()
,to_int64()
,to_complex
.Args
x
Tensor
dtype
- New data type as
phiml.math.DType
, e.g.DType(int, 16)
.
Returns
Tensor
with data typedtype
- NumPy:
def ceil(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Computes ⌈x⌉ of the
Tensor
orphiml.math.magic.PhiTreeNode
x
. def center_of_mass(density: phi.field._field.Field)
-
Compute the center of mass of a density field.
Args
density
- Scalar
Field
Returns
Tensor
holding only batch dimensions. def concat(fields: Sequence[phi.field._field.Field], dim: str) ‑> phi.field._field.Field
def convert(x, backend: phiml.backend._backend.Backend = None, use_dlpack=True)
-
Convert the native representation of a
Tensor
orphiml.math.magic.PhiTreeNode
to the native format ofbackend
.Warning: This operation breaks the automatic differentiation chain.
See Also:
phiml.math.backend.convert()
.Args
x
Tensor
to convert. Ifx
is aphiml.math.magic.PhiTreeNode
, its variable attributes are converted.backend
- Target backend. If
None
, uses the current default backend, seephiml.math.backend.default_backend()
.
Returns
Tensor
with native representation belonging tobackend
. def cos(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Computes cos(x) of the
Tensor
orphiml.math.magic.PhiTreeNode
x
. def curl(field: phi.field._field.Field, at='corner')
-
Computes the finite-difference curl of the give 2D
StaggeredGrid()
.Args
field
Field
at
- Either
center
orface
.
def divergence(field: phi.field._field.Field, order=2, implicit: phiml.math._optimize.Solve = None, upwind: phi.field._field.Field = None, implicitness: int = None) ‑>
CenteredGrid() at 0x7ff7ac40eca0> -
Computes the divergence of a grid using finite differences.
This function can operate in two modes depending on the type of
field
:CenteredGrid()
approximates the divergence at cell centers using central differencesStaggeredGrid()
exactly computes the divergence at cell centers
Args
field
- vector field as
CenteredGrid()
orStaggeredGrid()
order
- Spatial order of accuracy. Higher orders entail larger stencils and more computation time but result in more accurate results assuming a large enough resolution. Supported: 2 explicit, 4 explicit, 6 implicit.
implicit
- When a
Solve
object is passed, performs an implicit operation with the specified solver and tolerances. Otherwise, an explicit stencil is used. implicitness
- specifies the size of the implicit stencil in case an implicit treatment is used
upwind
- For unstructured meshes only. Whether to use upwind interpolation.
Returns
Divergence field as
CenteredGrid()
def downsample2x(grid: phi.field._field.Field) ‑> phi.field._field.Field
-
Reduces the number of sample points by a factor of 2 in each spatial dimension. The new values are determined via linear interpolation.
See Also:
upsample2x()
.Args
grid
CenteredGrid()
orStaggeredGrid()
.
Returns
Field
of same type asgrid
. def exp(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Computes exp(x) of the
Tensor
orphiml.math.magic.PhiTreeNode
x
. def finite_fill(grid: phi.field._field.Field, distance=1, diagonal=True) ‑> phi.field._field.Field
-
Extrapolates values of
grid
which are marked by nonzero values invalid
using `phi.math.masked_fill(). Ifvalues
is a StaggeredGrid, its components get extrapolated independently.Args
grid
- Grid holding the values for extrapolation and possible non-finite values to be filled.
distance
- Number of extrapolation steps, i.e. how far a cell can be from the closest finite value to get filled.
diagonal
- Whether to extrapolate values to their diagonal neighbors per step.
Returns
grid
- Grid with extrapolated values.
valid
- binary Grid marking all valid values after extrapolation.
def floor(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Computes ⌊x⌋ of the
Tensor
orphiml.math.magic.PhiTreeNode
x
. def fourier_laplace(grid: phi.field._field.Field, times=1) ‑> phi.field._field.Field
-
See
phi.math.fourier_laplace()
def fourier_poisson(grid: phi.field._field.Field, times=1) ‑> phi.field._field.Field
-
See
phi.math.fourier_poisson()
def frequency_loss(x, frequency_falloff: float = 100, threshold=1e-05, ignore_mean=False, n=2) ‑> phiml.math._tensors.Tensor
-
Penalizes the squared
values
in frequency (Fourier) space. Lower frequencies are weighted more strongly then higher frequencies, depending onfrequency_falloff
.Args
x
Tensor
orphiml.math.magic.PhiTreeNode
Values to penalize, typicallyactual - target
.frequency_falloff
- Large values put more emphasis on lower frequencies, 1.0 weights all frequencies equally. Note: The total loss is not normalized. Varying the value will result in losses of different magnitudes.
threshold
- Frequency amplitudes below this value are ignored. Setting this to zero may cause infinities or NaN values during backpropagation.
ignore_mean
- If
True
, does not penalize the mean value (frequency=0 component).
Returns
Scalar loss value
def functional_gradient(f: Callable, wrt: str = None, get_output=True) ‑> Callable
-
Creates a function which computes the gradient of
f
.Example:
def loss_function(x, y): prediction = f(x) loss = math.l2_loss(prediction - y) return loss, prediction dx = gradient(loss_function, 'x', get_output=False)(x, y) (loss, prediction), (dx, dy) = gradient(loss_function, 'x,y', get_output=True)(x, y)
Functional gradients are implemented for the following backends:
- PyTorch:
torch.autograd.grad
/torch.autograd.backward
- TensorFlow:
tf.GradientTape
- Jax:
jax.grad
When the gradient function is invoked,
f
is called with tensors that track the gradient. For PyTorch,arg.requires_grad = True
for all positional arguments off
.Args
f
- Function to be differentiated.
f
must return a floating pointTensor
with rank zero. It can return additional tensors which are treated as auxiliary data and will be returned by the gradient function ifreturn_values=True
. All arguments for which the gradient is computed must be of dtype float or complex. get_output
- Whether the gradient function should also return the return values of
f
. wrt
- Comma-separated parameter names of
f
with respect to which the gradient should be computed. If not specified, the gradient will be computed w.r.t. the first positional argument (highly discouraged).
Returns
Function with the same arguments as
f
that returns the value off
, auxiliary data and gradient off
ifget_output=True
, else just the gradient off
. - PyTorch:
def gradient(f: Callable, wrt: str = None, get_output=True) ‑> Callable
-
Creates a function which computes the gradient of
f
.Example:
def loss_function(x, y): prediction = f(x) loss = math.l2_loss(prediction - y) return loss, prediction dx = gradient(loss_function, 'x', get_output=False)(x, y) (loss, prediction), (dx, dy) = gradient(loss_function, 'x,y', get_output=True)(x, y)
Functional gradients are implemented for the following backends:
- PyTorch:
torch.autograd.grad
/torch.autograd.backward
- TensorFlow:
tf.GradientTape
- Jax:
jax.grad
When the gradient function is invoked,
f
is called with tensors that track the gradient. For PyTorch,arg.requires_grad = True
for all positional arguments off
.Args
f
- Function to be differentiated.
f
must return a floating pointTensor
with rank zero. It can return additional tensors which are treated as auxiliary data and will be returned by the gradient function ifreturn_values=True
. All arguments for which the gradient is computed must be of dtype float or complex. get_output
- Whether the gradient function should also return the return values of
f
. wrt
- Comma-separated parameter names of
f
with respect to which the gradient should be computed. If not specified, the gradient will be computed w.r.t. the first positional argument (highly discouraged).
Returns
Function with the same arguments as
f
that returns the value off
, auxiliary data and gradient off
ifget_output=True
, else just the gradient off
. - PyTorch:
def imag(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Returns the imaginary part of
x
. Ifx
does not store complex numbers, returns a zero tensor with the same shape and dtype as this tensor.See Also:
real()
,conjugate()
.Args
x
Tensor
orphiml.math.magic.PhiTreeNode
or native tensor.
Returns
Imaginary component of
x
ifx
is complex, zeros otherwise. def integrate(field: phi.field._field.Field, region: phi.geom._geom.Geometry, **kwargs) ‑> phiml.math._tensors.Tensor
-
Computes ∫R f(x) dxd , where f denotes the
Field
, R theregion
and d the number of spatial dimensions (d=field.shape.spatial_rank
). Depending on thesample()
implementation forfield
, the integral may be a rough approximation.This method is currently only implemented for
CenteredGrid()
.Args
field
Field
to integrate.region
- Region to integrate over.
**kwargs
- Specify numerical scheme.
Returns
Integral as
phi.Tensor
def is_finite(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Returns a
Tensor
orphiml.math.magic.PhiTreeNode
matchingx
with valuesTrue
wherex
has a finite value andFalse
otherwise. def isfinite(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Returns a
Tensor
orphiml.math.magic.PhiTreeNode
matchingx
with valuesTrue
wherex
has a finite value andFalse
otherwise. def jacobian(f: Callable, wrt: str = None, get_output=True) ‑> Callable
-
Creates a function which computes the Jacobian matrix of
f
. For scalar functions, consider usinggradient()
instead.Example:
def f(x, y): prediction = f(x) loss = math.l2_loss(prediction - y) return loss, prediction dx = jacobian(loss_function, wrt='x', get_output=False)(x, y) (loss, prediction), (dx, dy) = jacobian(loss_function, wrt='x,y', get_output=True)(x, y)
Functional gradients are implemented for the following backends:
- PyTorch:
torch.autograd.grad
/torch.autograd.backward
- TensorFlow:
tf.GradientTape
- Jax:
jax.grad
When the gradient function is invoked,
f
is called with tensors that track the gradient. For PyTorch,arg.requires_grad = True
for all positional arguments off
.Args
f
- Function to be differentiated.
f
must return a floating pointTensor
with rank zero. It can return additional tensors which are treated as auxiliary data and will be returned by the gradient function ifreturn_values=True
. All arguments for which the gradient is computed must be of dtype float or complex. get_output
- Whether the gradient function should also return the return values of
f
. wrt
- Comma-separated parameter names of
f
with respect to which the gradient should be computed. If not specified, the gradient will be computed w.r.t. the first positional argument (highly discouraged).
Returns
Function with the same arguments as
f
that returns the value off
, auxiliary data and Jacobian off
ifget_output=True
, else just the Jacobian off
. - PyTorch:
def jit_compile(f: Callable = None, auxiliary_args: str = '', forget_traces: bool = None) ‑> Callable
-
Compiles a graph based on the function
f
. The graph compilation is performed just-in-time (jit), e.g. when the returned function is called for the first time.The traced function will compute the same result as
f
but may run much faster. Some checks may be disabled in the compiled function.Can be used as a decorator:
@math.jit_compile def my_function(x: math.Tensor) -> math.Tensor:
Invoking the returned function may invoke re-tracing / re-compiling
f
after the first call if either- it is called with a different number of arguments,
- the tensor arguments have different dimension names or types (the dimension order also counts),
- any
Tensor
arguments require a different backend than previous invocations, phiml.math.magic.PhiTreeNode
positional arguments do not match in non-variable properties.
Compilation is implemented for the following backends:
- PyTorch:
torch.jit.trace
- TensorFlow:
tf.function
- Jax:
jax.jit
Jit-compilations cannot be nested, i.e. you cannot call
jit_compile()
while another function is being compiled. An exception to this isjit_compile_linear()
which can be called from within a jit-compiled function.See Also:
jit_compile_linear()
Args
f
- Function to be traced.
All positional arguments must be of type
Tensor
orphiml.math.magic.PhiTreeNode
returning a singleTensor
orphiml.math.magic.PhiTreeNode
. auxiliary_args
- Comma-separated parameter names of arguments that are not relevant to backpropagation.
forget_traces
- If
True
, only remembers the most recent compiled instance of this function. Upon tracing with new instance (due to changed shapes or auxiliary args), deletes the previous traces.
Returns
Function with similar signature and return values as
f
. def jit_compile_linear(f: Callable[[~X], ~Y] = None, auxiliary_args: str = None, forget_traces: bool = None)
-
Compile an optimized representation of the linear function
f
. For backends that support sparse tensors, a sparse matrix will be constructed forf
.Can be used as a decorator:
@math.jit_compile_linear def my_linear_function(x: math.Tensor) -> math.Tensor:
Unlike
jit_compile()
,jit_compile_linear()
can be called during a regular jit compilation.See Also:
jit_compile()
Args
f
- Function that is linear in its positional arguments.
All positional arguments must be of type
Tensor
andf
must return aTensor
. auxiliary_args
- Which parameters
f
is not linear in. These arguments are treated as conditioning arguments and will cause re-tracing on change. forget_traces
- If
True
, only remembers the most recent compiled instance of this function. Upon tracing with new instance (due to changed shapes or auxiliary args), deletes the previous traces.
Returns
LinearFunction
with similar signature and return values asf
. def l1_loss(x, reduce: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = <function non_batch>) ‑> phiml.math._tensors.Tensor
-
Computes ∑i ||xi||1, summing over all non-batch dimensions.
Args
x
Tensor
orphiml.math.magic.PhiTreeNode
or 0D or 1D native tensor. Forphiml.math.magic.PhiTreeNode
objects, only value the sum over all value attributes is computed.reduce
- Dimensions to reduce as
DimFilter
.
Returns
loss
Tensor
def l2_loss(x, reduce: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = <function non_batch>) ‑> phiml.math._tensors.Tensor
-
Computes ∑i ||xi||22 / 2, summing over all non-batch dimensions.
Args
x
Tensor
orphiml.math.magic.PhiTreeNode
or 0D or 1D native tensor. Forphiml.math.magic.PhiTreeNode
objects, only value the sum over all value attributes is computed.reduce
- Dimensions to reduce as
DimFilter
.
Returns
loss
Tensor
def laplace(u: phi.field._field.Field, axes: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = <function spatial>, gradient: phi.field._field.Field = None, order=2, implicit: phiml.math._optimize.Solve = None, implicitness: int = None, weights: Union[phiml.math._tensors.Tensor, phi.field._field.Field] = None, upwind: phi.field._field.Field = None, correct_skew=True) ‑> phi.field._field.Field
-
Spatial Laplace operator for scalar grid.
For grids, uses a finite difference scheme specified by
order
andimplicit
. For unstructured meshes, the scheme is specified viaorder
andupwind
.Args
u
- n-dimensional grid or mesh.
axes
- The second derivative along these dimensions is summed over
weights
- (Optional) Multiply the axis terms by these factors before summation.
Must be a
phi.math.Tensor
orField
with a single channel dimension that lists all laplace axes by name. gradient
- Only used by FVM at the moment. Approximate gradient of
u
, e.g. ∇u of the previous time step. IfNone
, approximates the gradient as(u_neighbor - u_self) / distance
. order
- Spatial order of accuracy.
Higher orders entail larger stencils and more computation time but result in more accurate results assuming a large enough resolution.
Supported: 2 explicit, 4 explicit, 6 implicit (inherited from
laplace()
). For FVM, the order is used when interpolatingv
andprev_v
to cell faces if needed. implicit
- When a
Solve
object is passed, performs an implicit operation with the specified solver and tolerances. Otherwise, an explicit stencil is used. implicitness
- specifies the size of the implicit stencil in case an implicit treatment is used
upwind
- FVM only. Whether to use upwind interpolation.
correct_skew
- If
True
, adds a correction term for cell skewness. This requiresgradient()
to be passed.
Returns
laplacian field as
CenteredGrid()
def mask(obj: phi.field._field.Field) ‑> phi.field._field.Field
-
Returns a
Field
that masks the inside (or non-zero values whenobj
is a grid) of a physical object. The mask takes the value 1 inside the object and 0 outside. ForCenteredGrid()
andStaggeredGrid()
, the mask labels non-zero non-NaN entries as 1 and all other values as 0Returns
Field
type orPointCloud()
def maximum(f1: phi.field._field.Field, f2: phi.field._field.Field)
def mean(field: phi.field._field.Field, dim=<function <lambda>>) ‑> phiml.math._tensors.Tensor
-
Computes the mean value by reducing all spatial / instance dimensions.
Args
field
Field
Returns
phi.Tensor
def minimize(f: Callable[[~X], ~Y], solve: phiml.math._optimize.Solve[~X, ~Y]) ‑> ~X
-
Finds a minimum of the scalar function f(x). The
method
argument ofsolve
determines which optimizer is used. All optimizers supported byscipy.optimize.minimize
are supported, see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html . Additionally a gradient descent solver with adaptive step size can be used withmethod='GD'
.math.minimize()
is limited to backends that supportjacobian()
, i.e. PyTorch, TensorFlow and Jax.To obtain additional information about the performed solve, use a
SolveTape
.See Also:
solve_nonlinear()
.Args
f
- Function whose output is subject to minimization.
All positional arguments of
f
are optimized and must beTensor
orphiml.math.magic.PhiTreeNode
. Ifsolve.x0
is atuple
orlist
, it will be passed to f as varargs,f(*x0)
. To minimize a subset of the positional arguments, define a new (lambda) function depending only on those. The first return value off
must be a scalar floatTensor
orphiml.math.magic.PhiTreeNode
. solve
Solve
object to specify method type, parameters and initial guess forx
.
Returns
x
- solution, the minimum point
x
.
Raises
NotConverged
- If the desired accuracy was not be reached within the maximum number of iterations.
Diverged
- If the optimization failed prematurely.
def minimum(f1: phi.field._field.Field, f2: phi.field._field.Field)
def native_call(f, *inputs, channels_last=None, channel_dim='vector', extrapolation=None) ‑> Union[phiml.math._tensors.Tensor, phi.field._field.Field]
-
Similar to
phi.math.native_call()
.Args
f
- Function to be called on native tensors of
inputs.values
. The function output must have the same dimension layout as the inputs and the batch size must be identical. *inputs
Field
orphi.Tensor
instances.extrapolation
- (Optional) Extrapolation of the output field. If
None
, uses the extrapolation of the first input field.
Returns
def normalize(field: phi.field._field.Field, norm: phi.field._field.Field, epsilon=1e-05)
-
Multiplies the values of
field
so that its sum matches the source. def pack_dims(field: phi.field._field.Field, dims: phiml.math._shape.Shape, packed_dim: phiml.math._shape.Shape, pos: int = None) ‑> phi.field._field.Field
def pad(grid: phi.field._field.Field, widths: Union[int, tuple, list, dict]) ‑> phi.field._field.Field
-
Pads a
Field
using its extrapolation.Unlike
phi.math.pad()
, this function also affects thebounds
of the grid, changing its size and origin depending onwidths
.Args
grid
CenteredGrid()
orStaggeredGrid()
widths
- Either
int
or(lower, upper)
to pad the same number of cells in all spatial dimensions ordict
mapping dimension names to(lower, upper)
.
Returns
Field
of the same type asgrid
def read(file: Union[str, phiml.math._tensors.Tensor], convert_to_backend=True) ‑> phi.field._field.Field
-
Loads a previously saved
Field
from disc.See Also:
write()
.Args
file
- Single file as
str
orTensor
of string type. Iffile
is a tensor, all contained files are loaded an stacked according to the dimensions offile
. convert_to_backend
- Whether to convert the read data to the data format of the default backend, e.g. TensorFlow tensors.
Returns
Loaded
Field
. def real(x: ~TensorOrTree) ‑> ~TensorOrTree
-
See Also:
imag()
,conjugate()
.Args
x
Tensor
orphiml.math.magic.PhiTreeNode
or native tensor.
Returns
Real component of
x
. def reduce_sample(field: Union[phi.field._field.Field, phi.geom._geom.Geometry, phi.field._field.FieldInitializer, Callable], geometry: phi.geom._geom.Geometry, **kwargs) ‑> phiml.math._tensors.Tensor
-
Alias for
sample()
withdot_face_normal=field.geometry
. def resample(value: Union[phi.field._field.Field, phi.geom._geom.Geometry, phiml.math._tensors.Tensor, float, phi.field._field.FieldInitializer], to: Union[phi.field._field.Field, phi.geom._geom.Geometry], keep_boundary=False, **kwargs)
-
Samples a
Field
,Geometry
or value at the sample points of the fieldto
. The result will approximatevalue
on the data structure ofto
. Unlikesample()
, this method returns aField
object, not aTensor
.Aliases
value.at(to)
, (and the deprecatedvalue @ to
).See Also:
sample()
,reduce_sample()
,Field.at()
, Resampling overview.Args
value
- Object containing values to resample. This can be
to
Field
(CenteredGrid()
,StaggeredGrid()
orPointCloud()
) object defining the sample points. The current values ofto
are ignored.keep_boundary
- Only available if
self
is aField
. If True, the resampled field will inherit the extrapolation fromself
instead ofrepresentation
. This can result in non-compatible value tensors for staggered grids where the tensor size depends on the extrapolation type. **kwargs
- Sampling arguments, e.g. to specify the numerical scheme. By default, linear interpolation is used. Grids also support 6th order implicit sampling at mid-points.
Returns
Field object of same type as
representation
Examples
>>> grid = CenteredGrid(x=64, y=32) >>> field.resample(Noise(), to=grid) CenteredGrid[(xˢ=64, yˢ=32), size=(x=64, y=32), extrapolation=float64 0.0] >>> field.resample(1, to=grid) CenteredGrid[(xˢ=64, yˢ=32), size=(x=64, y=32), extrapolation=float64 0.0] >>> field.resample(Box(x=1, y=2), to=grid) CenteredGrid[(xˢ=64, yˢ=32), size=(x=64, y=32), extrapolation=float64 0.0] >>> field.resample(grid, to=grid) == grid True
def round(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Rounds the
Tensor
orphiml.math.magic.PhiTreeNode
x
to the closest integer. def safe_mul(x, y)
-
See
phiml.math.safe_mul()
def sample(field: Union[phi.field._field.Field, phi.geom._geom.Geometry, phi.field._field.FieldInitializer, Callable], geometry: phi.geom._geom.Geometry, at: str = 'center', boundary: Union[phiml.math.extrapolation.Extrapolation, phiml.math._tensors.Tensor, numbers.Number] = None, dot_face_normal: Optional[phi.geom._geom.Geometry] = None, **kwargs) ‑> phiml.math._tensors.Tensor
-
Computes the field value inside the volume of the (batched)
geometry
.The field value may be determined by integrating over the volume, sampling the central value or any other way.
The batch dimensions of
geometry
are matched with this field. Thegeometry
must not share any channel dimensions with this field. Spatial dimensions ofgeometry
can be used to sample a grid of geometries.See Also:
Field.at()
, Resampling overview.Args
field
- Source
Field
to sample. geometry
- Single or batched
Geometry
orField
or locationTensor
. When passing aField
, itselements
are used as sample points. When passing a vector-valuedTensor
, aPoint
geometry will be created. at
- One of 'center', 'face', 'vertex'
boundary
- Target extrapolation.
dot_face_normal
- If not
None
and ,field
is a vector field andat=='face'
, the dot product between sampled field vectors and the face normals is returned instead. **kwargs
- Sampling arguments, e.g. to specify the numerical scheme. By default, linear interpolation is used. Grids also support 6th order implicit sampling at mid-points.
Returns
Sampled values as a
phi.math.Tensor
def shift(grid: phi.field._field.Field, offsets: tuple, stack_dim: Optional[phiml.math._shape.Shape] = (shiftᶜ=None), dims=<function spatial>, pad=True)
-
Wraps :func:
math.shift
for CenteredGrid.Args
grid
- CenteredGrid:
offsets
- tuple:
stack_dim
- (Default value = 'shift')
def sign(x: ~TensorOrTree) ‑> ~TensorOrTree
-
The sign of positive numbers is 1 and -1 for negative numbers. The sign of 0 is undefined.
Args
x
Tensor
orphiml.math.magic.PhiTreeNode
Returns
Tensor
orphiml.math.magic.PhiTreeNode
matchingx
. def sin(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Computes sin(x) of the
Tensor
orphiml.math.magic.PhiTreeNode
x
. def solve_linear(f: Union[Callable[[~X], ~Y], phiml.math._tensors.Tensor], y: ~Y, solve: phiml.math._optimize.Solve[~X, ~Y], *f_args, grad_for_f=False, f_kwargs: dict = None, **f_kwargs_) ‑> ~X
-
Solves the system of linear equations f(x) = y and returns x. This method will use the solver specified in
solve
. The following method identifiers are supported by all backends:'auto'
: Automatically choose a solver'CG'
: Conjugate gradient, only for symmetric and positive definite matrices.'CG-adaptive'
: Conjugate gradient with adaptive step size, only for symmetric and positive definite matrices.'biCG'
or'biCG-stab(0)'
: Biconjugate gradient'biCG-stab'
or'biCG-stab(1)'
: Biconjugate gradient stabilized, first order'biCG-stab(2)'
,'biCG-stab(4)'
, …: Biconjugate gradient stabilized, second or higher order'scipy-direct'
: SciPy direct solve always run oh the CPU usingscipy.sparse.linalg.spsolve
.'scipy-CG'
,'scipy-GMres'
,'scipy-biCG'
,'scipy-biCG-stab'
,'scipy-CGS'
,'scipy-QMR'
,'scipy-GCrotMK'
: SciPy iterative solvers always run oh the CPU, both in eager execution and JIT mode.
For maximum performance, compile
f
usingjit_compile_linear()
beforehand. Then, an optimized representation off
(such as a sparse matrix) will be used to solve the linear system.Caution: The matrix construction may potentially be performed each time
solve_linear()
is called if auxiliary arguments change. To prevent this, jit-compile the function that makes the call tosolve_linear()
.To obtain additional information about the performed solve, perform the solve within a
SolveTape
context. The used implementation can be obtained asSolveInfo.method
.The gradient of this operation will perform another linear solve with the parameters specified by
Solve.gradient_solve
.See Also:
solve_nonlinear()
,jit_compile_linear()
.Args
f
-
One of the following:
- Linear function with
Tensor
orphiml.math.magic.PhiTreeNode
first parameter and return value.f
can have additional auxiliary arguments and return auxiliary values. - Dense matrix (
Tensor
with at least one dual dimension) - Sparse matrix (Sparse
Tensor
with at least one dual dimension) - Native tensor (not yet supported)
- Linear function with
y
- Desired output of
f(x)
asTensor
orphiml.math.magic.PhiTreeNode
. solve
Solve
object specifying optimization method, parameters and initial guess forx
.*f_args
- Positional arguments to be passed to
f
aftersolve.x0
. These arguments will not be solved for. Supports vararg mode or pass all arguments as atuple
. f_kwargs
- Additional keyword arguments to be passed to
f
. These arguments are treated as auxiliary arguments and can be of any type.
Returns
x
- solution of the linear system of equations
f(x) = y
asTensor
orphiml.math.magic.PhiTreeNode
.
Raises
NotConverged
- If the desired accuracy was not be reached within the maximum number of iterations.
Diverged
- If the solve failed prematurely.
def solve_nonlinear(f: Callable, y, solve: phiml.math._optimize.Solve) ‑> phiml.math._tensors.Tensor
-
Solves the non-linear equation f(x) = y by minimizing the norm of the residual.
This method is limited to backends that support
jacobian()
, currently PyTorch, TensorFlow and Jax.To obtain additional information about the performed solve, use a
SolveTape
.See Also:
minimize()
,solve_linear()
.Args
f
- Function whose output is optimized to match
y
. All positional arguments off
are optimized and must beTensor
orphiml.math.magic.PhiTreeNode
. The output off
must matchy
. y
- Desired output of
f(x)
asTensor
orphiml.math.magic.PhiTreeNode
. solve
Solve
object specifying optimization method, parameters and initial guess forx
.
Returns
x
- Solution fulfilling
f(x) = y
within specified tolerance asTensor
orphiml.math.magic.PhiTreeNode
.
Raises
NotConverged
- If the desired accuracy was not be reached within the maximum number of iterations.
Diverged
- If the solve failed prematurely.
def spatial_gradient(field: phi.field._field.Field, boundary: phiml.math.extrapolation.Extrapolation = None, at: str = 'center', dims: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = <function spatial>, stack_dim: Union[phiml.math._shape.Shape, str] = (vectorᶜ=None), order=2, implicit: phiml.math._optimize.Solve = None, implicitness: int = None, scheme=None, upwind: phi.field._field.Field = None, gradient_extrapolation: phiml.math.extrapolation.Extrapolation = None)
-
Finite difference spatial_gradient.
This function can operate in two modes:
type=CenteredGrid
approximates the spatial_gradient at cell centers using central differencestype=StaggeredGrid
computes the spatial_gradient at face centers of neighbouring cells
Args
field
- centered grid of any number of dimensions (scalar field, vector field, tensor field)
boundary
- Boundary conditions of the gradient field.
at
- Either
'face'
or'center'
dims
- Along which dimensions to compute the spatial gradient. Only supported when
type==CenteredGrid
. stack_dim
- Dimension to be added. This dimension lists the spatial_gradient w.r.t. the spatial dimensions.
The
field
must not have a dimension of the same name. order
- Spatial order of accuracy. Higher orders entail larger stencils and more computation time but result in more accurate results assuming a large enough resolution. Supported: 2 explicit, 4 explicit, 6 implicit.
implicit
- When a
Solve
object is passed, performs an implicit operation with the specified solver and tolerances. Otherwise, an explicit stencil is used. implicitness
- specifies the size of the implicit stencil in case an implicit treatment is used
gradient_extrapolation
- Alias for
boundary
. scheme
- For unstructured meshes only. Currently only
'green-gauss'
is supported. upwind
- For unstructured meshes only. Whether to use upwind interpolation.
Returns
spatial_gradient field of type
type
. def sqrt(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Computes sqrt(x) of the
Tensor
orphiml.math.magic.PhiTreeNode
x
. def stack(fields: Sequence[phi.field._field.Field], dim: phiml.math._shape.Shape, dim_bounds: phi.geom._box.Box = None)
def stagger(field: phi.field._field.Field, face_function: Callable, boundary: float, at='face', dims=<function spatial>)
-
Creates a new grid by evaluating
face_function
given two neighbouring cells. One layer of missing cells is inferred from the extrapolation.This method returns a Field of type
type
which must be either StaggeredGrid or CenteredGrid. When returning a StaggeredGrid, the new values are sampled at the faces of neighbouring cells. When returning a CenteredGrid, the new grid has the same resolution asfield
.Args
field
- Grid
face_function
- function mapping (value1: Tensor, value2: Tensor) -> center_value: Tensor
boundary
- extrapolation mode of the returned grid. Has no effect on the values.
at
- Where the result should be sampled, one of 'face', 'center'
dims
- Which dimensions to stagger. Defaults to all spatial axes.
Returns
Grid sampled either at centers or faces depending on
at
. def stop_gradient(x)
-
Disables gradients for the given tensor. This may switch off the gradients for
x
itself or create a copy ofx
with disabled gradients.Implementations:
- PyTorch:
x.detach()
- TensorFlow:
tf.stop_gradient
- Jax:
jax.lax.stop_gradient
Args
x
Tensor
orphiml.math.magic.PhiTreeNode
for which gradients should be disabled.
Returns
Copy of
x
. - PyTorch:
def support(field: phi.field._field.Field, list_dim: phiml.math._shape.Shape = (nonzeroⁱ=None)) ‑> phiml.math._tensors.Tensor
-
Returns the points at which the field values are non-zero.
Args
field
Field
list_dim
- Dimension to list the non-zero values.
Returns
Tensor
with shape(list_dim, vector)
def to_float(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Converts the given tensor to floating point format with the currently specified precision.
The precision can be set globally using
math.set_global_precision()
and locally usingwith math.precision()
.See the documentation at https://tum-pbs.github.io/PhiML/Data_Types.html
See Also:
cast()
.Args
x
Tensor
orphiml.math.magic.PhiTreeNode
to convert
Returns
Tensor
orphiml.math.magic.PhiTreeNode
matchingx
. def to_int32(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Converts the
Tensor
orphiml.math.magic.PhiTreeNode
x
to 32-bit integer. def to_int64(x: ~TensorOrTree) ‑> ~TensorOrTree
-
Converts the
Tensor
orphiml.math.magic.PhiTreeNode
x
to 64-bit integer. def unstack(value, dim: Union[str, tuple, list, set, ForwardRef('Shape'), Callable]) ‑> tuple
-
Un-stacks a
Sliceable
along one or multiple dimensions.If multiple dimensions are given, the order of elements will be according to the dimension order in
dim
, i.e. elements along the last dimension will be neighbors in the returnedtuple
. If no dimension is given or none of the given dimensions exists onvalue
, returns a list containing onlyvalue
.See Also:
phiml.math.slice
.Args
value
phiml.math.magic.Shapable
, such asphiml.math.Tensor
dim
- Dimensions as
Shape
or comma-separatedstr
or dimension type, i.e.channel
,spatial
,instance
,batch
.
Returns
tuple
of objects matching the type ofvalue
.Examples
>>> unstack(expand(0, spatial(x=5)), 'x') (0.0, 0.0, 0.0, 0.0, 0.0)
def upsample2x(grid: phi.field._field.Field) ‑> phi.field._field.Field
-
Increases the number of sample points by a factor of 2 in each spatial dimension. The new values are determined via linear interpolation.
See Also:
downsample2x()
.Args
grid
CenteredGrid()
orStaggeredGrid()
.
Returns
Field
of same type asgrid
. def vec_abs(field: phi.field._field.Field)
-
See
phi.math.vec_abs()
def vec_length(field: phi.field._field.Field)
-
See
phi.math.vec_abs()
def vec_squared(field: phi.field._field.Field)
-
See
phi.math.vec_squared()
def where(mask: phi.field._field.Field, field_true: phi.field._field.Field, field_false: phi.field._field.Field) ‑> phi.field._field.Field
def write(field: phi.field._field.Field, file: Union[str, phiml.math._tensors.Tensor])
-
Writes a field to disc using a NumPy file format. Depending on
file
, the data may be split up into multiple files.All characteristics of the field are serialized so that it can be fully restored using
read()
.See Also:
read()
Args
field
- Field to be saved.
file
- Single file as
str
orTensor
of string type. Iffile
is a tensor, the dimensions offield
are matched to the dimensions offile
. Dimensions offile
that are missing infield
result in data duplication. Dimensions offield
that are missing infile
result in larger files.
Classes
class AngularVelocity (location: Union[phiml.math._tensors.Tensor, tuple, list, numbers.Number], strength: Union[phiml.math._tensors.Tensor, numbers.Number] = 1.0, falloff: Callable = None)
-
Model of a single vortex or set of vortices. The falloff of the velocity magnitude can be controlled.
Without a specified falloff, the velocity increases linearly with the distance from the vortex center. This is the case with rotating rigid bodies, for example.
Expand source code
class AngularVelocity(FieldInitializer): """ Model of a single vortex or set of vortices. The falloff of the velocity magnitude can be controlled. Without a specified falloff, the velocity increases linearly with the distance from the vortex center. This is the case with rotating rigid bodies, for example. """ def __init__(self, location: Union[Tensor, tuple, list, Number], strength: Union[Tensor, Number] = 1.0, falloff: Callable = None): location = wrap(location) strength = wrap(strength) assert location.shape.channel.names == ('vector',), "location must have a single channel dimension called 'vector'" assert location.shape.spatial.is_empty, "location tensor cannot have any spatial dimensions" assert not instance(location), "AngularVelocity does not support instance dimensions" self.location = location self.strength = strength self.falloff = falloff spatial_names = location.vector.item_names assert spatial_names is not None, "location.vector must list spatial dimensions as item names" self._shape = location.shape & spatial(**{dim: 1 for dim in spatial_names}) def _sample(self, geometry: Geometry, at: str, boundaries: Extrapolation, **kwargs) -> math.Tensor: points = get_sample_points(geometry, at, boundaries) distances = points - self.location strength = self.strength if self.falloff is None else self.strength * self.falloff(distances) velocity = math.cross_product(strength, distances) velocity = math.sum(velocity, self.location.shape.batch.without(points.shape)) return velocity
Ancestors
- phi.field._field.FieldInitializer
class Field (geometry: Union[phiml.math._tensors.Tensor, phi.geom._geom.Geometry], values: Union[phiml.math._tensors.Tensor, numbers.Number, bool, Callable, phi.field._field.FieldInitializer, phi.geom._geom.Geometry, ForwardRef('Field')], boundary: Union[numbers.Number, phiml.math.extrapolation.Extrapolation, ForwardRef('Field'), dict] = 0, **sampling_kwargs)
-
Base class for all fields.
Important implementations:
- CenteredGrid
- StaggeredGrid
- PointCloud
- Noise
See the
phi.field
module documentation at https://tum-pbs.github.io/PhiFlow/Fields.htmlArgs
elements
- Geometry object specifying the sample points and sizes
values
- values corresponding to elements
extrapolation
- values outside elements
Expand source code
class Field: """ Base class for all fields. Important implementations: * CenteredGrid * StaggeredGrid * PointCloud * Noise See the `phi.field` module documentation at https://tum-pbs.github.io/PhiFlow/Fields.html """ def __init__(self, geometry: Union[Geometry, Tensor], values: Union[Tensor, Number, bool, Callable, FieldInitializer, Geometry, 'Field'], boundary: Union[Number, Extrapolation, 'Field', dict] = 0, **sampling_kwargs): """ Args: elements: Geometry object specifying the sample points and sizes values: values corresponding to elements extrapolation: values outside elements """ assert isinstance(geometry, Geometry), f"geometry must be a Geometry object but got {type(geometry).__name__}" self._boundary: Extrapolation = as_boundary(boundary, geometry) self._geometry: Geometry = geometry if isinstance(values, (Tensor, Number, bool)): values = wrap(values) else: from ._resample import sample values = sample(values, geometry, 'center', self._boundary, **sampling_kwargs) matching_sets = [s for s, s_shape in geometry.sets.items() if s_shape in values.shape] if not matching_sets: values = expand(wrap(values), non_batch(geometry) - 'vector') self._values: Tensor = values math.merge_shapes(values, non_batch(self.sampled_elements).non_channel) # shape check @property def geometry(self) -> Geometry: """ Returns a geometrical representation of the discrete volume elements. The result is a tuple of Geometry objects, each of which can have additional spatial (but not batch) dimensions. For grids, the geometries are boxes while particle fields may be represented as spheres. If this Field has no discrete points, this method returns an empty geometry. """ return self._geometry @property def grid(self) -> UniformGrid: """Cast `self.geometry` to a `phi.geom.UniformGrid`.""" assert isinstance(self._geometry, UniformGrid), f"Geometry is not a UniformGrid but {type(self._geometry)}" return self._geometry @property def mesh(self) -> Mesh: """Cast `self.geometry` to a `phi.geom.Mesh`.""" assert isinstance(self._geometry, Mesh), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry @property def graph(self) -> Graph: """Cast `self.geometry` to a `phi.geom.Graph`.""" assert isinstance(self._geometry, Graph), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry @property def faces(self): return get_faces(self._geometry, self._boundary) @property def face_centers(self): return self._geometry.face_centers # return slice_off_constant_faces(self._geometry.face_centers, self._geometry.boundary_faces, self._boundary) @property def face_normals(self): return self._geometry.face_normals # return slice_off_constant_faces(self._geometry.face_normals, self._geometry.boundary_faces, self._boundary) @property def face_areas(self): return self._geometry.face_areas # return slice_off_constant_faces(self._geometry.face_areas, self._geometry.boundary_faces, self._boundary) @property def sampled_elements(self) -> Geometry: """ If the values represent are sampled at the element centers or represent the whole element, returns `self.geometry`. If the values are sampled at the faces, returns `self.faces`. """ return get_faces(self._geometry, self._boundary) if is_staggered(self._values, self._geometry) else self._geometry @property def elements(self): # raise SyntaxError("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead.") warnings.warn("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead. Field.elements now defaults to Field.geometry.", DeprecationWarning, stacklevel=2) return self._geometry @property def is_centered(self): return not self.is_staggered @property def is_staggered(self): return is_staggered(self._values, self._geometry) @property def center(self) -> Tensor: """ Returns the center points of the `elements` of this `Field`. """ all_points = self._geometry.get_points(self.sampled_at) boundary = self._geometry.get_boundary(self.sampled_at) return slice_off_constant_faces(all_points, boundary, self.extrapolation) @property def points(self): return self.center @property def values(self) -> Tensor: """ Returns the `values` of this `Field`. """ return self._values data = values def numpy(self, order: DimFilter = None): """ Return the field values as `NumPy` array(s). Args: order: Dimension order as `str` or `Shape`. Returns: A single NumPy array for uniform values, else a list of NumPy arrays. """ if order is None and self.is_grid: axes = self._values.shape.only(self._geometry.vector.item_names, reorder=True) order = concat_shapes(self._values.shape.dual, self._values.shape.batch, axes, self._values.shape.channel) if self._values.shape.is_uniform: return self._values.numpy(order) else: assert order is not None, f"order must be specified for non-uniform Field values" order = self._values.shape.only(order, reorder=True) stack_dims = order.non_uniform_shape inner_order = order.without(stack_dims) return [v.numpy(inner_order) for v in unstack(self._values, stack_dims)] def uniform_values(self): """ Returns a uniform tensor containing `values`. For periodic grids, which always have a uniform value tensor, `values' is returned directly. If `values` is not uniform, it is padded as in `StaggeredGrid.staggered_tensor()`. """ if self.values.shape.is_uniform: return self.values else: return self.staggered_tensor() @property def boundary(self) -> Extrapolation: """ Returns the boundary conditions set for this `Field`. Returns: Single `Extrapolation` instance that encodes the (varying) boundary conditions for all boundaries of this field's `elements`. """ return self._boundary @property def extrapolation(self) -> Extrapolation: """ Returns the `Extrapolation` of this `Field`. """ return self._boundary @property def shape(self) -> Shape: """ Returns a shape with the following properties * The spatial dimension names match the dimensions of this Field * The batch dimensions match the batch dimensions of this Field * The channel dimensions match the channels of this Field """ if self.is_grid and '~vector' in self._values.shape: return batch(self._geometry) & self.resolution & non_dual(self._values).without(self.resolution) & self._geometry.shape['vector'] set_shape = self._geometry.sets[self.sampled_at] return batch(self._geometry) & (channel(self._geometry) - 'vector') & set_shape & self._values @property def resolution(self): return self._geometry.shape.non_channel.non_dual.non_batch @property def spatial_rank(self) -> int: """ Spatial rank of the field (1 for 1D, 2 for 2D, 3 for 3D). This is equal to the spatial rank of the `data`. """ return self._geometry.spatial_rank @property def bounds(self) -> BaseBox: """ The bounds represent the area inside which the values of this `Field` are valid. The bounds will also be used as axis limits for plots. The bounds can be set manually in the constructor, otherwise default bounds will be generated. For fields that are valid without bounds, the lower and upper limit of `bounds` is set to `-inf` and `inf`, respectively. Fields whose spatial rank is determined only during sampling return an empty `Box`. """ if isinstance(self._geometry.bounds, BaseBox): return self._geometry.bounds extent = self._geometry.bounding_half_extent().vector.as_dual('_extent') points = self._geometry.center + extent lower = math.min(points, dim=points.shape.non_batch.non_channel) upper = math.max(points, dim=points.shape.non_batch.non_channel) return Box(lower, upper) box = bounds @property def is_grid(self): """A Field represents grid data if its `geometry` is a `phi.geom.UniformGrid` instance.""" return isinstance(self._geometry, UniformGrid) @property def is_mesh(self): """A Field represents mesh data if its `geometry` is a `phi.geom.Mesh` instance.""" return isinstance(self._geometry, Mesh) @property def is_graph(self): """A Field represents graph data if its `geometry` is a `phi.geom.Graph` instance.""" return isinstance(self._geometry, Graph) @property def is_point_cloud(self): """A Field represents graph data if its `geometry` is not a set of connected elements, but rather individual geometric objects.""" if isinstance(self._geometry, (UniformGrid, Mesh, Graph)): return False if isinstance(self._geometry, (BaseBox, Sphere, Point)): return True return True @property def dx(self) -> Tensor: assert spatial(self._geometry), f"dx is only defined for elements with spatial dims but Field has elements {self._geometry.shape}" return self.bounds.size / self.resolution @property def cells(self): assert isinstance(self._geometry, (UniformGrid, Mesh)) return self._geometry def to_grid(self, resolution=math.EMPTY_SHAPE, bounds=None, **resolution_): resolution = resolution.spatial & spatial(**resolution_) if self.is_grid and (not resolution or resolution == self.resolution) and (bounds is None or bounds == self.bounds): return self bounds = self.bounds if bounds is None else bounds if not resolution: half_sizes = self._geometry.bounding_half_extent() if (half_sizes > 0).all: size = math.min(2 * half_sizes, non_batch(half_sizes).non_channel) else: cell_count = non_batch(self._geometry).non_channel.non_dual.volume size = (bounds.volume / cell_count) ** (1 / self.spatial_rank) res = math.maximum(1, math.round(bounds.size / size)) resolution = spatial(**res.vector) return Field(UniformGrid(resolution, bounds), self, self.boundary) def as_points(self, list_dim: Optional[Shape] = instance('elements')) -> 'Field': """ Returns this field as a PointCloud. This replaces the `Field.geometry` with a `phi.geom.Point` instance while leaving the sample points unchanged. See Also: `Field.as_spheres()`. Args: list_dim: If not `None`, packs spatial, instance and dual dims. Defaults to `instance('elements')`. Returns: `Field` with same values and boundaries but `Point` geometry. """ points = self.sampled_elements.center values = self._values if list_dim: dims = non_batch(points).non_channel & non_batch(points).non_channel points = pack_dims(points, dims, list_dim) values = pack_dims(values, dims, list_dim) return Field(Point(points), values, self._boundary) def as_spheres(self, list_dim: Optional[Shape] = instance('elements')) -> 'Field': """ Returns this field as a PointCloud with spherical / circular elements, preserving element volumes. This replaces the `Field.geometry` with a `phi.geom.Sphere` instance while leaving the sample points unchanged. See Also: `Field.as_points()`. Args: list_dim: If not `None`, packs spatial, instance and dual dims. Defaults to `instance('elements')`. Returns: `Field` with same values and boundaries but `Sphere` geometry. """ points = self.sampled_elements.center volumes = self.sampled_elements.volume values = self._values if list_dim: dims = non_batch(points).non_channel & non_batch(points).non_channel points = pack_dims(points, dims, list_dim) values = pack_dims(values, dims, list_dim) volumes = pack_dims(volumes, dims, list_dim) return Field(Sphere(points, volume=volumes), values, self._boundary) def at_centers(self, **kwargs) -> 'Field': """ Interpolates the values to the cell centers. See Also: `Field.at_faces()`, `Field.at()`, `resample`. Args: **kwargs: Sampling arguments. Returns: `CenteredGrid` sampled at cell centers. """ if self.is_centered: return self from ._resample import sample values = sample(self, self._geometry, at='center', boundary=self._boundary, **kwargs) return Field(self._geometry, values, self._boundary) def at_faces(self, boundary=None, **kwargs) -> 'Field': if self.is_staggered and not boundary: return self boundary = as_boundary(boundary, self._geometry) if boundary else self._boundary from ._resample import sample values = sample(self, self._geometry, at='face', boundary=boundary, **kwargs) return Field(self._geometry, values, boundary) @property def sampled_at(self): matching_sets = [s for s, s_shape in self._geometry.sets.items() if s_shape in self._values.shape] return matching_sets[-1] def at(self, representation: Union['Field', Geometry], keep_boundary=False, **kwargs) -> 'Field': """ Short for `resample(self, representation)` See Also `resample()`. Returns: Field object of same type as `representation` """ from ._resample import resample return resample(self, representation, keep_boundary, **kwargs) def sample(self, where: Union[Geometry, 'Field', Tensor], at: str = 'center', **kwargs) -> 'Tensor': """ Sample the values of this `Field` at the given location or geometry. Args: where: Location `Tensor` or `Geometry` or at: `'center'` or `'face'`. **kwargs: Sampling arguments. Returns: `Tensor` """ from ._resample import sample return sample(self, where, at, **kwargs) def closest_values(self, points: Tensor): """ Sample the closest grid point values of this field at the world-space locations (in physical units) given by `points`. Points must have a single channel dimension named `vector`. It may additionally contain any number of batch and spatial dimensions, all treated as batch dimensions. Args: points: world-space locations Returns: Closest grid point values as a `Tensor`. For each dimension, the grid points immediately left and right of the sample points are evaluated. For each point in `points`, a *2^d* cube of points is determined where *d* is the number of spatial dimensions of this field. These values are stacked along the new dimensions `'closest_<dim>'` where `<dim>` refers to the name of a spatial dimension. """ warnings.warn("Field.closest_values() is deprecated.", DeprecationWarning, stacklevel=2) if isinstance(points, Geometry): points = points.center # --- CenteredGrid --- local_points = self.box.global_to_local(points) * self.resolution - 0.5 return math.closest_grid_values(self.values, local_points, self.extrapolation) # --- StaggeredGrid --- if 'staggered_direction' in points.shape: points_ = math.unstack(points, '~vector') channels = [component.closest_values(p) for p, component in zip(points_, self.vector.unstack())] else: channels = [component.closest_values(points) for component in self.vector.unstack()] return math.stack(channels, points.shape['~vector']) def with_values(self, values, **sampling_kwargs): """ Returns a copy of this field with `values` replaced. """ if not isinstance(values, (Tensor, Number)): from ._resample import sample values = sample(values, self._geometry, self.sampled_at, self._boundary, dot_face_normal=self._geometry if 'vector' not in self._values.shape else None, **sampling_kwargs) else: if not spatial(values): geo_shape = self.sampled_elements.shape if self.is_staggered else self._geometry.shape if '~vector' in geo_shape and 'vector' in shape(values) and '~vector' not in shape(values): values = values.vector.as_dual() values = expand(wrap(values), geo_shape.non_batch.non_channel) return Field(self._geometry, values, self._boundary) def with_boundary(self, boundary): """ Returns a copy of this field with the `boundary` replaced. """ boundary = as_boundary(boundary, self._geometry) boundary_elements = 'boundary_faces' if self.is_staggered else 'boundary_elements' old_determined_slices = {k: s for k, s in getattr(self._geometry, boundary_elements).items() if self._boundary.determines_boundary_values(k)} new_determined_slices = {k: s for k, s in getattr(self._geometry, boundary_elements).items() if boundary.determines_boundary_values(k)} if old_determined_slices.values() == new_determined_slices.values(): return Field(self._geometry, self._values, boundary) # ToDo unnecessary once the rest is implemented to_add = {k: sl for k, sl in old_determined_slices.items() if sl not in new_determined_slices.values()} to_remove = [sl for sl in new_determined_slices.values() if sl not in old_determined_slices.values()] values = math.slice_off(self._values, *to_remove) if to_add: if self.is_mesh: values = self.mesh.pad_boundary(values, to_add, self._boundary) elif self.is_grid and self.is_staggered: values = self._values.vector.dual.as_channel() to_add = {k: {'vector' if dim == '~vector' else dim: v for dim, v in sl.items()} for k, sl in to_add.items()} values = math.pad(values, list(to_add.values()), self._boundary, bounds=self.bounds) values = values.vector.as_dual() else: values = math.pad(values, list(to_add.values()), self._boundary, bounds=self.bounds) return Field(self._geometry, values, boundary) with_extrapolation = with_boundary def with_bounds(self, bounds: Box): """ Returns a copy of this field with `bounds` replaced. """ order = list(bounds.vector.item_names) geometry = self._geometry.vector[order] new_shape = self._values.shape.without(order) & self._values.shape.only(order, reorder=True) values = math.transpose(self._values, new_shape) return Field(geometry, values, self._boundary) def with_geometry(self, elements: Geometry): """ Returns a copy of this field with `elements` replaced. """ assert non_batch(elements) == non_batch(self._geometry), f"Field.with_elements() only accepts elements with equal non-batch dimensions but got {elements.shape} for Field with shape {self._geometry.shape}" return Field(elements, self._values, self._boundary) with_elements = with_geometry def shifted(self, delta: Tensor) -> 'Field': """ Move the positions of this field's `geometry` by `delta`. See Also: `Field.shifted_to`. Args: delta: Shift amount for each center position of `geometry`. Returns: New `Field` sampled at `geometry.center + delta`. """ return self.with_geometry(self._geometry.shifted(delta)) def shifted_to(self, position: Tensor) -> 'Field': """ Move the positions of this field's `geometry` to `positions`. See Also: `Field.shifted`. Args: position: New center positions of `geometry`. Returns: New `Field` sampled at given positions. """ return self.with_geometry(self._geometry.at(position)) def pad(self, widths: Union[int, tuple, list, dict]) -> 'Field': """ Alias for `phi.field.pad()`. Pads this `Field` using its extrapolation. Unlike padding the values, this function also affects the `geometry` of the field, changing its size and origin depending on `widths`. Args: widths: Either `int` or `(lower, upper)` to pad the same number of cells in all spatial dimensions or `dict` mapping dimension names to `(lower, upper)`. Returns: Padded `Field` """ from ._field_math import pad return pad(self, widths) def gradient(self, boundary: Extrapolation = None, at: str = 'center', dims: math.DimFilter = spatial, stack_dim: Union[Shape, str] = channel('vector'), order=2, implicit: Solve = None, scheme=None, upwind: 'Field' = None, gradient_extrapolation: Extrapolation = None): """Alias for `phi.field.spatial_gradient`""" from ._field_math import spatial_gradient return spatial_gradient(self, boundary=boundary, at=at, dims=dims, stack_dim=stack_dim, order=order, implicit=implicit, scheme=scheme, upwind=upwind, gradient_extrapolation=gradient_extrapolation) def divergence(self, order=2, implicit: Solve = None, upwind: 'Field' = None): """Alias for `phi.field.divergence`""" from ._field_math import divergence return divergence(self, order=order, implicit=implicit, upwind=upwind) def curl(self, at='corner'): """Alias for `phi.field.curl`""" from ._field_math import curl return curl(self, at=at) def laplace(self, axes: DimFilter = spatial, gradient: 'Field' = None, order=2, implicit: math.Solve = None, weights: Union[Tensor, 'Field'] = None, upwind: 'Field' = None, correct_skew=True): """Alias for `phi.field.laplace`""" from ._field_math import laplace return laplace(self, axes=axes, gradient=gradient, order=order, implicit=implicit, weights=weights, upwind=upwind, correct_skew=correct_skew) def downsample(self, factor: int): from ._field_math import downsample2x result = self while factor >= 2: result = downsample2x(result) factor /= 2 if math.close(factor, 1.): return result from ._resample import resample raise NotImplementedError(f"downsample does not support fractional re-sampling. Only 2^n currently supported.") def staggered_tensor(self) -> Tensor: """ Stacks all component grids into a single uniform `phi.math.Tensor`. The individual components are padded to a common (larger) shape before being stacked. The shape of the returned tensor is exactly one cell larger than the grid `resolution` in every spatial dimension. Returns: Uniform `phi.math.Tensor`. """ assert self.resolution.names == self.shape.get_item_names('vector'), "Field.staggered_tensor() only defined for Fields whose vector components match the resolution" padded = [] for dim, component in zip(self.resolution.names, self.vector): widths = {d: (0, 1) for d in self.resolution.names} lo_valid, up_valid = self.extrapolation.valid_outer_faces(dim) widths[dim] = (int(not lo_valid), int(not up_valid)) padded.append(math.pad(component.values, widths, self.extrapolation[{'vector': dim}], bounds=self.bounds)) result = math.stack(padded, channel(vector=self.resolution)) assert result.shape.is_uniform return result @staticmethod def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Field': from ._field_math import stack return stack(values, dim, kwargs.get('bounds', None)) @staticmethod def __concat__(values: tuple, dim: str, **kwargs) -> 'Field': from ._field_math import concat return concat(values, dim) def __and__(self, other): assert isinstance(other, Field) assert instance(self).rank == instance(other).rank == 1, f"Can only use & on PointClouds that have a single instance dimension but got shapes {self.shape} & {other.shape}" from ._field_math import concat return concat([self, other], instance(self)) def __matmul__(self, other: 'Field'): # value @ representation # Deprecated. Use `resample(value, field)` instead. warnings.warn("value @ field is deprecated. Use resample(value, field) instead.", DeprecationWarning) from ._resample import resample return resample(self, to=other, keep_boundary=False) def __rmatmul__(self, other): # values @ representation if isinstance(other, (Geometry, Number, tuple, list, FieldInitializer)): warnings.warn("value @ field is deprecated. Use resample(value, field) instead.", DeprecationWarning) from ._resample import resample return resample(other, to=self, keep_boundary=False) return NotImplemented def __rshift__(self, other): if isinstance(other, (Field, Geometry)): warnings.warn(">> operator for Fields is deprecated. Use field.at(), the constructor or obj @ field instead.", SyntaxWarning, stacklevel=2) return self.at(other, keep_boundary=False) else: return NotImplemented def __rrshift__(self, other): return self.with_values(other) def __lshift__(self, other): return self.with_values(other) def __rrshift__(self, other): warnings.warn(">> operator for Fields is deprecated. Use field.at(), the constructor or obj @ field instead.", SyntaxWarning, stacklevel=2) if not isinstance(self, Field): return NotImplemented if isinstance(other, (Geometry, float, int, complex, tuple, list, FieldInitializer)): from ._resample import resample return resample(other, to=self, keep_boundary=False) return NotImplemented def __getitem__(self, item) -> 'Field': """ Access a slice of the Field. The returned `Field` may be of a different type than `self`. Args: item: `dict` mapping dimensions (`str`) to selections (`int` or `slice`) or other supported type, such as `int` or `str`. Returns: Sliced `Field`. """ item = slicing_dict(self, item) if not item: return self boundary = domain_slice(self._boundary, item, self.resolution) item_without_vec = {dim: selection for dim, selection in item.items() if dim != 'vector'} geometry = self._geometry[item_without_vec] if self.is_staggered and 'vector' in item and '~vector' in self.geometry.face_shape: assert isinstance(self._geometry, UniformGrid), f"Vector slicing is only supported for grids" dims = item['vector'] dims_ = self._geometry.shape['vector'].after_gather({'vector': dims}) dims = dims_.item_names[0] if dims_ else [dims] if isinstance(dims, str) else [self._geometry.shape['vector'].item_names[0][dims]] proj_dims = set(self.resolution.names) - set(dims) if any(dim not in item for dim in proj_dims): # warnings.warn(f"Projecting a staggered grid (by slicing 'vector' without the corresponding spatial dims) will return a non-staggered grid. The projected dims {proj_dims} were not sliced off.\nFull slice: {item}") item['~vector'] = item['vector'] del item['vector'] geometry = self.sampled_elements[item] else: item['~vector'] = dims del item['vector'] values = self._values[item] return Field(geometry, values, boundary) def __getattr__(self, name: str) -> BoundDim: return BoundDim(self, name) def dimension(self, name: str): """ Returns a reference to one of the dimensions of this field. The dimension reference can be used the same way as a `Tensor` dimension reference. Notable properties and methods of a dimension reference are: indexing using `[index]`, `unstack()`, `size`, `exists`, `is_batch`, `is_spatial`, `is_channel`. A shortcut to calling this function is the syntax `field.<dim_name>` which calls `field.dimension(<dim_name>)`. Args: name: dimension name Returns: dimension reference """ return BoundDim(self, name) def __value_attrs__(self): return '_values', def __variable_attrs__(self): return '_values', '_geometry', '_boundary' def __expand__(self, dims: Shape, **kwargs) -> 'Field': return self.with_values(expand(self.values, dims, **kwargs)) def __replace_dims__(self, dims: Tuple[str, ...], new_dims: Shape, **kwargs) -> 'Field': elements = math.rename_dims(self._geometry, dims, new_dims) values = math.rename_dims(self._values, dims, new_dims) extrapolation = math.rename_dims(self._boundary, dims, new_dims, **kwargs) return Field(elements, values, extrapolation) def __eq__(self, other): if not isinstance(other, Field): return False if self._geometry != other._geometry: return False if self._boundary != other.boundary: return False return math.always_close(self._values, other._values) def __hash__(self): return hash((self._geometry, self._boundary)) def __mul__(self, other): return self._op2(other, lambda d1, d2: d1 * d2) __rmul__ = __mul__ def __truediv__(self, other): return self._op2(other, lambda d1, d2: d1 / d2) def __rtruediv__(self, other): return self._op2(other, lambda d1, d2: d2 / d1) def __sub__(self, other): return self._op2(other, lambda d1, d2: d1 - d2) def __rsub__(self, other): return self._op2(other, lambda d1, d2: d2 - d1) def __add__(self, other): return self._op2(other, lambda d1, d2: d1 + d2) __radd__ = __add__ def __pow__(self, power, modulo=None): return self._op2(power, lambda f, p: f ** p) def __neg__(self): return self._op1(lambda x: -x) def __gt__(self, other): return self._op2(other, lambda x, y: x > y) def __ge__(self, other): return self._op2(other, lambda x, y: x >= y) def __lt__(self, other): return self._op2(other, lambda x, y: x < y) def __le__(self, other): return self._op2(other, lambda x, y: x <= y) def __abs__(self): return self._op1(lambda x: abs(x)) def _op1(self: 'Field', operator: Callable) -> 'Field': """ Perform an operation on the data of this field. Args: operator: function that accepts tensors and extrapolations and returns objects of the same type and dimensions Returns: Field of same type """ values = operator(self.values) extrapolation_ = operator(self._boundary) return self.with_values(values).with_extrapolation(extrapolation_) def _op2(self, other, operator) -> 'Field': if isinstance(other, Geometry): raise ValueError(f"Cannot combine {self.__class__.__name__} with a Geometry, got {type(other)}") if isinstance(other, Field): if self._geometry == other._geometry: values = operator(self._values, other.values) extrapolation_ = operator(self._boundary, other.extrapolation) return Field(self._geometry, values, extrapolation_) from ._resample import sample other_values = sample(other, self._geometry, self.sampled_at, self.boundary, dot_face_normal=self._geometry) values = operator(self._values, other_values) boundary = operator(self._boundary, other.extrapolation) return Field(self._geometry, values, boundary) else: if isinstance(other, (tuple, list)) and len(other) == self.spatial_rank: other = math.wrap(other, self._geometry.shape['vector']) else: other = math.wrap(other) # try: # boundary = operator(self._boundary, as_boundary(other, self._geometry)) # except TypeError: # e.g. ZERO_GRADIENT + constant boundary = self._boundary # constants don't affect the boundary conditions (legacy reasons) if 'vector' in self.shape and 'vector' not in self.values.shape and '~vector' in self.values.shape: other = other.vector.as_dual() values = operator(self._values, other) return Field(self._geometry, values, boundary) def __repr__(self): if self.is_grid: type_name = "Grid" if self.is_centered else "Grid faces" elif self.is_mesh: type_name = "Mesh" if self.is_centered else "Mesh faces" elif self.is_point_cloud: type_name = "Point cloud" if self.is_centered else "Point cloud edges" elif self.is_graph: type_name = "Graph" if self.is_centered else "Graph edges" else: type_name = self.__class__.__name__ if self._values is not None: return f"{type_name}[{self.values}, ext={self._boundary}]" else: return f"{type_name}[{self.resolution}, ext={self._boundary}]" def grid_scatter(self, *args, **kwargs): """Deprecated. Use `sample` with `scatter=True` instead.""" warnings.warn("Field.grid_scatter() is deprecated. Use field.sample() with scatter=True instead.", DeprecationWarning, stacklevel=2) from ._resample import grid_scatter return grid_scatter(self, *args, **kwargs) def as_boundary(self) -> Extrapolation: """ Returns an `Extrapolation` representing this 'Field''s values as a Dirichlet (constant) boundary. If this `Field` encloses the required boundaries, its values will be interpolated to the required boundaries. If boundaries outside of this `Field`'s sampled domain are required, this `Field`'s boundary conditions will be applied to determine the boundary values. Returns: `Extrapolation` """ from ._embed import FieldEmbedding return FieldEmbedding(self)
Subclasses
- phi.field._mask.HardGeometryMask
Instance variables
prop boundary : phiml.math.extrapolation.Extrapolation
-
Returns the boundary conditions set for this
Field
.Returns
Single
Extrapolation
instance that encodes the (varying) boundary conditions for all boundaries of this field'selements
.Expand source code
@property def boundary(self) -> Extrapolation: """ Returns the boundary conditions set for this `Field`. Returns: Single `Extrapolation` instance that encodes the (varying) boundary conditions for all boundaries of this field's `elements`. """ return self._boundary
prop bounds : phi.geom._box.BaseBox
-
The bounds represent the area inside which the values of this
Field
are valid. The bounds will also be used as axis limits for plots.The bounds can be set manually in the constructor, otherwise default bounds will be generated.
For fields that are valid without bounds, the lower and upper limit of
bounds
is set to-inf
andinf
, respectively.Fields whose spatial rank is determined only during sampling return an empty
Box
.Expand source code
@property def bounds(self) -> BaseBox: """ The bounds represent the area inside which the values of this `Field` are valid. The bounds will also be used as axis limits for plots. The bounds can be set manually in the constructor, otherwise default bounds will be generated. For fields that are valid without bounds, the lower and upper limit of `bounds` is set to `-inf` and `inf`, respectively. Fields whose spatial rank is determined only during sampling return an empty `Box`. """ if isinstance(self._geometry.bounds, BaseBox): return self._geometry.bounds extent = self._geometry.bounding_half_extent().vector.as_dual('_extent') points = self._geometry.center + extent lower = math.min(points, dim=points.shape.non_batch.non_channel) upper = math.max(points, dim=points.shape.non_batch.non_channel) return Box(lower, upper)
prop box : phi.geom._box.BaseBox
-
The bounds represent the area inside which the values of this
Field
are valid. The bounds will also be used as axis limits for plots.The bounds can be set manually in the constructor, otherwise default bounds will be generated.
For fields that are valid without bounds, the lower and upper limit of
bounds
is set to-inf
andinf
, respectively.Fields whose spatial rank is determined only during sampling return an empty
Box
.Expand source code
@property def bounds(self) -> BaseBox: """ The bounds represent the area inside which the values of this `Field` are valid. The bounds will also be used as axis limits for plots. The bounds can be set manually in the constructor, otherwise default bounds will be generated. For fields that are valid without bounds, the lower and upper limit of `bounds` is set to `-inf` and `inf`, respectively. Fields whose spatial rank is determined only during sampling return an empty `Box`. """ if isinstance(self._geometry.bounds, BaseBox): return self._geometry.bounds extent = self._geometry.bounding_half_extent().vector.as_dual('_extent') points = self._geometry.center + extent lower = math.min(points, dim=points.shape.non_batch.non_channel) upper = math.max(points, dim=points.shape.non_batch.non_channel) return Box(lower, upper)
prop cells
-
Expand source code
@property def cells(self): assert isinstance(self._geometry, (UniformGrid, Mesh)) return self._geometry
prop center : phiml.math._tensors.Tensor
-
Returns the center points of the
elements
of thisField
.Expand source code
@property def center(self) -> Tensor: """ Returns the center points of the `elements` of this `Field`. """ all_points = self._geometry.get_points(self.sampled_at) boundary = self._geometry.get_boundary(self.sampled_at) return slice_off_constant_faces(all_points, boundary, self.extrapolation)
prop data : phiml.math._tensors.Tensor
-
Returns the
values
of thisField
.Expand source code
@property def values(self) -> Tensor: """ Returns the `values` of this `Field`. """ return self._values
prop dx : phiml.math._tensors.Tensor
-
Expand source code
@property def dx(self) -> Tensor: assert spatial(self._geometry), f"dx is only defined for elements with spatial dims but Field has elements {self._geometry.shape}" return self.bounds.size / self.resolution
prop elements
-
Expand source code
@property def elements(self): # raise SyntaxError("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead.") warnings.warn("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead. Field.elements now defaults to Field.geometry.", DeprecationWarning, stacklevel=2) return self._geometry
prop extrapolation : phiml.math.extrapolation.Extrapolation
-
Returns the
Extrapolation
of thisField
.Expand source code
@property def extrapolation(self) -> Extrapolation: """ Returns the `Extrapolation` of this `Field`. """ return self._boundary
prop face_areas
-
Expand source code
@property def face_areas(self): return self._geometry.face_areas # return slice_off_constant_faces(self._geometry.face_areas, self._geometry.boundary_faces, self._boundary)
prop face_centers
-
Expand source code
@property def face_centers(self): return self._geometry.face_centers # return slice_off_constant_faces(self._geometry.face_centers, self._geometry.boundary_faces, self._boundary)
prop face_normals
-
Expand source code
@property def face_normals(self): return self._geometry.face_normals # return slice_off_constant_faces(self._geometry.face_normals, self._geometry.boundary_faces, self._boundary)
prop faces
-
Expand source code
@property def faces(self): return get_faces(self._geometry, self._boundary)
prop geometry : phi.geom._geom.Geometry
-
Returns a geometrical representation of the discrete volume elements. The result is a tuple of Geometry objects, each of which can have additional spatial (but not batch) dimensions.
For grids, the geometries are boxes while particle fields may be represented as spheres.
If this Field has no discrete points, this method returns an empty geometry.
Expand source code
@property def geometry(self) -> Geometry: """ Returns a geometrical representation of the discrete volume elements. The result is a tuple of Geometry objects, each of which can have additional spatial (but not batch) dimensions. For grids, the geometries are boxes while particle fields may be represented as spheres. If this Field has no discrete points, this method returns an empty geometry. """ return self._geometry
prop graph : phi.geom._graph.Graph
-
Cast
self.geometry
to aGraph
.Expand source code
@property def graph(self) -> Graph: """Cast `self.geometry` to a `phi.geom.Graph`.""" assert isinstance(self._geometry, Graph), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry
prop grid : phi.geom._grid.UniformGrid
-
Cast
self.geometry
to aUniformGrid
.Expand source code
@property def grid(self) -> UniformGrid: """Cast `self.geometry` to a `phi.geom.UniformGrid`.""" assert isinstance(self._geometry, UniformGrid), f"Geometry is not a UniformGrid but {type(self._geometry)}" return self._geometry
prop is_centered
-
Expand source code
@property def is_centered(self): return not self.is_staggered
prop is_graph
-
A Field represents graph data if its
geometry
is aGraph
instance.Expand source code
@property def is_graph(self): """A Field represents graph data if its `geometry` is a `phi.geom.Graph` instance.""" return isinstance(self._geometry, Graph)
prop is_grid
-
A Field represents grid data if its
geometry
is aUniformGrid
instance.Expand source code
@property def is_grid(self): """A Field represents grid data if its `geometry` is a `phi.geom.UniformGrid` instance.""" return isinstance(self._geometry, UniformGrid)
prop is_mesh
-
A Field represents mesh data if its
geometry
is aMesh
instance.Expand source code
@property def is_mesh(self): """A Field represents mesh data if its `geometry` is a `phi.geom.Mesh` instance.""" return isinstance(self._geometry, Mesh)
prop is_point_cloud
-
A Field represents graph data if its
geometry
is not a set of connected elements, but rather individual geometric objects.Expand source code
@property def is_point_cloud(self): """A Field represents graph data if its `geometry` is not a set of connected elements, but rather individual geometric objects.""" if isinstance(self._geometry, (UniformGrid, Mesh, Graph)): return False if isinstance(self._geometry, (BaseBox, Sphere, Point)): return True return True
prop is_staggered
-
Expand source code
@property def is_staggered(self): return is_staggered(self._values, self._geometry)
prop mesh : phi.geom._mesh.Mesh
-
Cast
self.geometry
to aMesh
.Expand source code
@property def mesh(self) -> Mesh: """Cast `self.geometry` to a `phi.geom.Mesh`.""" assert isinstance(self._geometry, Mesh), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry
prop points
-
Expand source code
@property def points(self): return self.center
prop resolution
-
Expand source code
@property def resolution(self): return self._geometry.shape.non_channel.non_dual.non_batch
prop sampled_at
-
Expand source code
@property def sampled_at(self): matching_sets = [s for s, s_shape in self._geometry.sets.items() if s_shape in self._values.shape] return matching_sets[-1]
prop sampled_elements : phi.geom._geom.Geometry
-
If the values represent are sampled at the element centers or represent the whole element, returns
self.geometry
. If the values are sampled at the faces, returnsself.faces
.Expand source code
@property def sampled_elements(self) -> Geometry: """ If the values represent are sampled at the element centers or represent the whole element, returns `self.geometry`. If the values are sampled at the faces, returns `self.faces`. """ return get_faces(self._geometry, self._boundary) if is_staggered(self._values, self._geometry) else self._geometry
prop shape : phiml.math._shape.Shape
-
Returns a shape with the following properties
- The spatial dimension names match the dimensions of this Field
- The batch dimensions match the batch dimensions of this Field
- The channel dimensions match the channels of this Field
Expand source code
@property def shape(self) -> Shape: """ Returns a shape with the following properties * The spatial dimension names match the dimensions of this Field * The batch dimensions match the batch dimensions of this Field * The channel dimensions match the channels of this Field """ if self.is_grid and '~vector' in self._values.shape: return batch(self._geometry) & self.resolution & non_dual(self._values).without(self.resolution) & self._geometry.shape['vector'] set_shape = self._geometry.sets[self.sampled_at] return batch(self._geometry) & (channel(self._geometry) - 'vector') & set_shape & self._values
prop spatial_rank : int
-
Spatial rank of the field (1 for 1D, 2 for 2D, 3 for 3D). This is equal to the spatial rank of the
data
.Expand source code
@property def spatial_rank(self) -> int: """ Spatial rank of the field (1 for 1D, 2 for 2D, 3 for 3D). This is equal to the spatial rank of the `data`. """ return self._geometry.spatial_rank
prop values : phiml.math._tensors.Tensor
-
Returns the
values
of thisField
.Expand source code
@property def values(self) -> Tensor: """ Returns the `values` of this `Field`. """ return self._values
Methods
def as_boundary(self) ‑> phiml.math.extrapolation.Extrapolation
-
Returns an
Extrapolation
representing this 'Field''s values as a Dirichlet (constant) boundary. If thisField
encloses the required boundaries, its values will be interpolated to the required boundaries. If boundaries outside of thisField
's sampled domain are required, thisField
's boundary conditions will be applied to determine the boundary values.Returns
Extrapolation
def as_points(self, list_dim: Optional[phiml.math._shape.Shape] = (elementsⁱ=None)) ‑> phi.field._field.Field
-
Returns this field as a PointCloud. This replaces the
Field.geometry
with aPoint
instance while leaving the sample points unchanged.See Also:
Field.as_spheres()
.Args
list_dim
- If not
None
, packs spatial, instance and dual dims. Defaults toinstance('elements')
.
Returns
Field
with same values and boundaries butPoint
geometry. def as_spheres(self, list_dim: Optional[phiml.math._shape.Shape] = (elementsⁱ=None)) ‑> phi.field._field.Field
-
Returns this field as a PointCloud with spherical / circular elements, preserving element volumes. This replaces the
Field.geometry
with aSphere
instance while leaving the sample points unchanged.See Also:
Field.as_points()
.Args
list_dim
- If not
None
, packs spatial, instance and dual dims. Defaults toinstance('elements')
.
Returns
Field
with same values and boundaries butSphere
geometry. def at(self, representation: Union[ForwardRef('Field'), phi.geom._geom.Geometry], keep_boundary=False, **kwargs) ‑> phi.field._field.Field
-
Short for
resample()(self, representation)
See Also
resample()
.Returns
Field object of same type as
representation
def at_centers(self, **kwargs) ‑> phi.field._field.Field
-
Interpolates the values to the cell centers.
See Also:
Field.at_faces()
,Field.at()
,resample()
.Args
**kwargs
- Sampling arguments.
Returns
CenteredGrid()
sampled at cell centers. def at_faces(self, boundary=None, **kwargs) ‑> phi.field._field.Field
def closest_values(self, points: phiml.math._tensors.Tensor)
-
Sample the closest grid point values of this field at the world-space locations (in physical units) given by
points
. Points must have a single channel dimension namedvector
. It may additionally contain any number of batch and spatial dimensions, all treated as batch dimensions.Args
points
- world-space locations
Returns
Closest grid point values as a
Tensor
. For each dimension, the grid points immediately left and right of the sample points are evaluated. For each point inpoints
, a 2^d cube of points is determined where d is the number of spatial dimensions of this field. These values are stacked along the new dimensions'closest_<dim>'
where<dim>
refers to the name of a spatial dimension. def curl(self, at='corner')
-
Alias for
curl()
def dimension(self, name: str)
-
Returns a reference to one of the dimensions of this field.
The dimension reference can be used the same way as a
Tensor
dimension reference. Notable properties and methods of a dimension reference are: indexing using[index]
,unstack()
,size
,exists
,is_batch
,is_spatial
,is_channel
.A shortcut to calling this function is the syntax
field.<dim_name>
which callsfield.dimension(<dim_name>)
.Args
name
- dimension name
Returns
dimension reference
def divergence(self, order=2, implicit: phiml.math._optimize.Solve = None, upwind: Field = None)
-
Alias for
divergence()
def downsample(self, factor: int)
def gradient(self, boundary: phiml.math.extrapolation.Extrapolation = None, at: str = 'center', dims: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = <function spatial>, stack_dim: Union[phiml.math._shape.Shape, str] = (vectorᶜ=None), order=2, implicit: phiml.math._optimize.Solve = None, scheme=None, upwind: Field = None, gradient_extrapolation: phiml.math.extrapolation.Extrapolation = None)
-
Alias for
spatial_gradient()
def grid_scatter(self, *args, **kwargs)
-
Deprecated. Use
sample()
withscatter=True
instead. def laplace(self, axes: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = <function spatial>, gradient: Field = None, order=2, implicit: phiml.math._optimize.Solve = None, weights: Union[phiml.math._tensors.Tensor, ForwardRef('Field')] = None, upwind: Field = None, correct_skew=True)
-
Alias for
laplace()
def numpy(self, order: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = None)
-
Return the field values as
NumPy
array(s).Args
order
- Dimension order as
str
orShape
.
Returns
A single NumPy array for uniform values, else a list of NumPy arrays.
def pad(self, widths: Union[int, tuple, list, dict]) ‑> phi.field._field.Field
-
Alias for
pad()
.Pads this
Field
using its extrapolation.Unlike padding the values, this function also affects the
geometry
of the field, changing its size and origin depending onwidths
.Args
widths
- Either
int
or(lower, upper)
to pad the same number of cells in all spatial dimensions ordict
mapping dimension names to(lower, upper)
.
Returns
Padded
Field
def sample(self, where: Union[phi.geom._geom.Geometry, ForwardRef('Field'), phiml.math._tensors.Tensor], at: str = 'center', **kwargs) ‑> phiml.math._tensors.Tensor
-
Sample the values of this
Field
at the given location or geometry.Args
where
- Location
Tensor
orGeometry
or at
'center'
or'face'
.**kwargs
- Sampling arguments.
Returns
Tensor
def shifted(self, delta: phiml.math._tensors.Tensor) ‑> phi.field._field.Field
-
Move the positions of this field's
geometry
bydelta
.See Also:
Field.shifted_to()
.Args
delta
- Shift amount for each center position of
geometry
.
Returns
New
Field
sampled atgeometry.center + delta
. def shifted_to(self, position: phiml.math._tensors.Tensor) ‑> phi.field._field.Field
-
Move the positions of this field's
geometry
topositions
.See Also:
Field.shifted()
.Args
position
- New center positions of
geometry
.
Returns
New
Field
sampled at given positions. def staggered_tensor(self) ‑> phiml.math._tensors.Tensor
-
Stacks all component grids into a single uniform
phi.math.Tensor
. The individual components are padded to a common (larger) shape before being stacked. The shape of the returned tensor is exactly one cell larger than the gridresolution
in every spatial dimension.Returns
Uniform
phi.math.Tensor
. def to_grid(self, resolution=(), bounds=None, **resolution_)
def uniform_values(self)
-
Returns a uniform tensor containing
values
.For periodic grids, which always have a uniform value tensor, `values' is returned directly. If
values
is not uniform, it is padded as inStaggeredGrid.staggered_tensor()
. def with_boundary(self, boundary)
-
Returns a copy of this field with the
boundary
replaced. def with_bounds(self, bounds: phi.geom._box.Box)
-
Returns a copy of this field with
bounds
replaced. def with_elements(self, elements: phi.geom._geom.Geometry)
-
Returns a copy of this field with
elements
replaced. def with_extrapolation(self, boundary)
-
Returns a copy of this field with the
boundary
replaced. def with_geometry(self, elements: phi.geom._geom.Geometry)
-
Returns a copy of this field with
elements
replaced. def with_values(self, values, **sampling_kwargs)
-
Returns a copy of this field with
values
replaced.
class SampledField (geometry: Union[phiml.math._tensors.Tensor, phi.geom._geom.Geometry], values: Union[phiml.math._tensors.Tensor, numbers.Number, bool, Callable, phi.field._field.FieldInitializer, phi.geom._geom.Geometry, ForwardRef('Field')], boundary: Union[numbers.Number, phiml.math.extrapolation.Extrapolation, ForwardRef('Field'), dict] = 0, **sampling_kwargs)
-
Base class for all fields.
Important implementations:
- CenteredGrid
- StaggeredGrid
- PointCloud
- Noise
See the
phi.field
module documentation at https://tum-pbs.github.io/PhiFlow/Fields.htmlArgs
elements
- Geometry object specifying the sample points and sizes
values
- values corresponding to elements
extrapolation
- values outside elements
Expand source code
class Field: """ Base class for all fields. Important implementations: * CenteredGrid * StaggeredGrid * PointCloud * Noise See the `phi.field` module documentation at https://tum-pbs.github.io/PhiFlow/Fields.html """ def __init__(self, geometry: Union[Geometry, Tensor], values: Union[Tensor, Number, bool, Callable, FieldInitializer, Geometry, 'Field'], boundary: Union[Number, Extrapolation, 'Field', dict] = 0, **sampling_kwargs): """ Args: elements: Geometry object specifying the sample points and sizes values: values corresponding to elements extrapolation: values outside elements """ assert isinstance(geometry, Geometry), f"geometry must be a Geometry object but got {type(geometry).__name__}" self._boundary: Extrapolation = as_boundary(boundary, geometry) self._geometry: Geometry = geometry if isinstance(values, (Tensor, Number, bool)): values = wrap(values) else: from ._resample import sample values = sample(values, geometry, 'center', self._boundary, **sampling_kwargs) matching_sets = [s for s, s_shape in geometry.sets.items() if s_shape in values.shape] if not matching_sets: values = expand(wrap(values), non_batch(geometry) - 'vector') self._values: Tensor = values math.merge_shapes(values, non_batch(self.sampled_elements).non_channel) # shape check @property def geometry(self) -> Geometry: """ Returns a geometrical representation of the discrete volume elements. The result is a tuple of Geometry objects, each of which can have additional spatial (but not batch) dimensions. For grids, the geometries are boxes while particle fields may be represented as spheres. If this Field has no discrete points, this method returns an empty geometry. """ return self._geometry @property def grid(self) -> UniformGrid: """Cast `self.geometry` to a `phi.geom.UniformGrid`.""" assert isinstance(self._geometry, UniformGrid), f"Geometry is not a UniformGrid but {type(self._geometry)}" return self._geometry @property def mesh(self) -> Mesh: """Cast `self.geometry` to a `phi.geom.Mesh`.""" assert isinstance(self._geometry, Mesh), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry @property def graph(self) -> Graph: """Cast `self.geometry` to a `phi.geom.Graph`.""" assert isinstance(self._geometry, Graph), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry @property def faces(self): return get_faces(self._geometry, self._boundary) @property def face_centers(self): return self._geometry.face_centers # return slice_off_constant_faces(self._geometry.face_centers, self._geometry.boundary_faces, self._boundary) @property def face_normals(self): return self._geometry.face_normals # return slice_off_constant_faces(self._geometry.face_normals, self._geometry.boundary_faces, self._boundary) @property def face_areas(self): return self._geometry.face_areas # return slice_off_constant_faces(self._geometry.face_areas, self._geometry.boundary_faces, self._boundary) @property def sampled_elements(self) -> Geometry: """ If the values represent are sampled at the element centers or represent the whole element, returns `self.geometry`. If the values are sampled at the faces, returns `self.faces`. """ return get_faces(self._geometry, self._boundary) if is_staggered(self._values, self._geometry) else self._geometry @property def elements(self): # raise SyntaxError("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead.") warnings.warn("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead. Field.elements now defaults to Field.geometry.", DeprecationWarning, stacklevel=2) return self._geometry @property def is_centered(self): return not self.is_staggered @property def is_staggered(self): return is_staggered(self._values, self._geometry) @property def center(self) -> Tensor: """ Returns the center points of the `elements` of this `Field`. """ all_points = self._geometry.get_points(self.sampled_at) boundary = self._geometry.get_boundary(self.sampled_at) return slice_off_constant_faces(all_points, boundary, self.extrapolation) @property def points(self): return self.center @property def values(self) -> Tensor: """ Returns the `values` of this `Field`. """ return self._values data = values def numpy(self, order: DimFilter = None): """ Return the field values as `NumPy` array(s). Args: order: Dimension order as `str` or `Shape`. Returns: A single NumPy array for uniform values, else a list of NumPy arrays. """ if order is None and self.is_grid: axes = self._values.shape.only(self._geometry.vector.item_names, reorder=True) order = concat_shapes(self._values.shape.dual, self._values.shape.batch, axes, self._values.shape.channel) if self._values.shape.is_uniform: return self._values.numpy(order) else: assert order is not None, f"order must be specified for non-uniform Field values" order = self._values.shape.only(order, reorder=True) stack_dims = order.non_uniform_shape inner_order = order.without(stack_dims) return [v.numpy(inner_order) for v in unstack(self._values, stack_dims)] def uniform_values(self): """ Returns a uniform tensor containing `values`. For periodic grids, which always have a uniform value tensor, `values' is returned directly. If `values` is not uniform, it is padded as in `StaggeredGrid.staggered_tensor()`. """ if self.values.shape.is_uniform: return self.values else: return self.staggered_tensor() @property def boundary(self) -> Extrapolation: """ Returns the boundary conditions set for this `Field`. Returns: Single `Extrapolation` instance that encodes the (varying) boundary conditions for all boundaries of this field's `elements`. """ return self._boundary @property def extrapolation(self) -> Extrapolation: """ Returns the `Extrapolation` of this `Field`. """ return self._boundary @property def shape(self) -> Shape: """ Returns a shape with the following properties * The spatial dimension names match the dimensions of this Field * The batch dimensions match the batch dimensions of this Field * The channel dimensions match the channels of this Field """ if self.is_grid and '~vector' in self._values.shape: return batch(self._geometry) & self.resolution & non_dual(self._values).without(self.resolution) & self._geometry.shape['vector'] set_shape = self._geometry.sets[self.sampled_at] return batch(self._geometry) & (channel(self._geometry) - 'vector') & set_shape & self._values @property def resolution(self): return self._geometry.shape.non_channel.non_dual.non_batch @property def spatial_rank(self) -> int: """ Spatial rank of the field (1 for 1D, 2 for 2D, 3 for 3D). This is equal to the spatial rank of the `data`. """ return self._geometry.spatial_rank @property def bounds(self) -> BaseBox: """ The bounds represent the area inside which the values of this `Field` are valid. The bounds will also be used as axis limits for plots. The bounds can be set manually in the constructor, otherwise default bounds will be generated. For fields that are valid without bounds, the lower and upper limit of `bounds` is set to `-inf` and `inf`, respectively. Fields whose spatial rank is determined only during sampling return an empty `Box`. """ if isinstance(self._geometry.bounds, BaseBox): return self._geometry.bounds extent = self._geometry.bounding_half_extent().vector.as_dual('_extent') points = self._geometry.center + extent lower = math.min(points, dim=points.shape.non_batch.non_channel) upper = math.max(points, dim=points.shape.non_batch.non_channel) return Box(lower, upper) box = bounds @property def is_grid(self): """A Field represents grid data if its `geometry` is a `phi.geom.UniformGrid` instance.""" return isinstance(self._geometry, UniformGrid) @property def is_mesh(self): """A Field represents mesh data if its `geometry` is a `phi.geom.Mesh` instance.""" return isinstance(self._geometry, Mesh) @property def is_graph(self): """A Field represents graph data if its `geometry` is a `phi.geom.Graph` instance.""" return isinstance(self._geometry, Graph) @property def is_point_cloud(self): """A Field represents graph data if its `geometry` is not a set of connected elements, but rather individual geometric objects.""" if isinstance(self._geometry, (UniformGrid, Mesh, Graph)): return False if isinstance(self._geometry, (BaseBox, Sphere, Point)): return True return True @property def dx(self) -> Tensor: assert spatial(self._geometry), f"dx is only defined for elements with spatial dims but Field has elements {self._geometry.shape}" return self.bounds.size / self.resolution @property def cells(self): assert isinstance(self._geometry, (UniformGrid, Mesh)) return self._geometry def to_grid(self, resolution=math.EMPTY_SHAPE, bounds=None, **resolution_): resolution = resolution.spatial & spatial(**resolution_) if self.is_grid and (not resolution or resolution == self.resolution) and (bounds is None or bounds == self.bounds): return self bounds = self.bounds if bounds is None else bounds if not resolution: half_sizes = self._geometry.bounding_half_extent() if (half_sizes > 0).all: size = math.min(2 * half_sizes, non_batch(half_sizes).non_channel) else: cell_count = non_batch(self._geometry).non_channel.non_dual.volume size = (bounds.volume / cell_count) ** (1 / self.spatial_rank) res = math.maximum(1, math.round(bounds.size / size)) resolution = spatial(**res.vector) return Field(UniformGrid(resolution, bounds), self, self.boundary) def as_points(self, list_dim: Optional[Shape] = instance('elements')) -> 'Field': """ Returns this field as a PointCloud. This replaces the `Field.geometry` with a `phi.geom.Point` instance while leaving the sample points unchanged. See Also: `Field.as_spheres()`. Args: list_dim: If not `None`, packs spatial, instance and dual dims. Defaults to `instance('elements')`. Returns: `Field` with same values and boundaries but `Point` geometry. """ points = self.sampled_elements.center values = self._values if list_dim: dims = non_batch(points).non_channel & non_batch(points).non_channel points = pack_dims(points, dims, list_dim) values = pack_dims(values, dims, list_dim) return Field(Point(points), values, self._boundary) def as_spheres(self, list_dim: Optional[Shape] = instance('elements')) -> 'Field': """ Returns this field as a PointCloud with spherical / circular elements, preserving element volumes. This replaces the `Field.geometry` with a `phi.geom.Sphere` instance while leaving the sample points unchanged. See Also: `Field.as_points()`. Args: list_dim: If not `None`, packs spatial, instance and dual dims. Defaults to `instance('elements')`. Returns: `Field` with same values and boundaries but `Sphere` geometry. """ points = self.sampled_elements.center volumes = self.sampled_elements.volume values = self._values if list_dim: dims = non_batch(points).non_channel & non_batch(points).non_channel points = pack_dims(points, dims, list_dim) values = pack_dims(values, dims, list_dim) volumes = pack_dims(volumes, dims, list_dim) return Field(Sphere(points, volume=volumes), values, self._boundary) def at_centers(self, **kwargs) -> 'Field': """ Interpolates the values to the cell centers. See Also: `Field.at_faces()`, `Field.at()`, `resample`. Args: **kwargs: Sampling arguments. Returns: `CenteredGrid` sampled at cell centers. """ if self.is_centered: return self from ._resample import sample values = sample(self, self._geometry, at='center', boundary=self._boundary, **kwargs) return Field(self._geometry, values, self._boundary) def at_faces(self, boundary=None, **kwargs) -> 'Field': if self.is_staggered and not boundary: return self boundary = as_boundary(boundary, self._geometry) if boundary else self._boundary from ._resample import sample values = sample(self, self._geometry, at='face', boundary=boundary, **kwargs) return Field(self._geometry, values, boundary) @property def sampled_at(self): matching_sets = [s for s, s_shape in self._geometry.sets.items() if s_shape in self._values.shape] return matching_sets[-1] def at(self, representation: Union['Field', Geometry], keep_boundary=False, **kwargs) -> 'Field': """ Short for `resample(self, representation)` See Also `resample()`. Returns: Field object of same type as `representation` """ from ._resample import resample return resample(self, representation, keep_boundary, **kwargs) def sample(self, where: Union[Geometry, 'Field', Tensor], at: str = 'center', **kwargs) -> 'Tensor': """ Sample the values of this `Field` at the given location or geometry. Args: where: Location `Tensor` or `Geometry` or at: `'center'` or `'face'`. **kwargs: Sampling arguments. Returns: `Tensor` """ from ._resample import sample return sample(self, where, at, **kwargs) def closest_values(self, points: Tensor): """ Sample the closest grid point values of this field at the world-space locations (in physical units) given by `points`. Points must have a single channel dimension named `vector`. It may additionally contain any number of batch and spatial dimensions, all treated as batch dimensions. Args: points: world-space locations Returns: Closest grid point values as a `Tensor`. For each dimension, the grid points immediately left and right of the sample points are evaluated. For each point in `points`, a *2^d* cube of points is determined where *d* is the number of spatial dimensions of this field. These values are stacked along the new dimensions `'closest_<dim>'` where `<dim>` refers to the name of a spatial dimension. """ warnings.warn("Field.closest_values() is deprecated.", DeprecationWarning, stacklevel=2) if isinstance(points, Geometry): points = points.center # --- CenteredGrid --- local_points = self.box.global_to_local(points) * self.resolution - 0.5 return math.closest_grid_values(self.values, local_points, self.extrapolation) # --- StaggeredGrid --- if 'staggered_direction' in points.shape: points_ = math.unstack(points, '~vector') channels = [component.closest_values(p) for p, component in zip(points_, self.vector.unstack())] else: channels = [component.closest_values(points) for component in self.vector.unstack()] return math.stack(channels, points.shape['~vector']) def with_values(self, values, **sampling_kwargs): """ Returns a copy of this field with `values` replaced. """ if not isinstance(values, (Tensor, Number)): from ._resample import sample values = sample(values, self._geometry, self.sampled_at, self._boundary, dot_face_normal=self._geometry if 'vector' not in self._values.shape else None, **sampling_kwargs) else: if not spatial(values): geo_shape = self.sampled_elements.shape if self.is_staggered else self._geometry.shape if '~vector' in geo_shape and 'vector' in shape(values) and '~vector' not in shape(values): values = values.vector.as_dual() values = expand(wrap(values), geo_shape.non_batch.non_channel) return Field(self._geometry, values, self._boundary) def with_boundary(self, boundary): """ Returns a copy of this field with the `boundary` replaced. """ boundary = as_boundary(boundary, self._geometry) boundary_elements = 'boundary_faces' if self.is_staggered else 'boundary_elements' old_determined_slices = {k: s for k, s in getattr(self._geometry, boundary_elements).items() if self._boundary.determines_boundary_values(k)} new_determined_slices = {k: s for k, s in getattr(self._geometry, boundary_elements).items() if boundary.determines_boundary_values(k)} if old_determined_slices.values() == new_determined_slices.values(): return Field(self._geometry, self._values, boundary) # ToDo unnecessary once the rest is implemented to_add = {k: sl for k, sl in old_determined_slices.items() if sl not in new_determined_slices.values()} to_remove = [sl for sl in new_determined_slices.values() if sl not in old_determined_slices.values()] values = math.slice_off(self._values, *to_remove) if to_add: if self.is_mesh: values = self.mesh.pad_boundary(values, to_add, self._boundary) elif self.is_grid and self.is_staggered: values = self._values.vector.dual.as_channel() to_add = {k: {'vector' if dim == '~vector' else dim: v for dim, v in sl.items()} for k, sl in to_add.items()} values = math.pad(values, list(to_add.values()), self._boundary, bounds=self.bounds) values = values.vector.as_dual() else: values = math.pad(values, list(to_add.values()), self._boundary, bounds=self.bounds) return Field(self._geometry, values, boundary) with_extrapolation = with_boundary def with_bounds(self, bounds: Box): """ Returns a copy of this field with `bounds` replaced. """ order = list(bounds.vector.item_names) geometry = self._geometry.vector[order] new_shape = self._values.shape.without(order) & self._values.shape.only(order, reorder=True) values = math.transpose(self._values, new_shape) return Field(geometry, values, self._boundary) def with_geometry(self, elements: Geometry): """ Returns a copy of this field with `elements` replaced. """ assert non_batch(elements) == non_batch(self._geometry), f"Field.with_elements() only accepts elements with equal non-batch dimensions but got {elements.shape} for Field with shape {self._geometry.shape}" return Field(elements, self._values, self._boundary) with_elements = with_geometry def shifted(self, delta: Tensor) -> 'Field': """ Move the positions of this field's `geometry` by `delta`. See Also: `Field.shifted_to`. Args: delta: Shift amount for each center position of `geometry`. Returns: New `Field` sampled at `geometry.center + delta`. """ return self.with_geometry(self._geometry.shifted(delta)) def shifted_to(self, position: Tensor) -> 'Field': """ Move the positions of this field's `geometry` to `positions`. See Also: `Field.shifted`. Args: position: New center positions of `geometry`. Returns: New `Field` sampled at given positions. """ return self.with_geometry(self._geometry.at(position)) def pad(self, widths: Union[int, tuple, list, dict]) -> 'Field': """ Alias for `phi.field.pad()`. Pads this `Field` using its extrapolation. Unlike padding the values, this function also affects the `geometry` of the field, changing its size and origin depending on `widths`. Args: widths: Either `int` or `(lower, upper)` to pad the same number of cells in all spatial dimensions or `dict` mapping dimension names to `(lower, upper)`. Returns: Padded `Field` """ from ._field_math import pad return pad(self, widths) def gradient(self, boundary: Extrapolation = None, at: str = 'center', dims: math.DimFilter = spatial, stack_dim: Union[Shape, str] = channel('vector'), order=2, implicit: Solve = None, scheme=None, upwind: 'Field' = None, gradient_extrapolation: Extrapolation = None): """Alias for `phi.field.spatial_gradient`""" from ._field_math import spatial_gradient return spatial_gradient(self, boundary=boundary, at=at, dims=dims, stack_dim=stack_dim, order=order, implicit=implicit, scheme=scheme, upwind=upwind, gradient_extrapolation=gradient_extrapolation) def divergence(self, order=2, implicit: Solve = None, upwind: 'Field' = None): """Alias for `phi.field.divergence`""" from ._field_math import divergence return divergence(self, order=order, implicit=implicit, upwind=upwind) def curl(self, at='corner'): """Alias for `phi.field.curl`""" from ._field_math import curl return curl(self, at=at) def laplace(self, axes: DimFilter = spatial, gradient: 'Field' = None, order=2, implicit: math.Solve = None, weights: Union[Tensor, 'Field'] = None, upwind: 'Field' = None, correct_skew=True): """Alias for `phi.field.laplace`""" from ._field_math import laplace return laplace(self, axes=axes, gradient=gradient, order=order, implicit=implicit, weights=weights, upwind=upwind, correct_skew=correct_skew) def downsample(self, factor: int): from ._field_math import downsample2x result = self while factor >= 2: result = downsample2x(result) factor /= 2 if math.close(factor, 1.): return result from ._resample import resample raise NotImplementedError(f"downsample does not support fractional re-sampling. Only 2^n currently supported.") def staggered_tensor(self) -> Tensor: """ Stacks all component grids into a single uniform `phi.math.Tensor`. The individual components are padded to a common (larger) shape before being stacked. The shape of the returned tensor is exactly one cell larger than the grid `resolution` in every spatial dimension. Returns: Uniform `phi.math.Tensor`. """ assert self.resolution.names == self.shape.get_item_names('vector'), "Field.staggered_tensor() only defined for Fields whose vector components match the resolution" padded = [] for dim, component in zip(self.resolution.names, self.vector): widths = {d: (0, 1) for d in self.resolution.names} lo_valid, up_valid = self.extrapolation.valid_outer_faces(dim) widths[dim] = (int(not lo_valid), int(not up_valid)) padded.append(math.pad(component.values, widths, self.extrapolation[{'vector': dim}], bounds=self.bounds)) result = math.stack(padded, channel(vector=self.resolution)) assert result.shape.is_uniform return result @staticmethod def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Field': from ._field_math import stack return stack(values, dim, kwargs.get('bounds', None)) @staticmethod def __concat__(values: tuple, dim: str, **kwargs) -> 'Field': from ._field_math import concat return concat(values, dim) def __and__(self, other): assert isinstance(other, Field) assert instance(self).rank == instance(other).rank == 1, f"Can only use & on PointClouds that have a single instance dimension but got shapes {self.shape} & {other.shape}" from ._field_math import concat return concat([self, other], instance(self)) def __matmul__(self, other: 'Field'): # value @ representation # Deprecated. Use `resample(value, field)` instead. warnings.warn("value @ field is deprecated. Use resample(value, field) instead.", DeprecationWarning) from ._resample import resample return resample(self, to=other, keep_boundary=False) def __rmatmul__(self, other): # values @ representation if isinstance(other, (Geometry, Number, tuple, list, FieldInitializer)): warnings.warn("value @ field is deprecated. Use resample(value, field) instead.", DeprecationWarning) from ._resample import resample return resample(other, to=self, keep_boundary=False) return NotImplemented def __rshift__(self, other): if isinstance(other, (Field, Geometry)): warnings.warn(">> operator for Fields is deprecated. Use field.at(), the constructor or obj @ field instead.", SyntaxWarning, stacklevel=2) return self.at(other, keep_boundary=False) else: return NotImplemented def __rrshift__(self, other): return self.with_values(other) def __lshift__(self, other): return self.with_values(other) def __rrshift__(self, other): warnings.warn(">> operator for Fields is deprecated. Use field.at(), the constructor or obj @ field instead.", SyntaxWarning, stacklevel=2) if not isinstance(self, Field): return NotImplemented if isinstance(other, (Geometry, float, int, complex, tuple, list, FieldInitializer)): from ._resample import resample return resample(other, to=self, keep_boundary=False) return NotImplemented def __getitem__(self, item) -> 'Field': """ Access a slice of the Field. The returned `Field` may be of a different type than `self`. Args: item: `dict` mapping dimensions (`str`) to selections (`int` or `slice`) or other supported type, such as `int` or `str`. Returns: Sliced `Field`. """ item = slicing_dict(self, item) if not item: return self boundary = domain_slice(self._boundary, item, self.resolution) item_without_vec = {dim: selection for dim, selection in item.items() if dim != 'vector'} geometry = self._geometry[item_without_vec] if self.is_staggered and 'vector' in item and '~vector' in self.geometry.face_shape: assert isinstance(self._geometry, UniformGrid), f"Vector slicing is only supported for grids" dims = item['vector'] dims_ = self._geometry.shape['vector'].after_gather({'vector': dims}) dims = dims_.item_names[0] if dims_ else [dims] if isinstance(dims, str) else [self._geometry.shape['vector'].item_names[0][dims]] proj_dims = set(self.resolution.names) - set(dims) if any(dim not in item for dim in proj_dims): # warnings.warn(f"Projecting a staggered grid (by slicing 'vector' without the corresponding spatial dims) will return a non-staggered grid. The projected dims {proj_dims} were not sliced off.\nFull slice: {item}") item['~vector'] = item['vector'] del item['vector'] geometry = self.sampled_elements[item] else: item['~vector'] = dims del item['vector'] values = self._values[item] return Field(geometry, values, boundary) def __getattr__(self, name: str) -> BoundDim: return BoundDim(self, name) def dimension(self, name: str): """ Returns a reference to one of the dimensions of this field. The dimension reference can be used the same way as a `Tensor` dimension reference. Notable properties and methods of a dimension reference are: indexing using `[index]`, `unstack()`, `size`, `exists`, `is_batch`, `is_spatial`, `is_channel`. A shortcut to calling this function is the syntax `field.<dim_name>` which calls `field.dimension(<dim_name>)`. Args: name: dimension name Returns: dimension reference """ return BoundDim(self, name) def __value_attrs__(self): return '_values', def __variable_attrs__(self): return '_values', '_geometry', '_boundary' def __expand__(self, dims: Shape, **kwargs) -> 'Field': return self.with_values(expand(self.values, dims, **kwargs)) def __replace_dims__(self, dims: Tuple[str, ...], new_dims: Shape, **kwargs) -> 'Field': elements = math.rename_dims(self._geometry, dims, new_dims) values = math.rename_dims(self._values, dims, new_dims) extrapolation = math.rename_dims(self._boundary, dims, new_dims, **kwargs) return Field(elements, values, extrapolation) def __eq__(self, other): if not isinstance(other, Field): return False if self._geometry != other._geometry: return False if self._boundary != other.boundary: return False return math.always_close(self._values, other._values) def __hash__(self): return hash((self._geometry, self._boundary)) def __mul__(self, other): return self._op2(other, lambda d1, d2: d1 * d2) __rmul__ = __mul__ def __truediv__(self, other): return self._op2(other, lambda d1, d2: d1 / d2) def __rtruediv__(self, other): return self._op2(other, lambda d1, d2: d2 / d1) def __sub__(self, other): return self._op2(other, lambda d1, d2: d1 - d2) def __rsub__(self, other): return self._op2(other, lambda d1, d2: d2 - d1) def __add__(self, other): return self._op2(other, lambda d1, d2: d1 + d2) __radd__ = __add__ def __pow__(self, power, modulo=None): return self._op2(power, lambda f, p: f ** p) def __neg__(self): return self._op1(lambda x: -x) def __gt__(self, other): return self._op2(other, lambda x, y: x > y) def __ge__(self, other): return self._op2(other, lambda x, y: x >= y) def __lt__(self, other): return self._op2(other, lambda x, y: x < y) def __le__(self, other): return self._op2(other, lambda x, y: x <= y) def __abs__(self): return self._op1(lambda x: abs(x)) def _op1(self: 'Field', operator: Callable) -> 'Field': """ Perform an operation on the data of this field. Args: operator: function that accepts tensors and extrapolations and returns objects of the same type and dimensions Returns: Field of same type """ values = operator(self.values) extrapolation_ = operator(self._boundary) return self.with_values(values).with_extrapolation(extrapolation_) def _op2(self, other, operator) -> 'Field': if isinstance(other, Geometry): raise ValueError(f"Cannot combine {self.__class__.__name__} with a Geometry, got {type(other)}") if isinstance(other, Field): if self._geometry == other._geometry: values = operator(self._values, other.values) extrapolation_ = operator(self._boundary, other.extrapolation) return Field(self._geometry, values, extrapolation_) from ._resample import sample other_values = sample(other, self._geometry, self.sampled_at, self.boundary, dot_face_normal=self._geometry) values = operator(self._values, other_values) boundary = operator(self._boundary, other.extrapolation) return Field(self._geometry, values, boundary) else: if isinstance(other, (tuple, list)) and len(other) == self.spatial_rank: other = math.wrap(other, self._geometry.shape['vector']) else: other = math.wrap(other) # try: # boundary = operator(self._boundary, as_boundary(other, self._geometry)) # except TypeError: # e.g. ZERO_GRADIENT + constant boundary = self._boundary # constants don't affect the boundary conditions (legacy reasons) if 'vector' in self.shape and 'vector' not in self.values.shape and '~vector' in self.values.shape: other = other.vector.as_dual() values = operator(self._values, other) return Field(self._geometry, values, boundary) def __repr__(self): if self.is_grid: type_name = "Grid" if self.is_centered else "Grid faces" elif self.is_mesh: type_name = "Mesh" if self.is_centered else "Mesh faces" elif self.is_point_cloud: type_name = "Point cloud" if self.is_centered else "Point cloud edges" elif self.is_graph: type_name = "Graph" if self.is_centered else "Graph edges" else: type_name = self.__class__.__name__ if self._values is not None: return f"{type_name}[{self.values}, ext={self._boundary}]" else: return f"{type_name}[{self.resolution}, ext={self._boundary}]" def grid_scatter(self, *args, **kwargs): """Deprecated. Use `sample` with `scatter=True` instead.""" warnings.warn("Field.grid_scatter() is deprecated. Use field.sample() with scatter=True instead.", DeprecationWarning, stacklevel=2) from ._resample import grid_scatter return grid_scatter(self, *args, **kwargs) def as_boundary(self) -> Extrapolation: """ Returns an `Extrapolation` representing this 'Field''s values as a Dirichlet (constant) boundary. If this `Field` encloses the required boundaries, its values will be interpolated to the required boundaries. If boundaries outside of this `Field`'s sampled domain are required, this `Field`'s boundary conditions will be applied to determine the boundary values. Returns: `Extrapolation` """ from ._embed import FieldEmbedding return FieldEmbedding(self)
Subclasses
- phi.field._mask.HardGeometryMask
Instance variables
prop boundary : phiml.math.extrapolation.Extrapolation
-
Returns the boundary conditions set for this
Field
.Returns
Single
Extrapolation
instance that encodes the (varying) boundary conditions for all boundaries of this field'selements
.Expand source code
@property def boundary(self) -> Extrapolation: """ Returns the boundary conditions set for this `Field`. Returns: Single `Extrapolation` instance that encodes the (varying) boundary conditions for all boundaries of this field's `elements`. """ return self._boundary
prop bounds : phi.geom._box.BaseBox
-
The bounds represent the area inside which the values of this
Field
are valid. The bounds will also be used as axis limits for plots.The bounds can be set manually in the constructor, otherwise default bounds will be generated.
For fields that are valid without bounds, the lower and upper limit of
bounds
is set to-inf
andinf
, respectively.Fields whose spatial rank is determined only during sampling return an empty
Box
.Expand source code
@property def bounds(self) -> BaseBox: """ The bounds represent the area inside which the values of this `Field` are valid. The bounds will also be used as axis limits for plots. The bounds can be set manually in the constructor, otherwise default bounds will be generated. For fields that are valid without bounds, the lower and upper limit of `bounds` is set to `-inf` and `inf`, respectively. Fields whose spatial rank is determined only during sampling return an empty `Box`. """ if isinstance(self._geometry.bounds, BaseBox): return self._geometry.bounds extent = self._geometry.bounding_half_extent().vector.as_dual('_extent') points = self._geometry.center + extent lower = math.min(points, dim=points.shape.non_batch.non_channel) upper = math.max(points, dim=points.shape.non_batch.non_channel) return Box(lower, upper)
prop box : phi.geom._box.BaseBox
-
The bounds represent the area inside which the values of this
Field
are valid. The bounds will also be used as axis limits for plots.The bounds can be set manually in the constructor, otherwise default bounds will be generated.
For fields that are valid without bounds, the lower and upper limit of
bounds
is set to-inf
andinf
, respectively.Fields whose spatial rank is determined only during sampling return an empty
Box
.Expand source code
@property def bounds(self) -> BaseBox: """ The bounds represent the area inside which the values of this `Field` are valid. The bounds will also be used as axis limits for plots. The bounds can be set manually in the constructor, otherwise default bounds will be generated. For fields that are valid without bounds, the lower and upper limit of `bounds` is set to `-inf` and `inf`, respectively. Fields whose spatial rank is determined only during sampling return an empty `Box`. """ if isinstance(self._geometry.bounds, BaseBox): return self._geometry.bounds extent = self._geometry.bounding_half_extent().vector.as_dual('_extent') points = self._geometry.center + extent lower = math.min(points, dim=points.shape.non_batch.non_channel) upper = math.max(points, dim=points.shape.non_batch.non_channel) return Box(lower, upper)
prop cells
-
Expand source code
@property def cells(self): assert isinstance(self._geometry, (UniformGrid, Mesh)) return self._geometry
prop center : phiml.math._tensors.Tensor
-
Returns the center points of the
elements
of thisField
.Expand source code
@property def center(self) -> Tensor: """ Returns the center points of the `elements` of this `Field`. """ all_points = self._geometry.get_points(self.sampled_at) boundary = self._geometry.get_boundary(self.sampled_at) return slice_off_constant_faces(all_points, boundary, self.extrapolation)
prop data : phiml.math._tensors.Tensor
-
Returns the
values
of thisField
.Expand source code
@property def values(self) -> Tensor: """ Returns the `values` of this `Field`. """ return self._values
prop dx : phiml.math._tensors.Tensor
-
Expand source code
@property def dx(self) -> Tensor: assert spatial(self._geometry), f"dx is only defined for elements with spatial dims but Field has elements {self._geometry.shape}" return self.bounds.size / self.resolution
prop elements
-
Expand source code
@property def elements(self): # raise SyntaxError("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead.") warnings.warn("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead. Field.elements now defaults to Field.geometry.", DeprecationWarning, stacklevel=2) return self._geometry
prop extrapolation : phiml.math.extrapolation.Extrapolation
-
Returns the
Extrapolation
of thisField
.Expand source code
@property def extrapolation(self) -> Extrapolation: """ Returns the `Extrapolation` of this `Field`. """ return self._boundary
prop face_areas
-
Expand source code
@property def face_areas(self): return self._geometry.face_areas # return slice_off_constant_faces(self._geometry.face_areas, self._geometry.boundary_faces, self._boundary)
prop face_centers
-
Expand source code
@property def face_centers(self): return self._geometry.face_centers # return slice_off_constant_faces(self._geometry.face_centers, self._geometry.boundary_faces, self._boundary)
prop face_normals
-
Expand source code
@property def face_normals(self): return self._geometry.face_normals # return slice_off_constant_faces(self._geometry.face_normals, self._geometry.boundary_faces, self._boundary)
prop faces
-
Expand source code
@property def faces(self): return get_faces(self._geometry, self._boundary)
prop geometry : phi.geom._geom.Geometry
-
Returns a geometrical representation of the discrete volume elements. The result is a tuple of Geometry objects, each of which can have additional spatial (but not batch) dimensions.
For grids, the geometries are boxes while particle fields may be represented as spheres.
If this Field has no discrete points, this method returns an empty geometry.
Expand source code
@property def geometry(self) -> Geometry: """ Returns a geometrical representation of the discrete volume elements. The result is a tuple of Geometry objects, each of which can have additional spatial (but not batch) dimensions. For grids, the geometries are boxes while particle fields may be represented as spheres. If this Field has no discrete points, this method returns an empty geometry. """ return self._geometry
prop graph : phi.geom._graph.Graph
-
Cast
self.geometry
to aGraph
.Expand source code
@property def graph(self) -> Graph: """Cast `self.geometry` to a `phi.geom.Graph`.""" assert isinstance(self._geometry, Graph), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry
prop grid : phi.geom._grid.UniformGrid
-
Cast
self.geometry
to aUniformGrid
.Expand source code
@property def grid(self) -> UniformGrid: """Cast `self.geometry` to a `phi.geom.UniformGrid`.""" assert isinstance(self._geometry, UniformGrid), f"Geometry is not a UniformGrid but {type(self._geometry)}" return self._geometry
prop is_centered
-
Expand source code
@property def is_centered(self): return not self.is_staggered
prop is_graph
-
A Field represents graph data if its
geometry
is aGraph
instance.Expand source code
@property def is_graph(self): """A Field represents graph data if its `geometry` is a `phi.geom.Graph` instance.""" return isinstance(self._geometry, Graph)
prop is_grid
-
A Field represents grid data if its
geometry
is aUniformGrid
instance.Expand source code
@property def is_grid(self): """A Field represents grid data if its `geometry` is a `phi.geom.UniformGrid` instance.""" return isinstance(self._geometry, UniformGrid)
prop is_mesh
-
A Field represents mesh data if its
geometry
is aMesh
instance.Expand source code
@property def is_mesh(self): """A Field represents mesh data if its `geometry` is a `phi.geom.Mesh` instance.""" return isinstance(self._geometry, Mesh)
prop is_point_cloud
-
A Field represents graph data if its
geometry
is not a set of connected elements, but rather individual geometric objects.Expand source code
@property def is_point_cloud(self): """A Field represents graph data if its `geometry` is not a set of connected elements, but rather individual geometric objects.""" if isinstance(self._geometry, (UniformGrid, Mesh, Graph)): return False if isinstance(self._geometry, (BaseBox, Sphere, Point)): return True return True
prop is_staggered
-
Expand source code
@property def is_staggered(self): return is_staggered(self._values, self._geometry)
prop mesh : phi.geom._mesh.Mesh
-
Cast
self.geometry
to aMesh
.Expand source code
@property def mesh(self) -> Mesh: """Cast `self.geometry` to a `phi.geom.Mesh`.""" assert isinstance(self._geometry, Mesh), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry
prop points
-
Expand source code
@property def points(self): return self.center
prop resolution
-
Expand source code
@property def resolution(self): return self._geometry.shape.non_channel.non_dual.non_batch
prop sampled_at
-
Expand source code
@property def sampled_at(self): matching_sets = [s for s, s_shape in self._geometry.sets.items() if s_shape in self._values.shape] return matching_sets[-1]
prop sampled_elements : phi.geom._geom.Geometry
-
If the values represent are sampled at the element centers or represent the whole element, returns
self.geometry
. If the values are sampled at the faces, returnsself.faces
.Expand source code
@property def sampled_elements(self) -> Geometry: """ If the values represent are sampled at the element centers or represent the whole element, returns `self.geometry`. If the values are sampled at the faces, returns `self.faces`. """ return get_faces(self._geometry, self._boundary) if is_staggered(self._values, self._geometry) else self._geometry
prop shape : phiml.math._shape.Shape
-
Returns a shape with the following properties
- The spatial dimension names match the dimensions of this Field
- The batch dimensions match the batch dimensions of this Field
- The channel dimensions match the channels of this Field
Expand source code
@property def shape(self) -> Shape: """ Returns a shape with the following properties * The spatial dimension names match the dimensions of this Field * The batch dimensions match the batch dimensions of this Field * The channel dimensions match the channels of this Field """ if self.is_grid and '~vector' in self._values.shape: return batch(self._geometry) & self.resolution & non_dual(self._values).without(self.resolution) & self._geometry.shape['vector'] set_shape = self._geometry.sets[self.sampled_at] return batch(self._geometry) & (channel(self._geometry) - 'vector') & set_shape & self._values
prop spatial_rank : int
-
Spatial rank of the field (1 for 1D, 2 for 2D, 3 for 3D). This is equal to the spatial rank of the
data
.Expand source code
@property def spatial_rank(self) -> int: """ Spatial rank of the field (1 for 1D, 2 for 2D, 3 for 3D). This is equal to the spatial rank of the `data`. """ return self._geometry.spatial_rank
prop values : phiml.math._tensors.Tensor
-
Returns the
values
of thisField
.Expand source code
@property def values(self) -> Tensor: """ Returns the `values` of this `Field`. """ return self._values
Methods
def as_boundary(self) ‑> phiml.math.extrapolation.Extrapolation
-
Returns an
Extrapolation
representing this 'Field''s values as a Dirichlet (constant) boundary. If thisField
encloses the required boundaries, its values will be interpolated to the required boundaries. If boundaries outside of thisField
's sampled domain are required, thisField
's boundary conditions will be applied to determine the boundary values.Returns
Extrapolation
def as_points(self, list_dim: Optional[phiml.math._shape.Shape] = (elementsⁱ=None)) ‑> phi.field._field.Field
-
Returns this field as a PointCloud. This replaces the
Field.geometry
with aPoint
instance while leaving the sample points unchanged.See Also:
Field.as_spheres()
.Args
list_dim
- If not
None
, packs spatial, instance and dual dims. Defaults toinstance('elements')
.
Returns
Field
with same values and boundaries butPoint
geometry. def as_spheres(self, list_dim: Optional[phiml.math._shape.Shape] = (elementsⁱ=None)) ‑> phi.field._field.Field
-
Returns this field as a PointCloud with spherical / circular elements, preserving element volumes. This replaces the
Field.geometry
with aSphere
instance while leaving the sample points unchanged.See Also:
Field.as_points()
.Args
list_dim
- If not
None
, packs spatial, instance and dual dims. Defaults toinstance('elements')
.
Returns
Field
with same values and boundaries butSphere
geometry. def at(self, representation: Union[ForwardRef('Field'), phi.geom._geom.Geometry], keep_boundary=False, **kwargs) ‑> phi.field._field.Field
-
Short for
resample()(self, representation)
See Also
resample()
.Returns
Field object of same type as
representation
def at_centers(self, **kwargs) ‑> phi.field._field.Field
-
Interpolates the values to the cell centers.
See Also:
Field.at_faces()
,Field.at()
,resample()
.Args
**kwargs
- Sampling arguments.
Returns
CenteredGrid()
sampled at cell centers. def at_faces(self, boundary=None, **kwargs) ‑> phi.field._field.Field
def closest_values(self, points: phiml.math._tensors.Tensor)
-
Sample the closest grid point values of this field at the world-space locations (in physical units) given by
points
. Points must have a single channel dimension namedvector
. It may additionally contain any number of batch and spatial dimensions, all treated as batch dimensions.Args
points
- world-space locations
Returns
Closest grid point values as a
Tensor
. For each dimension, the grid points immediately left and right of the sample points are evaluated. For each point inpoints
, a 2^d cube of points is determined where d is the number of spatial dimensions of this field. These values are stacked along the new dimensions'closest_<dim>'
where<dim>
refers to the name of a spatial dimension. def curl(self, at='corner')
-
Alias for
curl()
def dimension(self, name: str)
-
Returns a reference to one of the dimensions of this field.
The dimension reference can be used the same way as a
Tensor
dimension reference. Notable properties and methods of a dimension reference are: indexing using[index]
,unstack()
,size
,exists
,is_batch
,is_spatial
,is_channel
.A shortcut to calling this function is the syntax
field.<dim_name>
which callsfield.dimension(<dim_name>)
.Args
name
- dimension name
Returns
dimension reference
def divergence(self, order=2, implicit: phiml.math._optimize.Solve = None, upwind: Field = None)
-
Alias for
divergence()
def downsample(self, factor: int)
def gradient(self, boundary: phiml.math.extrapolation.Extrapolation = None, at: str = 'center', dims: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = <function spatial>, stack_dim: Union[phiml.math._shape.Shape, str] = (vectorᶜ=None), order=2, implicit: phiml.math._optimize.Solve = None, scheme=None, upwind: Field = None, gradient_extrapolation: phiml.math.extrapolation.Extrapolation = None)
-
Alias for
spatial_gradient()
def grid_scatter(self, *args, **kwargs)
-
Deprecated. Use
sample()
withscatter=True
instead. def laplace(self, axes: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = <function spatial>, gradient: Field = None, order=2, implicit: phiml.math._optimize.Solve = None, weights: Union[phiml.math._tensors.Tensor, ForwardRef('Field')] = None, upwind: Field = None, correct_skew=True)
-
Alias for
laplace()
def numpy(self, order: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = None)
-
Return the field values as
NumPy
array(s).Args
order
- Dimension order as
str
orShape
.
Returns
A single NumPy array for uniform values, else a list of NumPy arrays.
def pad(self, widths: Union[int, tuple, list, dict]) ‑> phi.field._field.Field
-
Alias for
pad()
.Pads this
Field
using its extrapolation.Unlike padding the values, this function also affects the
geometry
of the field, changing its size and origin depending onwidths
.Args
widths
- Either
int
or(lower, upper)
to pad the same number of cells in all spatial dimensions ordict
mapping dimension names to(lower, upper)
.
Returns
Padded
Field
def sample(self, where: Union[phi.geom._geom.Geometry, ForwardRef('Field'), phiml.math._tensors.Tensor], at: str = 'center', **kwargs) ‑> phiml.math._tensors.Tensor
-
Sample the values of this
Field
at the given location or geometry.Args
where
- Location
Tensor
orGeometry
or at
'center'
or'face'
.**kwargs
- Sampling arguments.
Returns
Tensor
def shifted(self, delta: phiml.math._tensors.Tensor) ‑> phi.field._field.Field
-
Move the positions of this field's
geometry
bydelta
.See Also:
Field.shifted_to()
.Args
delta
- Shift amount for each center position of
geometry
.
Returns
New
Field
sampled atgeometry.center + delta
. def shifted_to(self, position: phiml.math._tensors.Tensor) ‑> phi.field._field.Field
-
Move the positions of this field's
geometry
topositions
.See Also:
Field.shifted()
.Args
position
- New center positions of
geometry
.
Returns
New
Field
sampled at given positions. def staggered_tensor(self) ‑> phiml.math._tensors.Tensor
-
Stacks all component grids into a single uniform
phi.math.Tensor
. The individual components are padded to a common (larger) shape before being stacked. The shape of the returned tensor is exactly one cell larger than the gridresolution
in every spatial dimension.Returns
Uniform
phi.math.Tensor
. def to_grid(self, resolution=(), bounds=None, **resolution_)
def uniform_values(self)
-
Returns a uniform tensor containing
values
.For periodic grids, which always have a uniform value tensor, `values' is returned directly. If
values
is not uniform, it is padded as inStaggeredGrid.staggered_tensor()
. def with_boundary(self, boundary)
-
Returns a copy of this field with the
boundary
replaced. def with_bounds(self, bounds: phi.geom._box.Box)
-
Returns a copy of this field with
bounds
replaced. def with_elements(self, elements: phi.geom._geom.Geometry)
-
Returns a copy of this field with
elements
replaced. def with_extrapolation(self, boundary)
-
Returns a copy of this field with the
boundary
replaced. def with_geometry(self, elements: phi.geom._geom.Geometry)
-
Returns a copy of this field with
elements
replaced. def with_values(self, values, **sampling_kwargs)
-
Returns a copy of this field with
values
replaced.
class Grid
-
Base class for all fields.
Important implementations:
- CenteredGrid
- StaggeredGrid
- PointCloud
- Noise
See the
phi.field
module documentation at https://tum-pbs.github.io/PhiFlow/Fields.htmlArgs
elements
- Geometry object specifying the sample points and sizes
values
- values corresponding to elements
extrapolation
- values outside elements
Expand source code
class Field: """ Base class for all fields. Important implementations: * CenteredGrid * StaggeredGrid * PointCloud * Noise See the `phi.field` module documentation at https://tum-pbs.github.io/PhiFlow/Fields.html """ def __init__(self, geometry: Union[Geometry, Tensor], values: Union[Tensor, Number, bool, Callable, FieldInitializer, Geometry, 'Field'], boundary: Union[Number, Extrapolation, 'Field', dict] = 0, **sampling_kwargs): """ Args: elements: Geometry object specifying the sample points and sizes values: values corresponding to elements extrapolation: values outside elements """ assert isinstance(geometry, Geometry), f"geometry must be a Geometry object but got {type(geometry).__name__}" self._boundary: Extrapolation = as_boundary(boundary, geometry) self._geometry: Geometry = geometry if isinstance(values, (Tensor, Number, bool)): values = wrap(values) else: from ._resample import sample values = sample(values, geometry, 'center', self._boundary, **sampling_kwargs) matching_sets = [s for s, s_shape in geometry.sets.items() if s_shape in values.shape] if not matching_sets: values = expand(wrap(values), non_batch(geometry) - 'vector') self._values: Tensor = values math.merge_shapes(values, non_batch(self.sampled_elements).non_channel) # shape check @property def geometry(self) -> Geometry: """ Returns a geometrical representation of the discrete volume elements. The result is a tuple of Geometry objects, each of which can have additional spatial (but not batch) dimensions. For grids, the geometries are boxes while particle fields may be represented as spheres. If this Field has no discrete points, this method returns an empty geometry. """ return self._geometry @property def grid(self) -> UniformGrid: """Cast `self.geometry` to a `phi.geom.UniformGrid`.""" assert isinstance(self._geometry, UniformGrid), f"Geometry is not a UniformGrid but {type(self._geometry)}" return self._geometry @property def mesh(self) -> Mesh: """Cast `self.geometry` to a `phi.geom.Mesh`.""" assert isinstance(self._geometry, Mesh), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry @property def graph(self) -> Graph: """Cast `self.geometry` to a `phi.geom.Graph`.""" assert isinstance(self._geometry, Graph), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry @property def faces(self): return get_faces(self._geometry, self._boundary) @property def face_centers(self): return self._geometry.face_centers # return slice_off_constant_faces(self._geometry.face_centers, self._geometry.boundary_faces, self._boundary) @property def face_normals(self): return self._geometry.face_normals # return slice_off_constant_faces(self._geometry.face_normals, self._geometry.boundary_faces, self._boundary) @property def face_areas(self): return self._geometry.face_areas # return slice_off_constant_faces(self._geometry.face_areas, self._geometry.boundary_faces, self._boundary) @property def sampled_elements(self) -> Geometry: """ If the values represent are sampled at the element centers or represent the whole element, returns `self.geometry`. If the values are sampled at the faces, returns `self.faces`. """ return get_faces(self._geometry, self._boundary) if is_staggered(self._values, self._geometry) else self._geometry @property def elements(self): # raise SyntaxError("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead.") warnings.warn("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead. Field.elements now defaults to Field.geometry.", DeprecationWarning, stacklevel=2) return self._geometry @property def is_centered(self): return not self.is_staggered @property def is_staggered(self): return is_staggered(self._values, self._geometry) @property def center(self) -> Tensor: """ Returns the center points of the `elements` of this `Field`. """ all_points = self._geometry.get_points(self.sampled_at) boundary = self._geometry.get_boundary(self.sampled_at) return slice_off_constant_faces(all_points, boundary, self.extrapolation) @property def points(self): return self.center @property def values(self) -> Tensor: """ Returns the `values` of this `Field`. """ return self._values data = values def numpy(self, order: DimFilter = None): """ Return the field values as `NumPy` array(s). Args: order: Dimension order as `str` or `Shape`. Returns: A single NumPy array for uniform values, else a list of NumPy arrays. """ if order is None and self.is_grid: axes = self._values.shape.only(self._geometry.vector.item_names, reorder=True) order = concat_shapes(self._values.shape.dual, self._values.shape.batch, axes, self._values.shape.channel) if self._values.shape.is_uniform: return self._values.numpy(order) else: assert order is not None, f"order must be specified for non-uniform Field values" order = self._values.shape.only(order, reorder=True) stack_dims = order.non_uniform_shape inner_order = order.without(stack_dims) return [v.numpy(inner_order) for v in unstack(self._values, stack_dims)] def uniform_values(self): """ Returns a uniform tensor containing `values`. For periodic grids, which always have a uniform value tensor, `values' is returned directly. If `values` is not uniform, it is padded as in `StaggeredGrid.staggered_tensor()`. """ if self.values.shape.is_uniform: return self.values else: return self.staggered_tensor() @property def boundary(self) -> Extrapolation: """ Returns the boundary conditions set for this `Field`. Returns: Single `Extrapolation` instance that encodes the (varying) boundary conditions for all boundaries of this field's `elements`. """ return self._boundary @property def extrapolation(self) -> Extrapolation: """ Returns the `Extrapolation` of this `Field`. """ return self._boundary @property def shape(self) -> Shape: """ Returns a shape with the following properties * The spatial dimension names match the dimensions of this Field * The batch dimensions match the batch dimensions of this Field * The channel dimensions match the channels of this Field """ if self.is_grid and '~vector' in self._values.shape: return batch(self._geometry) & self.resolution & non_dual(self._values).without(self.resolution) & self._geometry.shape['vector'] set_shape = self._geometry.sets[self.sampled_at] return batch(self._geometry) & (channel(self._geometry) - 'vector') & set_shape & self._values @property def resolution(self): return self._geometry.shape.non_channel.non_dual.non_batch @property def spatial_rank(self) -> int: """ Spatial rank of the field (1 for 1D, 2 for 2D, 3 for 3D). This is equal to the spatial rank of the `data`. """ return self._geometry.spatial_rank @property def bounds(self) -> BaseBox: """ The bounds represent the area inside which the values of this `Field` are valid. The bounds will also be used as axis limits for plots. The bounds can be set manually in the constructor, otherwise default bounds will be generated. For fields that are valid without bounds, the lower and upper limit of `bounds` is set to `-inf` and `inf`, respectively. Fields whose spatial rank is determined only during sampling return an empty `Box`. """ if isinstance(self._geometry.bounds, BaseBox): return self._geometry.bounds extent = self._geometry.bounding_half_extent().vector.as_dual('_extent') points = self._geometry.center + extent lower = math.min(points, dim=points.shape.non_batch.non_channel) upper = math.max(points, dim=points.shape.non_batch.non_channel) return Box(lower, upper) box = bounds @property def is_grid(self): """A Field represents grid data if its `geometry` is a `phi.geom.UniformGrid` instance.""" return isinstance(self._geometry, UniformGrid) @property def is_mesh(self): """A Field represents mesh data if its `geometry` is a `phi.geom.Mesh` instance.""" return isinstance(self._geometry, Mesh) @property def is_graph(self): """A Field represents graph data if its `geometry` is a `phi.geom.Graph` instance.""" return isinstance(self._geometry, Graph) @property def is_point_cloud(self): """A Field represents graph data if its `geometry` is not a set of connected elements, but rather individual geometric objects.""" if isinstance(self._geometry, (UniformGrid, Mesh, Graph)): return False if isinstance(self._geometry, (BaseBox, Sphere, Point)): return True return True @property def dx(self) -> Tensor: assert spatial(self._geometry), f"dx is only defined for elements with spatial dims but Field has elements {self._geometry.shape}" return self.bounds.size / self.resolution @property def cells(self): assert isinstance(self._geometry, (UniformGrid, Mesh)) return self._geometry def to_grid(self, resolution=math.EMPTY_SHAPE, bounds=None, **resolution_): resolution = resolution.spatial & spatial(**resolution_) if self.is_grid and (not resolution or resolution == self.resolution) and (bounds is None or bounds == self.bounds): return self bounds = self.bounds if bounds is None else bounds if not resolution: half_sizes = self._geometry.bounding_half_extent() if (half_sizes > 0).all: size = math.min(2 * half_sizes, non_batch(half_sizes).non_channel) else: cell_count = non_batch(self._geometry).non_channel.non_dual.volume size = (bounds.volume / cell_count) ** (1 / self.spatial_rank) res = math.maximum(1, math.round(bounds.size / size)) resolution = spatial(**res.vector) return Field(UniformGrid(resolution, bounds), self, self.boundary) def as_points(self, list_dim: Optional[Shape] = instance('elements')) -> 'Field': """ Returns this field as a PointCloud. This replaces the `Field.geometry` with a `phi.geom.Point` instance while leaving the sample points unchanged. See Also: `Field.as_spheres()`. Args: list_dim: If not `None`, packs spatial, instance and dual dims. Defaults to `instance('elements')`. Returns: `Field` with same values and boundaries but `Point` geometry. """ points = self.sampled_elements.center values = self._values if list_dim: dims = non_batch(points).non_channel & non_batch(points).non_channel points = pack_dims(points, dims, list_dim) values = pack_dims(values, dims, list_dim) return Field(Point(points), values, self._boundary) def as_spheres(self, list_dim: Optional[Shape] = instance('elements')) -> 'Field': """ Returns this field as a PointCloud with spherical / circular elements, preserving element volumes. This replaces the `Field.geometry` with a `phi.geom.Sphere` instance while leaving the sample points unchanged. See Also: `Field.as_points()`. Args: list_dim: If not `None`, packs spatial, instance and dual dims. Defaults to `instance('elements')`. Returns: `Field` with same values and boundaries but `Sphere` geometry. """ points = self.sampled_elements.center volumes = self.sampled_elements.volume values = self._values if list_dim: dims = non_batch(points).non_channel & non_batch(points).non_channel points = pack_dims(points, dims, list_dim) values = pack_dims(values, dims, list_dim) volumes = pack_dims(volumes, dims, list_dim) return Field(Sphere(points, volume=volumes), values, self._boundary) def at_centers(self, **kwargs) -> 'Field': """ Interpolates the values to the cell centers. See Also: `Field.at_faces()`, `Field.at()`, `resample`. Args: **kwargs: Sampling arguments. Returns: `CenteredGrid` sampled at cell centers. """ if self.is_centered: return self from ._resample import sample values = sample(self, self._geometry, at='center', boundary=self._boundary, **kwargs) return Field(self._geometry, values, self._boundary) def at_faces(self, boundary=None, **kwargs) -> 'Field': if self.is_staggered and not boundary: return self boundary = as_boundary(boundary, self._geometry) if boundary else self._boundary from ._resample import sample values = sample(self, self._geometry, at='face', boundary=boundary, **kwargs) return Field(self._geometry, values, boundary) @property def sampled_at(self): matching_sets = [s for s, s_shape in self._geometry.sets.items() if s_shape in self._values.shape] return matching_sets[-1] def at(self, representation: Union['Field', Geometry], keep_boundary=False, **kwargs) -> 'Field': """ Short for `resample(self, representation)` See Also `resample()`. Returns: Field object of same type as `representation` """ from ._resample import resample return resample(self, representation, keep_boundary, **kwargs) def sample(self, where: Union[Geometry, 'Field', Tensor], at: str = 'center', **kwargs) -> 'Tensor': """ Sample the values of this `Field` at the given location or geometry. Args: where: Location `Tensor` or `Geometry` or at: `'center'` or `'face'`. **kwargs: Sampling arguments. Returns: `Tensor` """ from ._resample import sample return sample(self, where, at, **kwargs) def closest_values(self, points: Tensor): """ Sample the closest grid point values of this field at the world-space locations (in physical units) given by `points`. Points must have a single channel dimension named `vector`. It may additionally contain any number of batch and spatial dimensions, all treated as batch dimensions. Args: points: world-space locations Returns: Closest grid point values as a `Tensor`. For each dimension, the grid points immediately left and right of the sample points are evaluated. For each point in `points`, a *2^d* cube of points is determined where *d* is the number of spatial dimensions of this field. These values are stacked along the new dimensions `'closest_<dim>'` where `<dim>` refers to the name of a spatial dimension. """ warnings.warn("Field.closest_values() is deprecated.", DeprecationWarning, stacklevel=2) if isinstance(points, Geometry): points = points.center # --- CenteredGrid --- local_points = self.box.global_to_local(points) * self.resolution - 0.5 return math.closest_grid_values(self.values, local_points, self.extrapolation) # --- StaggeredGrid --- if 'staggered_direction' in points.shape: points_ = math.unstack(points, '~vector') channels = [component.closest_values(p) for p, component in zip(points_, self.vector.unstack())] else: channels = [component.closest_values(points) for component in self.vector.unstack()] return math.stack(channels, points.shape['~vector']) def with_values(self, values, **sampling_kwargs): """ Returns a copy of this field with `values` replaced. """ if not isinstance(values, (Tensor, Number)): from ._resample import sample values = sample(values, self._geometry, self.sampled_at, self._boundary, dot_face_normal=self._geometry if 'vector' not in self._values.shape else None, **sampling_kwargs) else: if not spatial(values): geo_shape = self.sampled_elements.shape if self.is_staggered else self._geometry.shape if '~vector' in geo_shape and 'vector' in shape(values) and '~vector' not in shape(values): values = values.vector.as_dual() values = expand(wrap(values), geo_shape.non_batch.non_channel) return Field(self._geometry, values, self._boundary) def with_boundary(self, boundary): """ Returns a copy of this field with the `boundary` replaced. """ boundary = as_boundary(boundary, self._geometry) boundary_elements = 'boundary_faces' if self.is_staggered else 'boundary_elements' old_determined_slices = {k: s for k, s in getattr(self._geometry, boundary_elements).items() if self._boundary.determines_boundary_values(k)} new_determined_slices = {k: s for k, s in getattr(self._geometry, boundary_elements).items() if boundary.determines_boundary_values(k)} if old_determined_slices.values() == new_determined_slices.values(): return Field(self._geometry, self._values, boundary) # ToDo unnecessary once the rest is implemented to_add = {k: sl for k, sl in old_determined_slices.items() if sl not in new_determined_slices.values()} to_remove = [sl for sl in new_determined_slices.values() if sl not in old_determined_slices.values()] values = math.slice_off(self._values, *to_remove) if to_add: if self.is_mesh: values = self.mesh.pad_boundary(values, to_add, self._boundary) elif self.is_grid and self.is_staggered: values = self._values.vector.dual.as_channel() to_add = {k: {'vector' if dim == '~vector' else dim: v for dim, v in sl.items()} for k, sl in to_add.items()} values = math.pad(values, list(to_add.values()), self._boundary, bounds=self.bounds) values = values.vector.as_dual() else: values = math.pad(values, list(to_add.values()), self._boundary, bounds=self.bounds) return Field(self._geometry, values, boundary) with_extrapolation = with_boundary def with_bounds(self, bounds: Box): """ Returns a copy of this field with `bounds` replaced. """ order = list(bounds.vector.item_names) geometry = self._geometry.vector[order] new_shape = self._values.shape.without(order) & self._values.shape.only(order, reorder=True) values = math.transpose(self._values, new_shape) return Field(geometry, values, self._boundary) def with_geometry(self, elements: Geometry): """ Returns a copy of this field with `elements` replaced. """ assert non_batch(elements) == non_batch(self._geometry), f"Field.with_elements() only accepts elements with equal non-batch dimensions but got {elements.shape} for Field with shape {self._geometry.shape}" return Field(elements, self._values, self._boundary) with_elements = with_geometry def shifted(self, delta: Tensor) -> 'Field': """ Move the positions of this field's `geometry` by `delta`. See Also: `Field.shifted_to`. Args: delta: Shift amount for each center position of `geometry`. Returns: New `Field` sampled at `geometry.center + delta`. """ return self.with_geometry(self._geometry.shifted(delta)) def shifted_to(self, position: Tensor) -> 'Field': """ Move the positions of this field's `geometry` to `positions`. See Also: `Field.shifted`. Args: position: New center positions of `geometry`. Returns: New `Field` sampled at given positions. """ return self.with_geometry(self._geometry.at(position)) def pad(self, widths: Union[int, tuple, list, dict]) -> 'Field': """ Alias for `phi.field.pad()`. Pads this `Field` using its extrapolation. Unlike padding the values, this function also affects the `geometry` of the field, changing its size and origin depending on `widths`. Args: widths: Either `int` or `(lower, upper)` to pad the same number of cells in all spatial dimensions or `dict` mapping dimension names to `(lower, upper)`. Returns: Padded `Field` """ from ._field_math import pad return pad(self, widths) def gradient(self, boundary: Extrapolation = None, at: str = 'center', dims: math.DimFilter = spatial, stack_dim: Union[Shape, str] = channel('vector'), order=2, implicit: Solve = None, scheme=None, upwind: 'Field' = None, gradient_extrapolation: Extrapolation = None): """Alias for `phi.field.spatial_gradient`""" from ._field_math import spatial_gradient return spatial_gradient(self, boundary=boundary, at=at, dims=dims, stack_dim=stack_dim, order=order, implicit=implicit, scheme=scheme, upwind=upwind, gradient_extrapolation=gradient_extrapolation) def divergence(self, order=2, implicit: Solve = None, upwind: 'Field' = None): """Alias for `phi.field.divergence`""" from ._field_math import divergence return divergence(self, order=order, implicit=implicit, upwind=upwind) def curl(self, at='corner'): """Alias for `phi.field.curl`""" from ._field_math import curl return curl(self, at=at) def laplace(self, axes: DimFilter = spatial, gradient: 'Field' = None, order=2, implicit: math.Solve = None, weights: Union[Tensor, 'Field'] = None, upwind: 'Field' = None, correct_skew=True): """Alias for `phi.field.laplace`""" from ._field_math import laplace return laplace(self, axes=axes, gradient=gradient, order=order, implicit=implicit, weights=weights, upwind=upwind, correct_skew=correct_skew) def downsample(self, factor: int): from ._field_math import downsample2x result = self while factor >= 2: result = downsample2x(result) factor /= 2 if math.close(factor, 1.): return result from ._resample import resample raise NotImplementedError(f"downsample does not support fractional re-sampling. Only 2^n currently supported.") def staggered_tensor(self) -> Tensor: """ Stacks all component grids into a single uniform `phi.math.Tensor`. The individual components are padded to a common (larger) shape before being stacked. The shape of the returned tensor is exactly one cell larger than the grid `resolution` in every spatial dimension. Returns: Uniform `phi.math.Tensor`. """ assert self.resolution.names == self.shape.get_item_names('vector'), "Field.staggered_tensor() only defined for Fields whose vector components match the resolution" padded = [] for dim, component in zip(self.resolution.names, self.vector): widths = {d: (0, 1) for d in self.resolution.names} lo_valid, up_valid = self.extrapolation.valid_outer_faces(dim) widths[dim] = (int(not lo_valid), int(not up_valid)) padded.append(math.pad(component.values, widths, self.extrapolation[{'vector': dim}], bounds=self.bounds)) result = math.stack(padded, channel(vector=self.resolution)) assert result.shape.is_uniform return result @staticmethod def __stack__(values: tuple, dim: Shape, **kwargs) -> 'Field': from ._field_math import stack return stack(values, dim, kwargs.get('bounds', None)) @staticmethod def __concat__(values: tuple, dim: str, **kwargs) -> 'Field': from ._field_math import concat return concat(values, dim) def __and__(self, other): assert isinstance(other, Field) assert instance(self).rank == instance(other).rank == 1, f"Can only use & on PointClouds that have a single instance dimension but got shapes {self.shape} & {other.shape}" from ._field_math import concat return concat([self, other], instance(self)) def __matmul__(self, other: 'Field'): # value @ representation # Deprecated. Use `resample(value, field)` instead. warnings.warn("value @ field is deprecated. Use resample(value, field) instead.", DeprecationWarning) from ._resample import resample return resample(self, to=other, keep_boundary=False) def __rmatmul__(self, other): # values @ representation if isinstance(other, (Geometry, Number, tuple, list, FieldInitializer)): warnings.warn("value @ field is deprecated. Use resample(value, field) instead.", DeprecationWarning) from ._resample import resample return resample(other, to=self, keep_boundary=False) return NotImplemented def __rshift__(self, other): if isinstance(other, (Field, Geometry)): warnings.warn(">> operator for Fields is deprecated. Use field.at(), the constructor or obj @ field instead.", SyntaxWarning, stacklevel=2) return self.at(other, keep_boundary=False) else: return NotImplemented def __rrshift__(self, other): return self.with_values(other) def __lshift__(self, other): return self.with_values(other) def __rrshift__(self, other): warnings.warn(">> operator for Fields is deprecated. Use field.at(), the constructor or obj @ field instead.", SyntaxWarning, stacklevel=2) if not isinstance(self, Field): return NotImplemented if isinstance(other, (Geometry, float, int, complex, tuple, list, FieldInitializer)): from ._resample import resample return resample(other, to=self, keep_boundary=False) return NotImplemented def __getitem__(self, item) -> 'Field': """ Access a slice of the Field. The returned `Field` may be of a different type than `self`. Args: item: `dict` mapping dimensions (`str`) to selections (`int` or `slice`) or other supported type, such as `int` or `str`. Returns: Sliced `Field`. """ item = slicing_dict(self, item) if not item: return self boundary = domain_slice(self._boundary, item, self.resolution) item_without_vec = {dim: selection for dim, selection in item.items() if dim != 'vector'} geometry = self._geometry[item_without_vec] if self.is_staggered and 'vector' in item and '~vector' in self.geometry.face_shape: assert isinstance(self._geometry, UniformGrid), f"Vector slicing is only supported for grids" dims = item['vector'] dims_ = self._geometry.shape['vector'].after_gather({'vector': dims}) dims = dims_.item_names[0] if dims_ else [dims] if isinstance(dims, str) else [self._geometry.shape['vector'].item_names[0][dims]] proj_dims = set(self.resolution.names) - set(dims) if any(dim not in item for dim in proj_dims): # warnings.warn(f"Projecting a staggered grid (by slicing 'vector' without the corresponding spatial dims) will return a non-staggered grid. The projected dims {proj_dims} were not sliced off.\nFull slice: {item}") item['~vector'] = item['vector'] del item['vector'] geometry = self.sampled_elements[item] else: item['~vector'] = dims del item['vector'] values = self._values[item] return Field(geometry, values, boundary) def __getattr__(self, name: str) -> BoundDim: return BoundDim(self, name) def dimension(self, name: str): """ Returns a reference to one of the dimensions of this field. The dimension reference can be used the same way as a `Tensor` dimension reference. Notable properties and methods of a dimension reference are: indexing using `[index]`, `unstack()`, `size`, `exists`, `is_batch`, `is_spatial`, `is_channel`. A shortcut to calling this function is the syntax `field.<dim_name>` which calls `field.dimension(<dim_name>)`. Args: name: dimension name Returns: dimension reference """ return BoundDim(self, name) def __value_attrs__(self): return '_values', def __variable_attrs__(self): return '_values', '_geometry', '_boundary' def __expand__(self, dims: Shape, **kwargs) -> 'Field': return self.with_values(expand(self.values, dims, **kwargs)) def __replace_dims__(self, dims: Tuple[str, ...], new_dims: Shape, **kwargs) -> 'Field': elements = math.rename_dims(self._geometry, dims, new_dims) values = math.rename_dims(self._values, dims, new_dims) extrapolation = math.rename_dims(self._boundary, dims, new_dims, **kwargs) return Field(elements, values, extrapolation) def __eq__(self, other): if not isinstance(other, Field): return False if self._geometry != other._geometry: return False if self._boundary != other.boundary: return False return math.always_close(self._values, other._values) def __hash__(self): return hash((self._geometry, self._boundary)) def __mul__(self, other): return self._op2(other, lambda d1, d2: d1 * d2) __rmul__ = __mul__ def __truediv__(self, other): return self._op2(other, lambda d1, d2: d1 / d2) def __rtruediv__(self, other): return self._op2(other, lambda d1, d2: d2 / d1) def __sub__(self, other): return self._op2(other, lambda d1, d2: d1 - d2) def __rsub__(self, other): return self._op2(other, lambda d1, d2: d2 - d1) def __add__(self, other): return self._op2(other, lambda d1, d2: d1 + d2) __radd__ = __add__ def __pow__(self, power, modulo=None): return self._op2(power, lambda f, p: f ** p) def __neg__(self): return self._op1(lambda x: -x) def __gt__(self, other): return self._op2(other, lambda x, y: x > y) def __ge__(self, other): return self._op2(other, lambda x, y: x >= y) def __lt__(self, other): return self._op2(other, lambda x, y: x < y) def __le__(self, other): return self._op2(other, lambda x, y: x <= y) def __abs__(self): return self._op1(lambda x: abs(x)) def _op1(self: 'Field', operator: Callable) -> 'Field': """ Perform an operation on the data of this field. Args: operator: function that accepts tensors and extrapolations and returns objects of the same type and dimensions Returns: Field of same type """ values = operator(self.values) extrapolation_ = operator(self._boundary) return self.with_values(values).with_extrapolation(extrapolation_) def _op2(self, other, operator) -> 'Field': if isinstance(other, Geometry): raise ValueError(f"Cannot combine {self.__class__.__name__} with a Geometry, got {type(other)}") if isinstance(other, Field): if self._geometry == other._geometry: values = operator(self._values, other.values) extrapolation_ = operator(self._boundary, other.extrapolation) return Field(self._geometry, values, extrapolation_) from ._resample import sample other_values = sample(other, self._geometry, self.sampled_at, self.boundary, dot_face_normal=self._geometry) values = operator(self._values, other_values) boundary = operator(self._boundary, other.extrapolation) return Field(self._geometry, values, boundary) else: if isinstance(other, (tuple, list)) and len(other) == self.spatial_rank: other = math.wrap(other, self._geometry.shape['vector']) else: other = math.wrap(other) # try: # boundary = operator(self._boundary, as_boundary(other, self._geometry)) # except TypeError: # e.g. ZERO_GRADIENT + constant boundary = self._boundary # constants don't affect the boundary conditions (legacy reasons) if 'vector' in self.shape and 'vector' not in self.values.shape and '~vector' in self.values.shape: other = other.vector.as_dual() values = operator(self._values, other) return Field(self._geometry, values, boundary) def __repr__(self): if self.is_grid: type_name = "Grid" if self.is_centered else "Grid faces" elif self.is_mesh: type_name = "Mesh" if self.is_centered else "Mesh faces" elif self.is_point_cloud: type_name = "Point cloud" if self.is_centered else "Point cloud edges" elif self.is_graph: type_name = "Graph" if self.is_centered else "Graph edges" else: type_name = self.__class__.__name__ if self._values is not None: return f"{type_name}[{self.values}, ext={self._boundary}]" else: return f"{type_name}[{self.resolution}, ext={self._boundary}]" def grid_scatter(self, *args, **kwargs): """Deprecated. Use `sample` with `scatter=True` instead.""" warnings.warn("Field.grid_scatter() is deprecated. Use field.sample() with scatter=True instead.", DeprecationWarning, stacklevel=2) from ._resample import grid_scatter return grid_scatter(self, *args, **kwargs) def as_boundary(self) -> Extrapolation: """ Returns an `Extrapolation` representing this 'Field''s values as a Dirichlet (constant) boundary. If this `Field` encloses the required boundaries, its values will be interpolated to the required boundaries. If boundaries outside of this `Field`'s sampled domain are required, this `Field`'s boundary conditions will be applied to determine the boundary values. Returns: `Extrapolation` """ from ._embed import FieldEmbedding return FieldEmbedding(self)
Subclasses
- phi.field._mask.HardGeometryMask
Instance variables
prop boundary : phiml.math.extrapolation.Extrapolation
-
Returns the boundary conditions set for this
Field
.Returns
Single
Extrapolation
instance that encodes the (varying) boundary conditions for all boundaries of this field'selements
.Expand source code
@property def boundary(self) -> Extrapolation: """ Returns the boundary conditions set for this `Field`. Returns: Single `Extrapolation` instance that encodes the (varying) boundary conditions for all boundaries of this field's `elements`. """ return self._boundary
prop bounds : phi.geom._box.BaseBox
-
The bounds represent the area inside which the values of this
Field
are valid. The bounds will also be used as axis limits for plots.The bounds can be set manually in the constructor, otherwise default bounds will be generated.
For fields that are valid without bounds, the lower and upper limit of
bounds
is set to-inf
andinf
, respectively.Fields whose spatial rank is determined only during sampling return an empty
Box
.Expand source code
@property def bounds(self) -> BaseBox: """ The bounds represent the area inside which the values of this `Field` are valid. The bounds will also be used as axis limits for plots. The bounds can be set manually in the constructor, otherwise default bounds will be generated. For fields that are valid without bounds, the lower and upper limit of `bounds` is set to `-inf` and `inf`, respectively. Fields whose spatial rank is determined only during sampling return an empty `Box`. """ if isinstance(self._geometry.bounds, BaseBox): return self._geometry.bounds extent = self._geometry.bounding_half_extent().vector.as_dual('_extent') points = self._geometry.center + extent lower = math.min(points, dim=points.shape.non_batch.non_channel) upper = math.max(points, dim=points.shape.non_batch.non_channel) return Box(lower, upper)
prop box : phi.geom._box.BaseBox
-
The bounds represent the area inside which the values of this
Field
are valid. The bounds will also be used as axis limits for plots.The bounds can be set manually in the constructor, otherwise default bounds will be generated.
For fields that are valid without bounds, the lower and upper limit of
bounds
is set to-inf
andinf
, respectively.Fields whose spatial rank is determined only during sampling return an empty
Box
.Expand source code
@property def bounds(self) -> BaseBox: """ The bounds represent the area inside which the values of this `Field` are valid. The bounds will also be used as axis limits for plots. The bounds can be set manually in the constructor, otherwise default bounds will be generated. For fields that are valid without bounds, the lower and upper limit of `bounds` is set to `-inf` and `inf`, respectively. Fields whose spatial rank is determined only during sampling return an empty `Box`. """ if isinstance(self._geometry.bounds, BaseBox): return self._geometry.bounds extent = self._geometry.bounding_half_extent().vector.as_dual('_extent') points = self._geometry.center + extent lower = math.min(points, dim=points.shape.non_batch.non_channel) upper = math.max(points, dim=points.shape.non_batch.non_channel) return Box(lower, upper)
prop cells
-
Expand source code
@property def cells(self): assert isinstance(self._geometry, (UniformGrid, Mesh)) return self._geometry
prop center : phiml.math._tensors.Tensor
-
Returns the center points of the
elements
of thisField
.Expand source code
@property def center(self) -> Tensor: """ Returns the center points of the `elements` of this `Field`. """ all_points = self._geometry.get_points(self.sampled_at) boundary = self._geometry.get_boundary(self.sampled_at) return slice_off_constant_faces(all_points, boundary, self.extrapolation)
prop data : phiml.math._tensors.Tensor
-
Returns the
values
of thisField
.Expand source code
@property def values(self) -> Tensor: """ Returns the `values` of this `Field`. """ return self._values
prop dx : phiml.math._tensors.Tensor
-
Expand source code
@property def dx(self) -> Tensor: assert spatial(self._geometry), f"dx is only defined for elements with spatial dims but Field has elements {self._geometry.shape}" return self.bounds.size / self.resolution
prop elements
-
Expand source code
@property def elements(self): # raise SyntaxError("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead.") warnings.warn("Field.elements is deprecated. Use Field.geometry or Field.sampled_elements instead. Field.elements now defaults to Field.geometry.", DeprecationWarning, stacklevel=2) return self._geometry
prop extrapolation : phiml.math.extrapolation.Extrapolation
-
Returns the
Extrapolation
of thisField
.Expand source code
@property def extrapolation(self) -> Extrapolation: """ Returns the `Extrapolation` of this `Field`. """ return self._boundary
prop face_areas
-
Expand source code
@property def face_areas(self): return self._geometry.face_areas # return slice_off_constant_faces(self._geometry.face_areas, self._geometry.boundary_faces, self._boundary)
prop face_centers
-
Expand source code
@property def face_centers(self): return self._geometry.face_centers # return slice_off_constant_faces(self._geometry.face_centers, self._geometry.boundary_faces, self._boundary)
prop face_normals
-
Expand source code
@property def face_normals(self): return self._geometry.face_normals # return slice_off_constant_faces(self._geometry.face_normals, self._geometry.boundary_faces, self._boundary)
prop faces
-
Expand source code
@property def faces(self): return get_faces(self._geometry, self._boundary)
prop geometry : phi.geom._geom.Geometry
-
Returns a geometrical representation of the discrete volume elements. The result is a tuple of Geometry objects, each of which can have additional spatial (but not batch) dimensions.
For grids, the geometries are boxes while particle fields may be represented as spheres.
If this Field has no discrete points, this method returns an empty geometry.
Expand source code
@property def geometry(self) -> Geometry: """ Returns a geometrical representation of the discrete volume elements. The result is a tuple of Geometry objects, each of which can have additional spatial (but not batch) dimensions. For grids, the geometries are boxes while particle fields may be represented as spheres. If this Field has no discrete points, this method returns an empty geometry. """ return self._geometry
prop graph : phi.geom._graph.Graph
-
Cast
self.geometry
to aGraph
.Expand source code
@property def graph(self) -> Graph: """Cast `self.geometry` to a `phi.geom.Graph`.""" assert isinstance(self._geometry, Graph), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry
prop grid : phi.geom._grid.UniformGrid
-
Cast
self.geometry
to aUniformGrid
.Expand source code
@property def grid(self) -> UniformGrid: """Cast `self.geometry` to a `phi.geom.UniformGrid`.""" assert isinstance(self._geometry, UniformGrid), f"Geometry is not a UniformGrid but {type(self._geometry)}" return self._geometry
prop is_centered
-
Expand source code
@property def is_centered(self): return not self.is_staggered
prop is_graph
-
A Field represents graph data if its
geometry
is aGraph
instance.Expand source code
@property def is_graph(self): """A Field represents graph data if its `geometry` is a `phi.geom.Graph` instance.""" return isinstance(self._geometry, Graph)
prop is_grid
-
A Field represents grid data if its
geometry
is aUniformGrid
instance.Expand source code
@property def is_grid(self): """A Field represents grid data if its `geometry` is a `phi.geom.UniformGrid` instance.""" return isinstance(self._geometry, UniformGrid)
prop is_mesh
-
A Field represents mesh data if its
geometry
is aMesh
instance.Expand source code
@property def is_mesh(self): """A Field represents mesh data if its `geometry` is a `phi.geom.Mesh` instance.""" return isinstance(self._geometry, Mesh)
prop is_point_cloud
-
A Field represents graph data if its
geometry
is not a set of connected elements, but rather individual geometric objects.Expand source code
@property def is_point_cloud(self): """A Field represents graph data if its `geometry` is not a set of connected elements, but rather individual geometric objects.""" if isinstance(self._geometry, (UniformGrid, Mesh, Graph)): return False if isinstance(self._geometry, (BaseBox, Sphere, Point)): return True return True
prop is_staggered
-
Expand source code
@property def is_staggered(self): return is_staggered(self._values, self._geometry)
prop mesh : phi.geom._mesh.Mesh
-
Cast
self.geometry
to aMesh
.Expand source code
@property def mesh(self) -> Mesh: """Cast `self.geometry` to a `phi.geom.Mesh`.""" assert isinstance(self._geometry, Mesh), f"Geometry is not a mesh but {type(self._geometry)}" return self._geometry
prop points
-
Expand source code
@property def points(self): return self.center
prop resolution
-
Expand source code
@property def resolution(self): return self._geometry.shape.non_channel.non_dual.non_batch
prop sampled_at
-
Expand source code
@property def sampled_at(self): matching_sets = [s for s, s_shape in self._geometry.sets.items() if s_shape in self._values.shape] return matching_sets[-1]
prop sampled_elements : phi.geom._geom.Geometry
-
If the values represent are sampled at the element centers or represent the whole element, returns
self.geometry
. If the values are sampled at the faces, returnsself.faces
.Expand source code
@property def sampled_elements(self) -> Geometry: """ If the values represent are sampled at the element centers or represent the whole element, returns `self.geometry`. If the values are sampled at the faces, returns `self.faces`. """ return get_faces(self._geometry, self._boundary) if is_staggered(self._values, self._geometry) else self._geometry
prop shape : phiml.math._shape.Shape
-
Returns a shape with the following properties
- The spatial dimension names match the dimensions of this Field
- The batch dimensions match the batch dimensions of this Field
- The channel dimensions match the channels of this Field
Expand source code
@property def shape(self) -> Shape: """ Returns a shape with the following properties * The spatial dimension names match the dimensions of this Field * The batch dimensions match the batch dimensions of this Field * The channel dimensions match the channels of this Field """ if self.is_grid and '~vector' in self._values.shape: return batch(self._geometry) & self.resolution & non_dual(self._values).without(self.resolution) & self._geometry.shape['vector'] set_shape = self._geometry.sets[self.sampled_at] return batch(self._geometry) & (channel(self._geometry) - 'vector') & set_shape & self._values
prop spatial_rank : int
-
Spatial rank of the field (1 for 1D, 2 for 2D, 3 for 3D). This is equal to the spatial rank of the
data
.Expand source code
@property def spatial_rank(self) -> int: """ Spatial rank of the field (1 for 1D, 2 for 2D, 3 for 3D). This is equal to the spatial rank of the `data`. """ return self._geometry.spatial_rank
prop values : phiml.math._tensors.Tensor
-
Returns the
values
of thisField
.Expand source code
@property def values(self) -> Tensor: """ Returns the `values` of this `Field`. """ return self._values
Methods
def as_boundary(self) ‑> phiml.math.extrapolation.Extrapolation
-
Returns an
Extrapolation
representing this 'Field''s values as a Dirichlet (constant) boundary. If thisField
encloses the required boundaries, its values will be interpolated to the required boundaries. If boundaries outside of thisField
's sampled domain are required, thisField
's boundary conditions will be applied to determine the boundary values.Returns
Extrapolation
def as_points(self, list_dim: Optional[phiml.math._shape.Shape] = (elementsⁱ=None)) ‑> phi.field._field.Field
-
Returns this field as a PointCloud. This replaces the
Field.geometry
with aPoint
instance while leaving the sample points unchanged.See Also:
Field.as_spheres()
.Args
list_dim
- If not
None
, packs spatial, instance and dual dims. Defaults toinstance('elements')
.
Returns
Field
with same values and boundaries butPoint
geometry. def as_spheres(self, list_dim: Optional[phiml.math._shape.Shape] = (elementsⁱ=None)) ‑> phi.field._field.Field
-
Returns this field as a PointCloud with spherical / circular elements, preserving element volumes. This replaces the
Field.geometry
with aSphere
instance while leaving the sample points unchanged.See Also:
Field.as_points()
.Args
list_dim
- If not
None
, packs spatial, instance and dual dims. Defaults toinstance('elements')
.
Returns
Field
with same values and boundaries butSphere
geometry. def at(self, representation: Union[ForwardRef('Field'), phi.geom._geom.Geometry], keep_boundary=False, **kwargs) ‑> phi.field._field.Field
-
Short for
resample()(self, representation)
See Also
resample()
.Returns
Field object of same type as
representation
def at_centers(self, **kwargs) ‑> phi.field._field.Field
-
Interpolates the values to the cell centers.
See Also:
Field.at_faces()
,Field.at()
,resample()
.Args
**kwargs
- Sampling arguments.
Returns
CenteredGrid()
sampled at cell centers. def at_faces(self, boundary=None, **kwargs) ‑> phi.field._field.Field
def closest_values(self, points: phiml.math._tensors.Tensor)
-
Sample the closest grid point values of this field at the world-space locations (in physical units) given by
points
. Points must have a single channel dimension namedvector
. It may additionally contain any number of batch and spatial dimensions, all treated as batch dimensions.Args
points
- world-space locations
Returns
Closest grid point values as a
Tensor
. For each dimension, the grid points immediately left and right of the sample points are evaluated. For each point inpoints
, a 2^d cube of points is determined where d is the number of spatial dimensions of this field. These values are stacked along the new dimensions'closest_<dim>'
where<dim>
refers to the name of a spatial dimension. def curl(self, at='corner')
-
Alias for
curl()
def dimension(self, name: str)
-
Returns a reference to one of the dimensions of this field.
The dimension reference can be used the same way as a
Tensor
dimension reference. Notable properties and methods of a dimension reference are: indexing using[index]
,unstack()
,size
,exists
,is_batch
,is_spatial
,is_channel
.A shortcut to calling this function is the syntax
field.<dim_name>
which callsfield.dimension(<dim_name>)
.Args
name
- dimension name
Returns
dimension reference
def divergence(self, order=2, implicit: phiml.math._optimize.Solve = None, upwind: Field = None)
-
Alias for
divergence()
def downsample(self, factor: int)
def gradient(self, boundary: phiml.math.extrapolation.Extrapolation = None, at: str = 'center', dims: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = <function spatial>, stack_dim: Union[phiml.math._shape.Shape, str] = (vectorᶜ=None), order=2, implicit: phiml.math._optimize.Solve = None, scheme=None, upwind: Field = None, gradient_extrapolation: phiml.math.extrapolation.Extrapolation = None)
-
Alias for
spatial_gradient()
def grid_scatter(self, *args, **kwargs)
-
Deprecated. Use
sample()
withscatter=True
instead. def laplace(self, axes: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = <function spatial>, gradient: Field = None, order=2, implicit: phiml.math._optimize.Solve = None, weights: Union[phiml.math._tensors.Tensor, ForwardRef('Field')] = None, upwind: Field = None, correct_skew=True)
-
Alias for
laplace()
def numpy(self, order: Union[str, tuple, list, set, ForwardRef('Shape'), Callable] = None)
-
Return the field values as
NumPy
array(s).Args
order
- Dimension order as
str
orShape
.
Returns
A single NumPy array for uniform values, else a list of NumPy arrays.
def pad(self, widths: Union[int, tuple, list, dict]) ‑> phi.field._field.Field
-
Alias for
pad()
.Pads this
Field
using its extrapolation.Unlike padding the values, this function also affects the
geometry
of the field, changing its size and origin depending onwidths
.Args
widths
- Either
int
or(lower, upper)
to pad the same number of cells in all spatial dimensions ordict
mapping dimension names to(lower, upper)
.
Returns
Padded
Field
def sample(self, where: Union[phi.geom._geom.Geometry, ForwardRef('Field'), phiml.math._tensors.Tensor], at: str = 'center', **kwargs) ‑> phiml.math._tensors.Tensor
-
Sample the values of this
Field
at the given location or geometry.Args
where
- Location
Tensor
orGeometry
or at
'center'
or'face'
.**kwargs
- Sampling arguments.
Returns
Tensor
def shifted(self, delta: phiml.math._tensors.Tensor) ‑> phi.field._field.Field
-
Move the positions of this field's
geometry
bydelta
.See Also:
Field.shifted_to()
.Args
delta
- Shift amount for each center position of
geometry
.
Returns
New
Field
sampled atgeometry.center + delta
. def shifted_to(self, position: phiml.math._tensors.Tensor) ‑> phi.field._field.Field
-
Move the positions of this field's
geometry
topositions
.See Also:
Field.shifted()
.Args
position
- New center positions of
geometry
.
Returns
New
Field
sampled at given positions. def staggered_tensor(self) ‑> phiml.math._tensors.Tensor
-
Stacks all component grids into a single uniform
phi.math.Tensor
. The individual components are padded to a common (larger) shape before being stacked. The shape of the returned tensor is exactly one cell larger than the gridresolution
in every spatial dimension.Returns
Uniform
phi.math.Tensor
. def to_grid(self, resolution=(), bounds=None, **resolution_)
def uniform_values(self)
-
Returns a uniform tensor containing
values
.For periodic grids, which always have a uniform value tensor, `values' is returned directly. If
values
is not uniform, it is padded as inStaggeredGrid.staggered_tensor()
. def with_boundary(self, boundary)
-
Returns a copy of this field with the
boundary
replaced. def with_bounds(self, bounds: phi.geom._box.Box)
-
Returns a copy of this field with
bounds
replaced. def with_elements(self, elements: phi.geom._geom.Geometry)
-
Returns a copy of this field with
elements
replaced. def with_extrapolation(self, boundary)
-
Returns a copy of this field with the
boundary
replaced. def with_geometry(self, elements: phi.geom._geom.Geometry)
-
Returns a copy of this field with
elements
replaced. def with_values(self, values, **sampling_kwargs)
-
Returns a copy of this field with
values
replaced.
class HardGeometryMask (geometry: phi.geom._geom.Geometry)
-
Deprecated since version 1.3. Use
mask()
orresample()
instead.Args
elements
- Geometry object specifying the sample points and sizes
values
- values corresponding to elements
extrapolation
- values outside elements
Expand source code
class HardGeometryMask(Field): """ Deprecated since version 1.3. Use `phi.field.mask()` or `phi.field.resample()` instead. """ def __init__(self, geometry: Geometry): super().__init__(geometry, 1, 0) warnings.warn("HardGeometryMask and SoftGeometryMask are deprecated. Use field.mask or field.resample instead.", DeprecationWarning, stacklevel=2) @property def shape(self): return self.geometry.shape.non_channel def _sample(self, geometry: Geometry, **kwargs) -> Tensor: return math.to_float(self.geometry.lies_inside(geometry.center)) def __getitem__(self, item: dict): return HardGeometryMask(self.geometry[item])
Ancestors
- phi.field._field.Field
Subclasses
- phi.field._mask.SoftGeometryMask
Instance variables
prop shape
-
Returns a shape with the following properties
- The spatial dimension names match the dimensions of this Field
- The batch dimensions match the batch dimensions of this Field
- The channel dimensions match the channels of this Field
Expand source code
@property def shape(self): return self.geometry.shape.non_channel
class Noise (*shape: phiml.math._shape.Shape, scale=10.0, smoothness=1.0, **channel_dims)
-
Generates random noise fluctuations which can be configured in physical size and smoothness. Each time values are sampled from a Noise field, a new noise field is generated.
Noise is typically used as an initializer for CenteredGrids or StaggeredGrids.
Args
shape
- Batch and channel dimensions. Spatial dimensions will be added automatically once sampled on a grid.
scale
- Size of noise fluctuations in physical units.
smoothness
- Determines how quickly high frequencies die out.
**dims
- Additional dimensions, added to
shape
.
Expand source code
class Noise(FieldInitializer): """ Generates random noise fluctuations which can be configured in physical size and smoothness. Each time values are sampled from a Noise field, a new noise field is generated. Noise is typically used as an initializer for CenteredGrids or StaggeredGrids. """ def __init__(self, *shape: math.Shape, scale=10., smoothness=1.0, **channel_dims): """ Args: shape: Batch and channel dimensions. Spatial dimensions will be added automatically once sampled on a grid. scale: Size of noise fluctuations in physical units. smoothness: Determines how quickly high frequencies die out. **dims: Additional dimensions, added to `shape`. """ self.scale = scale self.smoothness = smoothness self._shape = math.concat_shapes(*shape, channel(**channel_dims)) def _sample(self, geometry: Geometry, at: str, boundaries: Extrapolation, **kwargs) -> Tensor: if isinstance(geometry, UniformGrid): if at == 'center': return self.grid_sample(geometry.resolution, geometry.grid_size) elif at == 'face': result = {dim: self.grid_sample(grid.resolution, grid.grid_size) for dim, grid in geometry.staggered_cells(boundaries).items()} return vec(geometry.face_shape.dual, **result) raise NotImplementedError(f"{type(geometry)} not supported. Only UniformGrid allowed.") def grid_sample(self, resolution: math.Shape, size, shape: math.Shape = None): shape = (self._shape if shape is None else shape) & resolution for dim in channel(self._shape): if dim.name == 'vector' and dim.item_names[0] is None: warnings.warn(f"Please provide item names for Noise dim {dim} using {dim}='x,y,z'", FutureWarning) shape &= channel(**{dim.name: resolution.names}) rndj = math.to_complex(random_normal(shape)) + 1j * math.to_complex(random_normal(shape)) # Note: there is no complex32 # --- Compute 1 / k^2 --- k_vec = math.fftfreq(resolution, size) * resolution * math.tensor(self.scale) # in physical units k2 = math.vec_squared(k_vec) lowest_frequency = 0.1 weight_mask = math.to_float(k2 > lowest_frequency) inv_k2 = math.divide_no_nan(1, k2) # --- Compute result --- fft = rndj * inv_k2 ** self.smoothness * weight_mask array = math.real(math.ifft(fft)) array /= math.std(array, dim=array.shape.non_batch) array -= math.mean(array, dim=array.shape.non_batch) array = math.to_float(array) return array def __repr__(self): return f"{self._shape}, scale={self.scale}, smoothness={self.smoothness}"
Ancestors
- phi.field._field.FieldInitializer
Methods
def grid_sample(self, resolution: phiml.math._shape.Shape, size, shape: phiml.math._shape.Shape = None)
class Scene
-
Provides methods for reading and writing simulation data.
See the format documentation at https://tum-pbs.github.io/PhiFlow/Scene_Format_Specification.html .
All data of a
Scene
is located inside a single directory with namesim_xxxxxx
wherexxxxxx
is theid
. The data of the scene is organized into NumPy files by name and frame.To create a new scene, use
Scene.create()
. To reference an existing scene, useScene.at()
. To list all scenes within a directory, useScene.list()
.Expand source code
class Scene: """ Provides methods for reading and writing simulation data. See the format documentation at https://tum-pbs.github.io/PhiFlow/Scene_Format_Specification.html . All data of a `Scene` is located inside a single directory with name `sim_xxxxxx` where `xxxxxx` is the `id`. The data of the scene is organized into NumPy files by *name* and *frame*. To create a new scene, use `Scene.create()`. To reference an existing scene, use `Scene.at()`. To list all scenes within a directory, use `Scene.list()`. """ def __init__(self, paths: Union[str, math.Tensor]): self._paths = math.wrap(paths) self._properties: Union[dict, None] = None def __getitem__(self, item): return Scene(self._paths[item]) def __getattr__(self, name: str) -> BoundDim: return BoundDim(self, name) def __variable_attrs__(self) -> Tuple[str, ...]: return 'paths', def __with_attrs__(self, **attrs): if 'paths' in attrs: return Scene(attrs['paths']) else: return Scene(self._paths) @property def shape(self): return self._paths.shape @property def is_batch(self): return self._paths.rank > 0 @property def path(self) -> str: """ Relative path of the scene directory. This property only exists for single scenes, not scene batches. """ assert not self.is_batch, "Scene.path is not defined for scene batches." return self._paths.native() @property def paths(self) -> math.Tensor: return self._paths @staticmethod def stack(*scenes: 'Scene', dim: Shape = batch('batch')) -> 'Scene': return Scene(math.stack([s._paths for s in scenes], dim)) @staticmethod def create(parent_directory: str, shape: math.Shape = math.EMPTY_SHAPE, name='sim', copy_calling_script=True, **dimensions) -> 'Scene': """ Creates a new `Scene` or a batch of new scenes inside `parent_directory`. See Also: `Scene.at()`, `Scene.list()`. Args: parent_directory: Directory to hold the new `Scene`. If it doesn't exist, it will be created. shape: Determines number of scenes to create. Multiple scenes will be represented by a `Scene` with `is_batch=True`. name: Name of the directory (excluding index). Default is `'sim'`. copy_calling_script: Whether to copy the Python file that invoked this method into the `src` folder of all created scenes. See `Scene.copy_calling_script()`. dimensions: Additional batch dimensions Returns: Single `Scene` object representing the new scene(s). """ shape = shape & math.batch(**dimensions) parent_directory = expanduser(parent_directory) abs_dir = abspath(parent_directory) if not isdir(abs_dir): os.makedirs(abs_dir) next_id = 0 else: indices = [int(f[len(name)+1:]) for f in os.listdir(abs_dir) if f.startswith(f"{name}_")] next_id = max([-1] + indices) + 1 ids = unpack_dim(wrap(tuple(range(next_id, next_id + shape.volume))), 'vector', shape) paths = math.map(lambda id_: join(parent_directory, f"{name}_{id_:06d}"), ids) scene = Scene(paths) scene.mkdir() if copy_calling_script: try: scene.copy_calling_script() except IOError as err: warnings.warn(f"Failed to copy calling script to scene during Scene.create(): {err}", RuntimeWarning) return scene @staticmethod def list(parent_directory: str, name='sim', include_other: bool = False, dim: Union[Shape, None] = None) -> Union['Scene', tuple]: """ Lists all scenes inside the given directory. See Also: `Scene.at()`, `Scene.create()`. Args: parent_directory: Directory that contains scene folders. name: Name of the directory (excluding index). Default is `'sim'`. include_other: Whether folders that do not match the scene format should also be treated as scenes. dim: Stack dimension. If None, returns tuple of `Scene` objects. Otherwise, returns a scene batch with this dimension. Returns: `tuple` of scenes. """ parent_directory = expanduser(parent_directory) abs_dir = abspath(parent_directory) if not isdir(abs_dir): return () names = [sim for sim in os.listdir(abs_dir) if sim.startswith(f"{name}_") or (include_other and isdir(join(abs_dir, sim)))] names = list(sorted(names)) if dim is None: return tuple(Scene(join(parent_directory, n)) for n in names) else: paths = math.wrap([join(parent_directory, n) for n in names], dim) return Scene(paths) @staticmethod def at(directory: Union[str, tuple, typing_list, math.Tensor, 'Scene'], id: Union[int, math.Tensor, None] = None) -> 'Scene': """ Creates a `Scene` for an existing directory. See Also: `Scene.create()`, `Scene.list()`. Args: directory: Either directory containing scene folder if `id` is given, or scene path if `id=None`. id: (Optional) Scene `id`, will be determined from `directory` if not specified. Returns: `Scene` object for existing scene. """ if isinstance(directory, Scene): assert id is None, f"Got id={id} but directory is already a Scene." return directory if isinstance(directory, (tuple, list)): directory = math.wrap(directory, batch('scenes')) directory = math.wrap(math.map(lambda d: expanduser(d), directory)) if isinstance(id, int) and id < 0: assert directory.shape.volume == 1 scenes = Scene.list(directory.native()) assert len(scenes) >= -id, f"Failed to get scene {id} at {directory}. {len(scenes)} scenes available in that directory." return scenes[id] if id is None: paths = wrap(directory) else: id = math.wrap(id) paths = wrap(math.map(lambda d, i: join(d, f"sim_{i:06d}"), directory, id)) # test all exist for path in math.flatten(wrap(paths), flatten_batch=True): if not isdir(path): raise IOError(f"There is no scene at '{path}'") return Scene(paths) def subpath(self, name: str, create=False, create_parent=False) -> Union[str, tuple]: """ Resolves the relative path `name` with this `Scene` as the root folder. Args: name: Relative path with this `Scene` as the root folder. create: Whether to create a directory of that name. create_parent: Whether to create the parent directory. Returns: Relative path including the path to this `Scene`. In batch mode, returns a `tuple`, else a `str`. """ def single_subpath(path): path = join(path, name) if create_parent and not isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) if create and not isdir(path): os.mkdir(path) return path result = math.map(single_subpath, self._paths) return result def _init_properties(self): if self._properties is not None: return def read_json(path: str) -> dict: json_file = join(path, "description.json") if isfile(json_file): with open(json_file) as stream: props = json.load(stream) if '__tensors__' in props: for key in props['__tensors__']: props[key] = math.from_dict(props[key]) return props else: return {} if self._paths.shape.volume == 1: self._properties = read_json(self._paths.native()) else: self._properties = {} dicts = [read_json(p) for p in self._paths] keys = set(sum([tuple(d.keys()) for d in dicts], ())) for key in keys: assert all(key in d for d in dicts), f"Failed to create batched Scene because property '{key}' is present in some scenes but not all." if all([math.all(d[key] == dicts[0][key]) for d in dicts]): self._properties[key] = dicts[0][key] else: self._properties[key] = stack([d[key] for d in dicts], self._paths.shape) if '__tensors__' in self._properties: del self._properties['__tensors__'] def exist_properties(self): """ Checks whether the file `description.json` exists or has existed. """ if self._properties is not None: return True # must have been written or read else: json_file = join(next(iter(math.flatten(self._paths, flatten_batch=True))), "description.json") return isfile(json_file) def exists_config(self): """ Tests if the configuration file *description.json* exists. In batch mode, tests if any configuration exists. """ if isinstance(self.path, str): return isfile(join(self.path, "description.json")) else: return any(isfile(join(p, "description.json")) for p in self.path) @property def properties(self): self._init_properties() return self._properties @properties.setter def properties(self, dict): self._properties = dict with open(join(self.path, "description.json"), "w") as out: json.dump(self._properties, out, indent=2) def put_property(self, key, value): """ See `Scene.put_properties()`. """ self._init_properties() self._properties[key] = value self._write_properties() def put_properties(self, update: dict = None, **kw_updates): """ Updates the properties dictionary and stores it in `description.json` of all scene folders. Args: update: new values, must be JSON serializable. kw_updates: additional update as keyword arguments. This overrides `update`. """ self._init_properties() if update: self._properties.update(update) self._properties.update(kw_updates) for key, value in self._properties.items(): if isinstance(value, (np.int64, np.int32)): value = int(value) elif isinstance(value, (np.float16, np.float32, np.float64, np.float16)) or (hasattr(np, 'float128') and isinstance(value, np.float128)): value = float(value) self._properties[key] = value self._write_properties() def _get_properties(self, index: dict): result = dict(self._properties) tensor_names = [] for key, value in self._properties.items(): if isinstance(value, math.Tensor): value = value[index] if value.rank == 0: value = value.dtype.kind(value) else: value = math.to_dict(value) tensor_names.append(key) result[key] = value if tensor_names: result['__tensors__'] = tuple(tensor_names) return result def _write_properties(self): for instance in self.paths.shape.meshgrid(): path = self.paths[instance].native() instance_properties = self._get_properties(instance) with open(join(path, "description.json"), "w") as out: json.dump(instance_properties, out, indent=2) def write(self, data: dict = None, frame=0, **kw_data): """ Writes fields to this scene. One NumPy file will be created for each `phi.field.Field` See Also: `Scene.read()`. Args: data: `dict` mapping field names to `Field` objects that can be written using `phi.field.write()`. kw_data: Additional data, overrides elements in `data`. frame: Frame number. """ data = dict(data) if data else {} data.update(kw_data) for name, field in data.items(): self.write_field(field, name, frame) def write_field(self, field: Field, name: str, frame: int): """ Write a `Field` to a file. The filenames are created from the provided names and the frame index in accordance with the scene format specification at https://tum-pbs.github.io/PhiFlow/Scene_Format_Specification.html . Args: field: single field or structure of Fields to save. name: Base file name. frame: Frame number as `int`, typically time step index. """ if not isinstance(field, Field): raise ValueError(f"Only Field instances can be saved but got {field}") name = _slugify_filename(name) files = wrap(math.map(lambda dir_: _filename(dir_, name, frame), self._paths)) write(field, files) def read_field(self, name: str, frame: int, convert_to_backend=True) -> Field: """ Reads a single `Field` from files contained in this `Scene` (batch). Args: name: Base file name. frame: Frame number as `int`, typically time step index. convert_to_backend: Whether to convert the read data to the data format of the default backend, e.g. TensorFlow tensors. Returns: `Field` """ name = _slugify_filename(name) files = math.map(lambda dir_: _filename(dir_, name, frame), self._paths) return read(files, convert_to_backend=convert_to_backend) read_array = read_field def read(self, *names: str, frame=0, convert_to_backend=True): """ Reads one or multiple fields from disc. See Also: `Scene.write()`. Args: names: Single field name or sequence of field names. frame: Frame number. convert_to_backend: Whether to convert the read data to the data format of the default backend, e.g. TensorFlow tensors. Returns: Single `phi.field.Field` or sequence of fields, depending on the type of `names`. """ if len(names) == 1 and isinstance(names[0], (tuple, list)): names = names[0] result = [self.read_array(name, frame, convert_to_backend) for name in names] return result[0] if len(names) == 1 else result @property def fieldnames(self) -> tuple: """ Determines all field names present in this `Scene`, independent of frame. """ return get_fieldnames(self.path) @property def frames(self): """ Determines all frame numbers present in this `Scene`, independent of field names. See `Scene.complete_frames`. """ return get_frames(self.path, mode=set.union) @property def complete_frames(self): """ Determines all frame number for which all existing fields are available. If there are multiple fields stored within this scene, a frame is considered complete only if an entry exists for all fields. See Also: `Scene.frames` """ return get_frames(self.path, mode=set.intersection) def __repr__(self): return f"{self.paths:no-dtype}" def __eq__(self, other): return isinstance(other, Scene) and (other._paths == self._paths).all def copy_calling_script(self, full_trace=False, include_context_information=True): """ Copies the Python file that called this method into the `src` folder of this `Scene`. In batch mode, the script is copied to all scenes. Args: full_trace: Whether to include scripts that indirectly called this method. include_context_information: If True, writes the phiflow version and `sys.argv` into `context.json`. """ script_paths = [frame.filename for frame in inspect.stack()] script_paths = list(filter(lambda path: not _is_phi_file(path), script_paths)) script_paths = set(script_paths) if full_trace else [script_paths[0]] self.subpath('src', create=True) for script_path in script_paths: if script_path.endswith('.py'): self.copy_src(script_path, only_external=False) elif 'ipython' in script_path: from IPython import get_ipython cells = get_ipython().user_ns['In'] blocks = [f"#%% In[{i}]\n{cell}" for i, cell in enumerate(cells)] text = "\n\n".join(blocks) self.copy_src_text('ipython.py', text) if include_context_information: for path in math.flatten(self._paths, flatten_batch=True): with open(join(path, 'src', 'context.json'), 'w') as context_file: json.dump({ 'phi_version': phi_version, 'argv': sys.argv }, context_file) def copy_src(self, script_path, only_external=True): for path in math.flatten(self._paths, flatten_batch=True): if not only_external or not _is_phi_file(script_path): shutil.copy(script_path, join(path, 'src', basename(script_path))) def copy_src_text(self, filename, text): for path in math.flatten(self._paths, flatten_batch=True): target = join(path, 'src', filename) with open(target, "w") as file: file.writelines(text) def mkdir(self): for path in math.flatten(self._paths, flatten_batch=True): isdir(path) or os.mkdir(path) def remove(self): """ Deletes the scene directory and all contained files. """ for p in math.flatten(self._paths, flatten_batch=True): p = abspath(p) if isdir(p): shutil.rmtree(p) def rename(self, name: str): """ Deletes the scene directory and all contained files. """ for p in math.flatten(self._paths, flatten_batch=True): p = abspath(p) if isdir(p): new_path = os.path.join(os.path.dirname(p), name) print(f"Renaming {p} to {new_path}") shutil.move(p, new_path)
Static methods
def at(directory: Union[str, tuple, list, phiml.math._tensors.Tensor, ForwardRef('Scene')], id: Union[int, phiml.math._tensors.Tensor, ForwardRef(None)] = None) ‑> phi.field._scene.Scene
-
Creates a
Scene
for an existing directory.See Also:
Scene.create()
,Scene.list()
.Args
directory
- Either directory containing scene folder if
id
is given, or scene path ifid=None
. id
- (Optional) Scene
id
, will be determined fromdirectory
if not specified.
Returns
Scene
object for existing scene. def create(parent_directory: str, shape: phiml.math._shape.Shape = (), name='sim', copy_calling_script=True, **dimensions) ‑> phi.field._scene.Scene
-
Creates a new
Scene
or a batch of new scenes insideparent_directory
.See Also:
Scene.at()
,Scene.list()
.Args
parent_directory
- Directory to hold the new
Scene
. If it doesn't exist, it will be created. shape
- Determines number of scenes to create. Multiple scenes will be represented by a
Scene
withis_batch=True
. name
- Name of the directory (excluding index). Default is
'sim'
. copy_calling_script
- Whether to copy the Python file that invoked this method into the
src
folder of all created scenes. SeeScene.copy_calling_script()
. dimensions
- Additional batch dimensions
Returns
Single
Scene
object representing the new scene(s). def list(parent_directory: str, name='sim', include_other: bool = False, dim: Optional[phiml.math._shape.Shape] = None) ‑> Union[phi.field._scene.Scene, tuple]
-
Lists all scenes inside the given directory.
See Also:
Scene.at()
,Scene.create()
.Args
parent_directory
- Directory that contains scene folders.
name
- Name of the directory (excluding index). Default is
'sim'
. include_other
- Whether folders that do not match the scene format should also be treated as scenes.
dim
- Stack dimension. If None, returns tuple of
Scene
objects. Otherwise, returns a scene batch with this dimension.
Returns
tuple
of scenes. def stack(*scenes: Scene, dim: phiml.math._shape.Shape = (batchᵇ=None)) ‑> phi.field._scene.Scene
Instance variables
prop complete_frames
-
Determines all frame number for which all existing fields are available. If there are multiple fields stored within this scene, a frame is considered complete only if an entry exists for all fields.
See Also:
Scene.frames
Expand source code
@property def complete_frames(self): """ Determines all frame number for which all existing fields are available. If there are multiple fields stored within this scene, a frame is considered complete only if an entry exists for all fields. See Also: `Scene.frames` """ return get_frames(self.path, mode=set.intersection)
prop fieldnames : tuple
-
Determines all field names present in this
Scene
, independent of frame.Expand source code
@property def fieldnames(self) -> tuple: """ Determines all field names present in this `Scene`, independent of frame. """ return get_fieldnames(self.path)
prop frames
-
Determines all frame numbers present in this
Scene
, independent of field names. SeeScene.complete_frames
.Expand source code
@property def frames(self): """ Determines all frame numbers present in this `Scene`, independent of field names. See `Scene.complete_frames`. """ return get_frames(self.path, mode=set.union)
prop is_batch
-
Expand source code
@property def is_batch(self): return self._paths.rank > 0
prop path : str
-
Relative path of the scene directory. This property only exists for single scenes, not scene batches.
Expand source code
@property def path(self) -> str: """ Relative path of the scene directory. This property only exists for single scenes, not scene batches. """ assert not self.is_batch, "Scene.path is not defined for scene batches." return self._paths.native()
prop paths : phiml.math._tensors.Tensor
-
Expand source code
@property def paths(self) -> math.Tensor: return self._paths
prop properties
-
Expand source code
@property def properties(self): self._init_properties() return self._properties
prop shape
-
Expand source code
@property def shape(self): return self._paths.shape
Methods
def copy_calling_script(self, full_trace=False, include_context_information=True)
-
Copies the Python file that called this method into the
src
folder of thisScene
.In batch mode, the script is copied to all scenes.
Args
full_trace
- Whether to include scripts that indirectly called this method.
include_context_information
- If True, writes the phiflow version and
sys.argv
intocontext.json
.
def copy_src(self, script_path, only_external=True)
def copy_src_text(self, filename, text)
def exist_properties(self)
-
Checks whether the file
description.json
exists or has existed. def exists_config(self)
-
Tests if the configuration file description.json exists. In batch mode, tests if any configuration exists.
def mkdir(self)
def put_properties(self, update: dict = None, **kw_updates)
-
Updates the properties dictionary and stores it in
description.json
of all scene folders.Args
update
- new values, must be JSON serializable.
kw_updates
- additional update as keyword arguments. This overrides
update
.
def put_property(self, key, value)
def read(self, *names: str, frame=0, convert_to_backend=True)
-
Reads one or multiple fields from disc.
See Also:
Scene.write()
.Args
names
- Single field name or sequence of field names.
frame
- Frame number.
convert_to_backend
- Whether to convert the read data to the data format of the default backend, e.g. TensorFlow tensors.
Returns
Single
Field
or sequence of fields, depending on the type ofnames
. def read_array(self, name: str, frame: int, convert_to_backend=True) ‑> phi.field._field.Field
def read_field(self, name: str, frame: int, convert_to_backend=True) ‑> phi.field._field.Field
def remove(self)
-
Deletes the scene directory and all contained files.
def rename(self, name: str)
-
Deletes the scene directory and all contained files.
def subpath(self, name: str, create=False, create_parent=False) ‑> Union[str, tuple]
-
Resolves the relative path
name
with thisScene
as the root folder.Args
name
- Relative path with this
Scene
as the root folder. create
- Whether to create a directory of that name.
create_parent
- Whether to create the parent directory.
Returns
Relative path including the path to this
Scene
. In batch mode, returns atuple
, else astr
. def write(self, data: dict = None, frame=0, **kw_data)
-
Writes fields to this scene. One NumPy file will be created for each
Field
See Also:
Scene.read()
.Args
def write_field(self, field: phi.field._field.Field, name: str, frame: int)
-
Write a
Field
to a file. The filenames are created from the provided names and the frame index in accordance with the scene format specification at https://tum-pbs.github.io/PhiFlow/Scene_Format_Specification.html .Args
field
- single field or structure of Fields to save.
name
- Base file name.
frame
- Frame number as
int
, typically time step index.
class GeometryMask (geometry: phi.geom._geom.Geometry, balance: Union[phiml.math._tensors.Tensor, float] = 0.5)
-
Deprecated since version 1.3. Use
mask()
orresample()
instead.Args
elements
- Geometry object specifying the sample points and sizes
values
- values corresponding to elements
extrapolation
- values outside elements
Expand source code
class SoftGeometryMask(HardGeometryMask): """ Deprecated since version 1.3. Use `phi.field.mask()` or `phi.field.resample()` instead. """ def __init__(self, geometry: Geometry, balance: Union[Tensor, float] = 0.5): warnings.warn("HardGeometryMask and SoftGeometryMask are deprecated. Use field.mask or field.resample instead.", DeprecationWarning, stacklevel=2) super().__init__(geometry) self.balance = balance def _sample(self, geometry: Geometry, **kwargs) -> Tensor: return self.geometry.approximate_fraction_inside(geometry, self.balance) def __getitem__(self, item: dict): return SoftGeometryMask(self.geometry[item], self.balance)
Ancestors
- phi.field._mask.HardGeometryMask
- phi.field._field.Field
class SoftGeometryMask (geometry: phi.geom._geom.Geometry, balance: Union[phiml.math._tensors.Tensor, float] = 0.5)
-
Deprecated since version 1.3. Use
mask()
orresample()
instead.Args
elements
- Geometry object specifying the sample points and sizes
values
- values corresponding to elements
extrapolation
- values outside elements
Expand source code
class SoftGeometryMask(HardGeometryMask): """ Deprecated since version 1.3. Use `phi.field.mask()` or `phi.field.resample()` instead. """ def __init__(self, geometry: Geometry, balance: Union[Tensor, float] = 0.5): warnings.warn("HardGeometryMask and SoftGeometryMask are deprecated. Use field.mask or field.resample instead.", DeprecationWarning, stacklevel=2) super().__init__(geometry) self.balance = balance def _sample(self, geometry: Geometry, **kwargs) -> Tensor: return self.geometry.approximate_fraction_inside(geometry, self.balance) def __getitem__(self, item: dict): return SoftGeometryMask(self.geometry[item], self.balance)
Ancestors
- phi.field._mask.HardGeometryMask
- phi.field._field.Field