def test_resizing_op_call(odl_tspace_impl): impl = odl_tspace_impl dtypes = [ dt for dt in tensor_space_impl(impl).available_dtypes() if is_numeric_dtype(dt) ] for dtype in dtypes: # Minimal test since this operator only wraps resize_array space = odl.uniform_discr([0, -1], [1, 1], (4, 5), impl=impl) res_space = odl.uniform_discr([0, -0.6], [2, 0.2], (8, 2), impl=impl) res_op = odl.ResizingOperator(space, res_space) out = res_op(space.one()) true_res = np.zeros((8, 2)) true_res[:4, :] = 1 assert np.array_equal(out, true_res) out = res_space.element() res_op(space.one(), out=out) assert np.array_equal(out, true_res) # Test also mapping to default impl for other 'impl' if impl != 'numpy': space = odl.uniform_discr([0, -1], [1, 1], (4, 5), impl=impl) res_space = odl.uniform_discr([0, -0.6], [2, 0.2], (8, 2)) res_op = odl.ResizingOperator(space, res_space) out = res_op(space.one()) true_res = np.zeros((8, 2)) true_res[:4, :] = 1 assert np.array_equal(out, true_res) out = res_space.element() res_op(space.one(), out=out) assert np.array_equal(out, true_res)
def examples(self): """Return example random vectors.""" # Always return the same numbers rand_state = np.random.get_state() np.random.seed(1337) if is_numeric_dtype(self.dtype): yield ('Linearly spaced samples', self.element( np.linspace(0, 1, self.size).reshape(self.shape))) yield ('Normally distributed noise', self.element(np.random.standard_normal(self.shape))) if self.is_real: yield ('Uniformly distributed noise', self.element(np.random.uniform(size=self.shape))) elif self.is_complex: yield ('Uniformly distributed noise', self.element( np.random.uniform(size=self.shape) + np.random.uniform(size=self.shape) * 1j)) else: # TODO: return something that always works, like zeros or ones? raise NotImplementedError('no examples available for non-numeric' 'data type') np.random.set_state(rand_state)
def complex_space(self): """The space corresponding to this space's `complex_dtype`. Raises ------ ValueError If `dtype` is not a numeric data type. """ if not is_numeric_dtype(self.dtype): raise ValueError( '`complex_space` not defined for non-numeric `dtype`') return self.astype(self.complex_dtype)
def complex_dtype(self): """The complex dtype corresponding to this space's `dtype`. Raises ------ NotImplementedError If `dtype` is not a numeric data type. """ if not is_numeric_dtype(self.dtype): raise NotImplementedError( '`complex_dtype` not defined for non-numeric `dtype`') return self.__complex_dtype
def real_space(self): """The space corresponding to this space's `real_dtype`. Raises ------ NotImplementedError If `dtype` is not a numeric data type. """ if not is_numeric_dtype(self.dtype): raise NotImplementedError( '`real_space` not defined for non-numeric `dtype`') return self.astype(self.real_dtype)
def tspace_type(space, impl, dtype=None): """Select the correct corresponding tensor space. Parameters ---------- space : `LinearSpace` Template space from which to infer an adequate tensor space. If it has a `LinearSpace.field` attribute, ``dtype`` must be consistent with it. impl : string Implementation backend for the tensor space. dtype : optional Data type which the space is supposed to use. If ``None`` is given, the space type is purely determined from ``space`` and ``impl``. Otherwise, it must be compatible with the field of ``space``. Returns ------- stype : type Space type selected after the space's field, the backend and the data type. """ field_type = type(getattr(space, 'field', None)) if dtype is None: pass elif is_real_floating_dtype(dtype): if field_type is None or field_type == ComplexNumbers: raise TypeError('real floating data type {!r} requires space ' 'field to be of type RealNumbers, got {}' ''.format(dtype, field_type)) elif is_complex_floating_dtype(dtype): if field_type is None or field_type == RealNumbers: raise TypeError('complex floating data type {!r} requires space ' 'field to be of type ComplexNumbers, got {!r}' ''.format(dtype, field_type)) elif is_numeric_dtype(dtype): if field_type == ComplexNumbers: raise TypeError('non-floating data type {!r} requires space field ' 'to be of type RealNumbers, got {!r}'.format( dtype, field_type)) try: return tensor_space_impl(impl) except ValueError: raise NotImplementedError('no corresponding tensor space available ' 'for space {!r} and implementation {!r}' ''.format(space, impl))
def test_resizing_op_properties(odl_tspace_impl, padding): impl = odl_tspace_impl dtypes = [ dt for dt in tensor_space_impl(impl).available_dtypes() if is_numeric_dtype(dt) ] pad_mode, pad_const = padding for dtype in dtypes: # Explicit range space = odl.uniform_discr([0, -1], [1, 1], (10, 5), dtype=dtype) res_space = odl.uniform_discr([0, -3], [2, 3], (20, 15), dtype=dtype) res_op = odl.ResizingOperator(space, res_space, pad_mode=pad_mode, pad_const=pad_const) assert res_op.domain == space assert res_op.range == res_space assert res_op.offset == (0, 5) assert res_op.pad_mode == pad_mode assert res_op.pad_const == pad_const if pad_mode == 'constant' and pad_const != 0: assert not res_op.is_linear else: assert res_op.is_linear # Implicit range via ran_shp and offset res_op = odl.ResizingOperator(space, ran_shp=(20, 15), offset=[0, 5], pad_mode=pad_mode, pad_const=pad_const) assert np.allclose(res_op.range.min_pt, res_space.min_pt) assert np.allclose(res_op.range.max_pt, res_space.max_pt) assert np.allclose(res_op.range.cell_sides, res_space.cell_sides) assert res_op.range.dtype == res_space.dtype assert res_op.offset == (0, 5) assert res_op.pad_mode == pad_mode assert res_op.pad_const == pad_const if pad_mode == 'constant' and pad_const != 0: assert not res_op.is_linear else: assert res_op.is_linear
def test_resizing_op_inverse(padding, odl_tspace_impl): impl = odl_tspace_impl pad_mode, pad_const = padding dtypes = [dt for dt in tensor_space_impl(impl).available_dtypes() if is_numeric_dtype(dt)] for dtype in dtypes: space = odl.uniform_discr([0, -1], [1, 1], (4, 5), dtype=dtype, impl=impl) res_space = odl.uniform_discr([0, -1.4], [1.5, 1.4], (6, 7), dtype=dtype, impl=impl) res_op = odl.ResizingOperator(space, res_space, pad_mode=pad_mode, pad_const=pad_const) # Only left inverse if the operator extends in all axes x = noise_element(space) assert res_op.inverse(res_op(x)) == x
def astype(self, dtype): """Return a copy of this space with new ``dtype``. Parameters ---------- dtype : Scalar data type of the returned space. Can be provided in any way the `numpy.dtype` constructor understands, e.g. as built-in type or as a string. Data types with non-trivial shapes are not allowed. Returns ------- newspace : `TensorSpace` Version of this space with given data type. """ if dtype is None: # Need to filter this out since Numpy iterprets it as 'float' raise ValueError('`None` is not a valid data type') dtype = np.dtype(dtype) if dtype == self.dtype: return self if is_numeric_dtype(self.dtype): # Caching for real and complex versions (exact dtype mappings) if dtype == self.__real_dtype: if self.__real_space is None: self.__real_space = self._astype(dtype) return self.__real_space elif dtype == self.__complex_dtype: if self.__complex_space is None: self.__complex_space = self._astype(dtype) return self.__complex_space else: return self._astype(dtype) else: return self._astype(dtype)
def contains_all(self, other): """Return ``True`` if ``other`` is a sequence of complex numbers.""" dtype = getattr(other, 'dtype', None) if dtype is None: dtype = np.result_type(*other) return is_numeric_dtype(dtype)
def dft_preprocess_data(arr, shift=True, axes=None, sign='-', out=None): """Pre-process the real-space data before DFT. This function multiplies the given data with the separable function:: p(x) = exp(+- 1j * dot(x - x[0], xi[0])) where ``x[0]`` and ``xi[0]`` are the minimum coodinates of the real-space and reciprocal grids, respectively. The sign of the exponent depends on the choice of ``sign``. In discretized form, this function becomes an array:: p[k] = exp(+- 1j * k * s * xi[0]) If the reciprocal grid is not shifted, i.e. symmetric around 0, it is ``xi[0] = pi/s * (-1 + 1/N)``, hence:: p[k] = exp(-+ 1j * pi * k * (1 - 1/N)) For a shifted grid, we have :math:``xi[0] = -pi/s``, thus the array is given by:: p[k] = (-1)**k Parameters ---------- arr : `array-like` Array to be pre-processed. If its data type is a real non-floating type, it is converted to 'float64'. shift : bool or or sequence of bools, optional If ``True``, the grid is shifted by half a stride in the negative direction. With a sequence, this option is applied separately on each axis. axes : int or sequence of ints, optional Dimensions in which to calculate the reciprocal. The sequence must have the same length as ``shift`` if the latter is given as a sequence. Default: all axes. sign : {'-', '+'}, optional Sign of the complex exponent. out : `numpy.ndarray`, optional Array in which the result is stored. If ``out is arr``, an in-place modification is performed. For real data type, this is only possible for ``shift=True`` since the factors are complex otherwise. Returns ------- out : `numpy.ndarray` Result of the pre-processing. If ``out`` was given, the returned object is a reference to it. Notes ----- If ``out`` is not specified, the data type of the returned array is the same as that of ``arr`` except when ``arr`` has real data type and ``shift`` is not ``True``. In this case, the return type is the complex counterpart of ``arr.dtype``. """ arr = np.asarray(arr) if not is_numeric_dtype(arr.dtype): raise ValueError('array has non-numeric data type {}' ''.format(dtype_repr(arr.dtype))) elif is_real_dtype(arr.dtype) and not is_real_floating_dtype(arr.dtype): arr = arr.astype('float64') if axes is None: axes = list(range(arr.ndim)) else: try: axes = [int(axes)] except TypeError: axes = list(axes) shape = arr.shape shift_list = normalized_scalar_param_list(shift, length=len(axes), param_conv=bool) # Make a copy of arr with correct data type if necessary, or copy values. if out is None: if is_real_dtype(arr.dtype) and not all(shift_list): out = np.array(arr, dtype=complex_dtype(arr.dtype), copy=True) else: out = arr.copy() else: out[:] = arr if is_real_dtype(out.dtype) and not shift: raise ValueError('cannot pre-process real input in-place without ' 'shift') if sign == '-': imag = -1j elif sign == '+': imag = 1j else: raise ValueError("`sign` '{}' not understood".format(sign)) def _onedim_arr(length, shift): if shift: # (-1)^indices factor = np.ones(length, dtype=out.dtype) factor[1::2] = -1 else: factor = np.arange(length, dtype=out.dtype) factor *= -imag * np.pi * (1 - 1.0 / length) np.exp(factor, out=factor) return factor.astype(out.dtype, copy=False) onedim_arrs = [] for axis, shift in zip(axes, shift_list): length = shape[axis] onedim_arrs.append(_onedim_arr(length, shift)) fast_1d_tensor_mult(out, onedim_arrs, axes=axes, out=out) return out
def __init__(self, map_type, fspace, partition, tspace, linear=False): """Initialize a new instance. Parameters ---------- map_type : {'sampling', 'interpolation'} The type of operator fspace : `FunctionSpace` The non-discretized (abstract) set of functions to be discretized partition : `RectPartition` Partition of (a subset of) ``fspace.domain`` based on a `RectGrid`. tspace : `TensorSpace` Space providing containers for the values/coefficients of a discretized object. Its `TensorSpace.shape` must be equal to ``partition.shape``. linear : bool, optional Create a linear operator if ``True``, otherwise a non-linear operator. """ map_type, map_type_in = str(map_type).lower(), map_type if map_type not in ('sampling', 'interpolation'): raise ValueError('`map_type` {!r} not understood' ''.format(map_type_in)) if not isinstance(fspace, FunctionSpace): raise TypeError('`fspace` {!r} is not a `FunctionSpace` ' 'instance'.format(fspace)) if not isinstance(partition, RectPartition): raise TypeError('`partition` {!r} is not a `RectPartition` ' 'instance'.format(partition)) if not isinstance(tspace, TensorSpace): raise TypeError('`tspace` {!r} is not a `TensorSpace` instance' ''.format(tspace)) if not fspace.domain.contains_set(partition): raise ValueError('{} not contained in the domain {} ' 'of the function set {}' ''.format(partition, fspace.domain, fspace)) if tspace.shape != partition.shape: raise ValueError('`tspace.shape` not equal to `partition.shape`: ' '{} != {}' ''.format(tspace.shape, partition.shape)) domain = fspace if map_type == 'sampling' else tspace range = tspace if map_type == 'sampling' else fspace super(FunctionSpaceMapping, self).__init__(domain, range, linear=linear) self.__partition = partition if self.is_linear: if self.domain.field is None: raise TypeError('`fspace.field` cannot be `None` for ' '`linear=True`') if not is_numeric_dtype(tspace.dtype): raise TypeError('`tspace.dtype` must be a numeric data type ' 'for `linear=True`, got {}' ''.format(dtype_repr(tspace))) if fspace.field != tspace.field: raise ValueError('`fspace.field` not equal to `tspace.field`: ' '{} != {}' ''.format(fspace.field, tspace.field))