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.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 with math.NUMPY: k = math.fftfreq(resolution) * resolution / math.tensor( size) * math.tensor(self.scale) # in physical units k = math.vec_squared(k) lowest_frequency = 0.1 weight_mask = math.to_float(k > lowest_frequency) # --- Compute 1/k --- k._native[(0, ) * len(k.shape)] = np.inf inv_k = 1 / k inv_k._native[(0, ) * len(k.shape)] = 0 # --- Compute result --- fft = rndj * inv_k**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 test_multi_dim_tensor_from_numpy(self): v = math.tensor(np.ones([1, 4, 3, 2]), batch('batch'), spatial('x,y'), channel('vector')) self.assertEqual((1, 4, 3, 2), v.shape.sizes) v = math.tensor(np.ones([10, 4, 3, 2]), batch('batch'), spatial('x,y'), channel('vector')) self.assertEqual((10, 4, 3, 2), v.shape.sizes)
def points(self, points: Tensor or Number or tuple or list, values: Tensor or Number = None, radius: Tensor or float or int or None = None, extrapolation: math.Extrapolation = math.extrapolation.ZERO, color: str or Tensor or tuple or list or None = None) -> PointCloud: """ Create a `phi.field.PointCloud` from the given `points`. The created field has no channel dimensions and all points carry the value `1`. Args: points: point locations in physical units values: (optional) values of the particles, defaults to 1. radius: (optional) size of the particles extrapolation: (optional) extrapolation to use, defaults to extrapolation.ZERO color: (optional) color used when plotting the points Returns: `phi.field.PointCloud` object """ extrapolation = extrapolation if isinstance(extrapolation, math.Extrapolation) else self.boundaries[extrapolation] if radius is None: radius = math.mean(self.bounds.size) * 0.005 # --- Parse points: tuple / list --- if isinstance(points, (tuple, list)): if len(points) == 0: # no points points = math.zeros(instance(points=0), channel(vector=1)) elif isinstance(points[0], Number): # single point points = math.tensor([points], instance('points'), channel('vector')) else: points = math.tensor(points, instance('points'), channel('vector')) elements = Sphere(points, radius) if values is None: values = math.tensor(1.) return PointCloud(elements, values, extrapolation, add_overlapping=False, bounds=self.bounds, color=color)
def read_single_field(file: str, convert_to_backend=True) -> SampledField: stored = np.load(file, allow_pickle=True) ftype = stored['field_type'] implemented_types = ('CenteredGrid', 'StaggeredGrid') if ftype in implemented_types: data = stored['data'] dim_item_names = stored.get('dim_item_names', (None, ) * len(data.shape)) data = NativeTensor( data, math.Shape(data.shape, tuple(stored['dim_names']), tuple(stored['dim_types']), tuple(dim_item_names))) if convert_to_backend: data = math.tensor(data, convert=convert_to_backend) bounds_item_names = stored.get('bounds_item_names', (None, ) * len(stored['lower'] + stored['upper'])) lower = math.wrap( stored['lower'], math.channel(vector=tuple(bounds_item_names)) ) if stored['lower'].ndim > 0 else math.wrap(stored['lower']) upper = math.wrap(stored['upper'], math.channel(vector=tuple(bounds_item_names))) extrapolation = math.extrapolation.from_dict( stored['extrapolation'][()]) if ftype == 'CenteredGrid': return CenteredGrid(data, bounds=geom.Box(lower, upper), extrapolation=extrapolation) elif ftype == 'StaggeredGrid': data_ = unstack_staggered_tensor(data, extrapolation) return StaggeredGrid(data_, bounds=geom.Box(lower, upper), extrapolation=extrapolation) raise NotImplementedError(f"{ftype} not implemented ({implemented_types})")
def test_np_speed_sum(self): print() np1, np2 = rnpv(64), rnpv(256) t1 = math.tensor(np1, batch('batch'), spatial('x, y'), channel('vector')) t2 = math.tensor(np2, batch('batch'), spatial('x, y'), channel('vector')) _assert_equally_fast(lambda: np.sum(np1), lambda: math.sum(t1, dim=t1.shape), n=10000) _assert_equally_fast(lambda: np.sum(np2), lambda: math.sum(t2, dim=t1.shape), n=10000)
def test_stacked_get(self): t0 = math.ones(batch(batch=10) & spatial(x=4, y=3) & channel(vector=2)) tensors = t0.unstack('vector') stacked = math.stack(tensors, channel('channel')) self.assertEqual(tensors, stacked.channel.unstack()) assert tensors[0] is stacked.channel[0] assert tensors[1] is stacked.channel[1:2].channel.unstack()[0] self.assertEqual(4, len(stacked.x.unstack()))
def stagger(field: CenteredGrid, face_function: Callable, extrapolation: math.extrapolation.Extrapolation, type: type = StaggeredGrid): """ 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 as `field`. Args: field: centered grid face_function: function mapping (value1: Tensor, value2: Tensor) -> center_value: Tensor extrapolation: extrapolation mode of the returned grid. Has no effect on the values. type: one of (StaggeredGrid, CenteredGrid) field: CenteredGrid: face_function: Callable: extrapolation: math.extrapolation.Extrapolation: type: type: (Default value = StaggeredGrid) Returns: grid of type matching the `type` argument """ all_lower = [] all_upper = [] if type == StaggeredGrid: for dim in field.shape.spatial.names: lo_valid, up_valid = extrapolation.valid_outer_faces(dim) width_lower = {dim: (int(lo_valid), int(up_valid) - 1)} width_upper = { dim: (int(lo_valid or up_valid) - 1, int(lo_valid and up_valid)) } all_lower.append( math.pad(field.values, width_lower, field.extrapolation)) all_upper.append( math.pad(field.values, width_upper, field.extrapolation)) all_upper = math.stack(all_upper, channel('vector')) all_lower = math.stack(all_lower, channel('vector')) values = face_function(all_lower, all_upper) result = StaggeredGrid(values, bounds=field.bounds, extrapolation=extrapolation) assert result.shape.spatial == field.shape.spatial return result elif type == CenteredGrid: left, right = math.shift(field.values, (-1, 1), padding=field.extrapolation, stack_dim=channel('vector')) values = face_function(left, right) return CenteredGrid(values, bounds=field.bounds, extrapolation=extrapolation) else: raise ValueError(type)
def test_merge_shapes_check_item_names(self): s1 = channel(vector='x,y,z') s2 = channel(vector='r,g,b') try: math.merge_shapes(s1, s2) self.fail('Merging incompatible shapes did not raise an error!') except math.IncompatibleShapes: pass math.merge_shapes(s1, s1)
def test_subshapes(self): s = batch(batch=10) & spatial(x=4, y=3) & channel(vector=2) & instance(points=1) self.assertEqual(batch(batch=10), s.batch) self.assertEqual(spatial(x=4, y=3), s.spatial) self.assertEqual(channel(vector=2), s.channel) self.assertEqual(instance(points=1), s.instance) self.assertEqual(batch(batch=10), batch(s)) self.assertEqual(spatial(x=4, y=3), spatial(s)) self.assertEqual(channel(vector=2), channel(s)) self.assertEqual(instance(points=1), instance(s))
def test_np_speed_op2(self): print() np1, np2 = rnpv(64), rnpv(64) t1 = math.tensor(np1, batch('batch'), spatial('x, y'), channel('vector')) t2 = math.tensor(np2, batch('batch'), spatial('x, y'), channel('vector')) _assert_equally_fast(lambda: np1 + np2, lambda: t1 + t2, n=10000) np1, np2 = rnpv(256), rnpv(256) t1 = math.tensor(np1, batch('batch'), spatial('x, y'), channel('vector')) t2 = math.tensor(np2, batch('batch'), spatial('x, y'), channel('vector')) _assert_equally_fast(lambda: np1 + np2, lambda: t1 + t2, n=1000)
def test_tensor_as_field(self): t = math.random_normal(spatial(x=4, y=3), channel(vector='x,y')) grid = field.tensor_as_field(t) self.assertIsInstance(grid, CenteredGrid) math.assert_close(grid.dx, 1) math.assert_close(grid.points.x[0].y[0], 0) t = math.random_normal(instance(points=5), channel(vector='x,y')) points = field.tensor_as_field(t) self.assertTrue((points.elements.bounding_radius() > 0).all) self.assertIsInstance(points, PointCloud)
def test_convert_point_cloud(self): loc = math.random_uniform(instance(points=4), channel(vector=2)) val = math.random_normal(instance(points=4), channel(vector=2)) points = PointCloud(Sphere(loc, radius=1), val) for backend in BACKENDS: converted = field.convert(points, backend) self.assertEqual(converted.values.default_backend, backend) self.assertEqual(converted.elements.center.default_backend, backend) self.assertEqual(converted.elements.radius.default_backend, backend)
def __mul__(self, other): if not isinstance(other, Box): return NotImplemented lower = self._lower.vector.unstack( self.spatial_rank) + other._lower.vector.unstack(self.spatial_rank) upper = self._upper.vector.unstack( self.spatial_rank) + other._upper.vector.unstack(self.spatial_rank) names = self._upper.vector.item_names + other._upper.vector.item_names lower = math.stack(lower, math.channel(vector=names)) upper = math.stack(upper, math.channel(vector=names)) return Box(lower, upper)
def test_collapsed_op2(self): # Collapsed + Collapsed a = math.zeros(channel(vector=4)) b = math.ones(batch(batch=3)) c = a + b self.assertIsInstance(c, CollapsedTensor) self.assertEqual(c.shape.volume, 12) self.assertEqual(c._inner.shape.volume, 1) # Collapsed + Native n = math.ones(channel(vector=3)) + (0, 1, 2) math.assert_close(n, (1, 2, 3))
def test_boolean_mask_batched(self): for backend in BACKENDS: with backend: x = math.expand(math.range(spatial('x'), 4), batch(batch=2)) * math.tensor([1, -1]) mask = math.tensor([[True, False, True, False], [False, True, False, False]], batch('batch'), spatial('x')) selected = math.boolean_mask(x, 'x', mask) expected_0 = math.tensor([(0, -0), (2, -2)], spatial('x'), channel('vector')) expected_1 = math.tensor([(1, -1)], spatial('x'), channel('vector')) math.assert_close(selected.batch[0], expected_0, msg=backend.name) math.assert_close(selected.batch[1], expected_1, msg=backend.name) math.assert_close(selected, x.x[mask], msg=backend.name)
def test_tensor_from_tuple_of_tensor_like(self): native = ([1, 2, 3], math.zeros(channel(vector=3))) for backend in BACKENDS: with backend: tens = wrap(native, batch(stack=2), channel(vector=3)) self.assertEqual(math.NUMPY, math.choose_backend(tens)) self.assertEqual( batch(stack=2) & channel(vector=3), tens.shape) tens = tensor(native, batch(stack=2), channel(vector=3)) self.assertEqual(backend, math.choose_backend(tens)) self.assertEqual( batch(stack=2) & channel(vector=3), tens.shape)
def test_stacked_native(self): t0 = math.ones(batch(batch=10) & spatial(x=4, y=3) & channel(vector=2)) tensors = t0.unstack('vector') stacked = math.stack(tensors, channel('vector2')) math.assert_close(stacked, t0) self.assertEqual((10, 4, 3, 2), stacked.native(stacked.shape).shape) self.assertEqual( (4, 3, 2, 10), stacked.native(order=('x', 'y', 'vector2', 'batch')).shape) self.assertEqual( (2, 10, 3, 4), stacked.native(order=('vector2', 'batch', 'y', 'x')).shape ) # this should re-stack since only the stacked dimension position is different
def test_op2_incompatible_item_names(self): t1 = math.random_normal(channel(vector='x,y,z')) t2 = math.random_normal(channel(vector='r,g,b')) self.assertEqual(('r', 'g', 'b'), t2.vector.item_names) try: t1 + t2 self.fail("Tensors with incompatible item names cannot be added") except math.IncompatibleShapes: pass t1 + t1 t2_ = t2 + math.random_normal(channel(vector=3)) self.assertEqual(('r', 'g', 'b'), t2_.vector.item_names) t2_ = math.random_normal(channel(vector=3)) + t2 self.assertEqual(('r', 'g', 'b'), t2_.vector.item_names)
def test_expand_copy_item_names(self): a = math.zeros(channel(vector=2)) try: math.expand(a, channel(vector=3)) self.fail() except IncompatibleShapes: pass b = math.expand(a, channel(vector='x,y')) self.assertEqual(('x', 'y'), b.vector.item_names) try: math.expand(b, channel(vector='a,b')) self.fail() except IncompatibleShapes: pass
def tensor_as_field(t: Tensor): """ Interpret a `Tensor` as a `CenteredGrid` or `PointCloud` depending on its dimensions. Unlike the `CenteredGrid` constructor, this function will have the values sampled at integer points for each spatial dimension. Args: t: `Tensor` with either `spatial` or `instance` dimensions. Returns: `CenteredGrid` or `PointCloud` """ if spatial(t): assert not instance( t ), f"Cannot interpret tensor as Field because it has both spatial and instance dimensions: {t.shape}" bounds = Box(-0.5, math.wrap(spatial(t), channel('vector')) - 0.5) return CenteredGrid(t, 0, bounds=bounds) if instance(t): assert not spatial( t ), f"Cannot interpret tensor as Field because it has both spatial and instance dimensions: {t.shape}" assert 'vector' in t.shape, f"Cannot interpret tensor as PointCloud because it has not vector dimension." point_count = instance(t).volume bounds = data_bounds(t) radius = math.vec_length( bounds.size) / (1 + point_count**(1 / t.vector.size)) return PointCloud(Sphere(t, radius=radius))
def test_meshgrid_names(self): shape = spatial(x=2) & channel(vector='x') indices = list(shape.meshgrid(names=True)) self.assertEqual([ dict(x=0, vector='x'), dict(x=1, vector='x'), ], indices)
def test_dimension_types(self): v = math.ones(batch(batch=10) & spatial(x=4, y=3) & channel(vector=2)) self.assertEqual(v.x.index, 1) self.assertEqual(v.x.name, 'x') self.assertEqual(('batch', 'spatial', 'spatial', 'channel'), v.shape.types) b = v.x.as_batch() self.assertEqual(('batch', 'batch', 'spatial', 'channel'), b.shape.types)
def vector_grid(self, value: Field or Tensor or Number or Geometry or callable = 0., type: type = CenteredGrid, extrapolation: math.Extrapolation or str = 'vector') -> CenteredGrid or StaggeredGrid: """ Creates a vector grid matching the resolution and bounds of the domain. The grid is created from the given `value` which must be one of the following: * Number (int, float, complex or zero-dimensional tensor): all grid values will be equal to `value`. This has a near-zero memory footprint. * Field: the given value is resampled to the grid cells of this Domain. * Tensor with spatial dimensions matcing the domain resolution: grid values will equal `value`. * Geometry: grid values are determined from the volume overlap between grid cells and geometry. Non-overlapping = 0, fully enclosed grid cell = 1. * function(location: Tensor) returning one of the above. The returned grid will have a vector dimension with size equal to the rank of the domain. Args: value: constant, Field, Tensor or function specifying the grid values type: class of Grid to create, must be either CenteredGrid or StaggeredGrid extrapolation: (optional) grid extrapolation, defaults to Domain.boundaries['vector'] Returns: Grid of specified type """ extrapolation = extrapolation if isinstance(extrapolation, math.Extrapolation) else self.boundaries[extrapolation] result = type(value, resolution=self.resolution, bounds=self.bounds, extrapolation=extrapolation) if result.shape.channel_rank == 0: result = result.with_values(math.expand(result.values, channel(vector=self.rank))) else: assert result.shape.get_size('vector') == self.rank return result
def test_indexing(self): s = batch(batch=10) & spatial(x=4, y=3) & channel(vector=2) self.assertEqual(batch(batch=10), s[0:1]) self.assertEqual(batch(batch=10), s[[0]]) self.assertEqual(spatial(x=4, y=3), s[1:3]) self.assertEqual(spatial(x=4), s['x']) self.assertEqual(spatial(x=4, y=3), s['x, y'])
def test_stacked_shapes(self): t0 = math.ones(batch(batch=10) & spatial(x=4, y=3) & channel(vector=2)) for dim in t0.shape.names: tensors = t0.unstack(dim) stacked = math.stack(tensors, t0.shape[dim].with_sizes([None])) self.assertEqual(set(t0.shape.names), set(stacked.shape.names)) self.assertEqual(t0.shape.volume, stacked.shape.volume)
def test_iterate_layout(self): a = [dict(a=1), dict(b=2)] t = math.layout(a, channel('outer,dict')) total = [] for d in t: total.append(d) self.assertEqual(total, [1, 2])
def test_layout_dict_conflict(self): a = [dict(a=1), dict(b=2)] t = math.layout(a, channel('outer,dict')) self.assertEqual(None, t.shape.get_item_names('dict')) self.assertEqual(a, t.native()) self.assertEqual([1, 2], t.dict[0].native()) self.assertEqual(2, t.dict[0].outer[1].native())
def __init__(self, center: Tensor = None, radius: float or Tensor = None, **center_: float or Tensor): """ Args: center: Sphere center as `Tensor` with `vector` dimension. The spatial dimension order should be specified in the `vector` dimension via item names. radius: Sphere radius as `float` or `Tensor` **center_: Specifies center when the `center` argument is not given. Center position by dimension, e.g. `x=0.5, y=0.2`. """ if center is not None: if not isinstance(center, Tensor): warnings.warn( f"constructor Sphere({type(center)}) is deprecated. Use Sphere(Tensor) or Sphere(**center_) instead.", DeprecationWarning) self._center = wrap(center) else: self._center = center assert 'vector' in self._center.shape, f"Sphere.center must have a 'vector' dimension. Use the syntax x=0.5." else: self._center = math.wrap( tuple(center_.values()), math.channel(vector=tuple(center_.keys()))) # self._center = wrap(math.spatial(**center_), math.channel('vector')) assert radius is not None, "radius must be specified." self._radius = wrap(radius)
def test_slice_centered_grid(self): g = CenteredGrid(Noise(batch(batch=10), channel(vector=2)), x=10, y=20) s1 = g[{'vector': 0, 'batch': 1, 'x': 1}] s2 = g.vector[0].batch[1].x[1] self.assertIsInstance(s1, CenteredGrid) self.assertEqual(s1.bounds, Box[1:2, 0:20]) field.assert_close(s1, s2)
def test_flip_item_names(self): t = math.zeros(spatial(x=4, y=3), channel(vector='x,y')) self.assertEqual(('x', 'y'), t.vector.item_names) t_ = t.vector.flip() self.assertEqual(('y', 'x'), t_.vector.item_names) t_ = t.vector[::-1] self.assertEqual(('y', 'x'), t_.vector.item_names)