def grid_sample(self, resolution, size, batch_size=1, channels=None): channels = channels or self.channels or len(size) shape = (batch_size, ) + tuple(resolution) + (channels, ) rndj = math.to_complex( self.math.random_normal(shape)) + 1j * math.to_complex( self.math.random_normal(shape)) # Note: there is no complex32 k = math.fftfreq( resolution) * resolution / size * self.scale # in physical units k = math.sum(k**2, axis=-1, keepdims=True) lowest_frequency = 0.1 weight_mask = 1 / (1 + math.exp( (lowest_frequency - k) * 1e3)) # High pass filter # --- Compute 1/k --- k[(0, ) * len(k.shape)] = np.inf inv_k = 1 / k inv_k[(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, axis=tuple(range(1, math.ndims(array))), keepdims=True) array -= math.mean(array, axis=tuple(range(1, math.ndims(array))), keepdims=True) array = math.to_float(array) return array
def data(self, data): if data is None: return None if isinstance(data, (tuple, list)): data = np.array(data) # numbers or objects if self.content_type in (struct.shape, struct.staticshape): assert math.ndims(data) == 1 else: if math.ndims(data) < 2: data = math.expand_dims(data, 0, number=2 - math.ndims(data)) return data
def __dataop__(self, other, linear_if_scalar, data_operator): if isinstance(other, StaggeredGrid): assert self.compatible( other), 'Fields are not compatible: %s and %s' % (self, other) data = [ data_operator(c1, c2) for c1, c2 in zip(self.data, other.data) ] flags = propagate_flags_operation(self.flags + other.flags, False, self.rank, self.component_count) elif math.ndims(other) > 0 and math.staticshape(other)[-1] > 1: other_components = math.unstack(math.as_tensor(other), axis=-1, keepdims=True) data = [ data_operator(c1, c2) for c1, c2 in zip(self.data, other_components) ] flags = propagate_flags_operation(self.flags, False, self.rank, self.component_count) else: data = [data_operator(c1, other) for c1 in self.data] flags = propagate_flags_operation(self.flags, linear_if_scalar, self.rank, self.component_count) return self.copied_with(data=np.array(data, dtype=np.object), flags=flags)
def sample(value, domain, batch_size=None, name=None): assert isinstance(domain, Domain) if isinstance(value, Field): assert_same_rank( value.rank, domain.rank, 'rank of value (%s) does not match domain (%s)' % (value.rank, domain.rank)) if isinstance(value, CenteredGrid) and value.box == domain.box and np.all( value.resolution == domain.resolution): data = value.data else: point_field = CenteredGrid.getpoints(domain.box, domain.resolution) point_field._batch_size = batch_size data = value.at(point_field).data else: # value is constant if callable(value): x = CenteredGrid.getpoints( domain.box, domain.resolution).copied_with( extrapolation=Material.extrapolation_mode( domain.boundaries), name=name) value = value(x) return value components = math.staticshape( value)[-1] if math.ndims(value) > 0 else 1 data = math.add( math.zeros((batch_size, ) + tuple(domain.resolution) + (components, )), value) return CenteredGrid(data, box=domain.box, extrapolation=Material.extrapolation_mode( domain.boundaries), name=name)
def data(self, data): if data is None: return None if isinstance(data, (tuple, list)): data = np.array(data) # numbers or objects while math.ndims(data) < 2: data = math.expand_dims(data) return data
def grid_sample(self, resolution, size, batch_size=1, dtype=np.float32): shape = (batch_size,) + tuple(resolution) + (self.channels,) rndj = math.randn(shape, dtype) + 1j * math.randn(shape, dtype) k = math.fftfreq(resolution) * resolution / size * self.scale # in physical units k = math.sum(k ** 2, axis=-1, keepdims=True) lowest_frequency = 0.1 weight_mask = 1 / (1 + math.exp((lowest_frequency - k) * 1e3)) # High pass filter # --- Compute 1/k --- k[(0,) * len(k.shape)] = np.inf inv_k = 1 / k inv_k[(0,) * len(k.shape)] = 0 # --- Compute result --- fft = rndj * inv_k ** self.smoothness * weight_mask array = math.real(math.ifft(fft)).astype(dtype) array /= math.std(array, axis=tuple(range(1, math.ndims(array))), keepdims=True) array -= math.mean(array, axis=tuple(range(1, math.ndims(array))), keepdims=True) return array
def data(self, data): assert math.is_tensor(data), data rank = math.ndims(data) assert rank <= 3 if rank < 3: assert rank in (0, 1) # Scalar field / vector field data = math.expand_dims(data, 0, 3 - rank) return math.to_float(data)
def shape(self): with struct.unsafe(): if math.ndims(self.data) > 0: data_shape = (self._batch_size, self._point_count, self.component_count) else: data_shape = () return self.copied_with(data=data_shape, sample_points=(self._batch_size, self._point_count, self.rank))
def unstack(self): unstacked = {} for arg in self.function_args: if isinstance(arg, Field): unstacked[arg] = arg.unstack() elif math.is_tensor(arg) and math.ndims(arg) > 0: unstacked[arg] = math.unstack(arg, axis=-1, keepdims=True) else: unstacked[arg] = [arg] * self.component_count assert len(unstacked[arg]) == self.component_count result = [_SymbolicOpField(self.function, [unstacked[arg][i] for arg in self.function_args]) for i in range(self.component_count)] return result
def _determine_component_count(args): result = None for arg in args: arg_channels = None if isinstance(arg, Field): arg_channels = arg.component_count elif math.is_tensor(arg) and math.ndims(arg) > 0: arg_channels = arg.shape[-1] if result is None: result = arg_channels else: assert result == arg_channels or arg_channels is None return result
def to_box(value, resolution_hint=None): if value is None: result = AABox(0, resolution_hint) elif isinstance(value, AABox): result = value elif isinstance(value, int): if resolution_hint is None: result = AABox(0, value) else: size = [value] * (1 if math.ndims(resolution_hint) == 0 else len(resolution_hint)) result = AABox(0, size) else: result = AABox(box) if resolution_hint is not None: assert_same_rank(len(resolution_hint), result, 'AABox rank does not match resolution.') return result
def to_box(value, resolution_hint=None): if value is None: assert resolution_hint is not None result = AABox(0, resolution_hint) elif isinstance(value, AABox): result = value elif isinstance(value, int): if resolution_hint is None: result = AABox(0, value) else: size = [value] * (1 if math.ndims(resolution_hint) == 0 else len(resolution_hint)) result = AABox(0, size) elif isinstance(value, (tuple, list)): result = AABox(0, box) else: raise ValueError("Box extent not understood: '%s'" % value) if resolution_hint is not None: assert_same_rank(len(resolution_hint), result, 'AABox rank does not match resolution.') return result
def sample(value, domain, batch_size=None): assert isinstance(domain, Domain) if isinstance(value, Field): assert_same_rank( value.rank, domain.rank, 'rank of value (%s) does not match domain (%s)' % (value.rank, domain.rank)) if isinstance(value, CenteredGrid) and value.box == domain.box and np.all( value.resolution == domain.resolution): data = value.data else: data = value.sample_at( CenteredGrid.getpoints(domain.box, domain.resolution).data) else: # value is constant components = math.staticshape( value)[-1] if math.ndims(value) > 0 else 1 data = math.zeros((batch_size, ) + tuple(domain.resolution) + (components, )) + value return CenteredGrid(data, box=domain.box, extrapolation=Material.extrapolation_mode( domain.boundaries))
def sample_at(self, points): points_rank = math.spatial_rank(points) src_rank = math.spatial_rank(self.location) # --- Expand shapes to format (batch_size, points_dims..., src_dims..., channels) --- points = math.expand_dims(points, axis=-2, number=src_rank) src_location = math.expand_dims(self.location, axis=-3, number=points_rank) src_strength = math.expand_dims(self.strength, axis=-1) if math.ndims(src_strength) == 1: src_strength = math.expand_dims(src_strength, axis=-2) src_strength = math.expand_dims(src_strength, axis=-3, number=points_rank) src_axes = tuple(range(-2, -2 - src_rank, -1)) # --- Compute distances and falloff --- distances = points - src_location if self.falloff is not None: falloff_value = self.falloff(distances) strength = src_strength * falloff_value else: strength = src_strength # --- Compute velocities --- if math.staticshape(points)[-1] == 2: # Curl in 2D dist_1, dist_2 = math.unstack(distances, axis=-1) if GLOBAL_AXIS_ORDER.is_x_first: velocity = strength * math.stack([-dist_2, dist_1], axis=-1) else: velocity = strength * math.stack([dist_2, -dist_1], axis=-1) elif math.staticshape(points)[-1] == 3: # Curl in 3D raise NotImplementedError('not yet implemented') else: raise AssertionError( 'Vector product not available in > 3 dimensions') velocity = math.sum(velocity, axis=src_axes) return velocity
def rank(self): if math.ndims(self.size) > 0: return self.size.shape[-1] else: return None
def rank(self): if math.ndims(self.upper) > 0: return math.staticshape(self.upper)[-1] else: return None
def _get(vector, axis): if math.ndims(vector) == 0: return vector else: return vector[..., axis]
def sample_points(self, sample_points): assert math.ndims(sample_points) == 3, sample_points.shape return sample_points
def location(self, loc): loc = math.to_float(loc) assert math.staticshape(loc)[-1] in (2, 3) if math.ndims(loc) < 2: loc = math.expand_dims(loc, axis=0, number=2 - math.ndims(loc)) return loc
def apply_A(pressure): from phi.physics.material import Material mode = 'replicate' if Material.solid(domain.domain.boundaries) else 'constant' padded = math.pad(pressure, [[0,0]] + [[1,1]]*(math.ndims(pressure)-2) + [[0,0]], mode=mode) return _weighted_sliced_laplace_nd(padded, weights=fluid_mask)
class SampledField(Field): def __init__(self, name, sample_points, data=1, mode='add', point_count=None, **kwargs): Field.__init__(self, **struct.kwargs(locals(), ignore=['point_count'])) self._point_count = point_count def sample_at(self, points, collapse_dimensions=True): raise NotImplementedError() def at(self, other_field, collapse_dimensions=True, force_optimization=False, return_self_if_compatible=False): if isinstance(other_field, SampledField) and other_field.sample_points is self.sample_points: return self elif isinstance(other_field, (CenteredGrid, Domain)): return self._grid_sample(other_field.box, other_field.resolution) elif isinstance(other_field, StaggeredGrid): return self._stagger_sample(other_field.box, other_field.resolution) else: return self def _grid_sample(self, box, resolution): """ Samples this field on a regular grid. :param box: physical dimensions of the grid :param resolution: grid resolution :return: CenteredGrid """ valid_indices = math.to_int(math.floor(self.sample_points)) valid_indices = math.minimum(math.maximum(0, valid_indices), resolution - 1) # Correct format for math.scatter valid_indices = batch_indices(valid_indices) scattered = math.scatter(self.sample_points, valid_indices, self.data, math.concat([[valid_indices.shape[0]], resolution, [1]], axis=-1), duplicates_handling=self.mode) return CenteredGrid(data=scattered, box=box, extrapolation='constant', name=self.name+'_centered') def _stagger_sample(self, box, resolution): """ Samples this field on a staggered grid. In addition to sampling, extrapolates the field using an occupancy mask generated from the points. :param box: physical dimensions of the grid :param resolution: grid resolution :return: StaggeredGrid """ resolution = np.array(resolution) valid_indices = math.to_int(math.floor(self.sample_points)) valid_indices = math.minimum(math.maximum(0, valid_indices), resolution - 1) # Correct format for math.scatter valid_indices = batch_indices(valid_indices) active_mask = math.scatter(self.sample_points, valid_indices, 1, math.concat([[valid_indices.shape[0]], resolution, [1]], axis=-1), duplicates_handling='any') mask = math.pad(active_mask, [[0, 0]] + [[1, 1]] * self.rank + [[0, 0]], "constant") if isinstance(self.data, (int, float, np.ndarray)): values = math.zeros_like(self.sample_points) + self.data else: values = self.data result = [] ones_1d = math.unstack(math.ones_like(values), axis=-1)[0] staggered_shape = [i + 1 for i in resolution] dx = box.size / resolution dims = range(len(resolution)) for d in dims: staggered_offset = math.stack([(0.5 * dx[i] * ones_1d if i == d else 0.0 * ones_1d) for i in dims], axis=-1) indices = math.to_int(math.floor(self.sample_points + staggered_offset)) valid_indices = math.maximum(0, math.minimum(indices, resolution)) valid_indices = batch_indices(valid_indices) values_d = math.expand_dims(math.unstack(values, axis=-1)[d], axis=-1) result.append(math.scatter(self.sample_points, valid_indices, values_d, [indices.shape[0]] + staggered_shape + [1], duplicates_handling=self.mode)) d_slice = tuple([(slice(0, -2) if i == d else slice(1,-1)) for i in dims]) u_slice = tuple([(slice(2, None) if i == d else slice(1,-1)) for i in dims]) active_mask = math.minimum(mask[(slice(None),) + d_slice + (slice(None),)], active_mask) active_mask = math.minimum(mask[(slice(None),) + u_slice + (slice(None),)], active_mask) staggered_tensor_prep = unstack_staggered_tensor(math.concat(result, axis=-1)) grid_values = StaggeredGrid(staggered_tensor_prep) # Fix values at boundary of liquids (using StaggeredGrid these might not receive a value, so we replace it with a value inside the liquid) grid_values, _ = extrapolate(grid_values, active_mask, voxel_distance=2) return grid_values @struct.variable() def data(self, data): if isinstance(data, (tuple, list, np.ndarray)): data = math.zeros_like(self.sample_points) + data return data data.override(struct.staticshape, lambda self, data: (self._batch_size, self._point_count, self.component_count) if math.ndims(self.data) > 0 else ()) @struct.constant(default='add') def mode(self, mode): assert mode in ('add', 'mean', 'any') return mode @struct.variable() def sample_points(self, sample_points): assert math.ndims(sample_points) == 3, sample_points.shape return sample_points sample_points.override(struct.staticshape, lambda self, data: (self._batch_size, self._point_count, self.rank)) @property def rank(self): return math.staticshape(self.sample_points)[-1] @property def component_count(self): if math.ndims(self.data) == 0: return 1 return math.shape(self.data)[-1] def unstack(self): raise NotImplementedError() @property def points(self): if SAMPLE_POINTS in self.flags or self.sample_points is self.data: return self return SampledField(self.name+'.points', self.sample_points, self.sample_points, flags=[SAMPLE_POINTS]) def compatible(self, other_field): if not other_field.has_points: return True if isinstance(other_field, SampledField) and other_field.sample_points is self.sample_points: return True return False def __repr__(self): return '%s[%sx(%d), %dD]' % (self.__class__.__name__, self._point_count if self._point_count is not None else '?', self.component_count, self.rank)
def component_count(self): if math.ndims(self.data) == 0: return 1 return math.shape(self.data)[-1]