def derivative(self, vf): """Derivative of the point-wise norm operator at ``vf``. The derivative at ``F`` of the point-wise norm operator ``N`` with finite exponent ``p`` and weights ``w`` is the pointwise inner product with the vector field ``x --> N(F)(x)^(1-p) * [ F_j(x) * |F_j(x)|^(p-2) ]_j``. Note that this is not well-defined for ``F = 0``. If ``p < 2``, any zero component will result in a singularity. Parameters ---------- vf : `domain` `element-like` Vector field ``F`` at which to evaluate the derivative. Returns ------- deriv : `PointwiseInner` Derivative operator at the given point ``vf``. Raises ------ NotImplementedError * if the vector field space is complex, since the derivative is not linear in that case * if the exponent is ``inf`` """ if self.domain.field == ComplexNumbers(): raise NotImplementedError('operator not Frechet-differentiable ' 'on a complex space') if self.exponent == float('inf'): raise NotImplementedError('operator not Frechet-differentiable ' 'for exponent = inf') vf = self.domain.element(vf) vf_pwnorm_fac = self(vf) if self.exponent != 2: # optimize away most common case. vf_pwnorm_fac **= (self.exponent - 1) inner_vf = vf.copy() for gi in inner_vf: # This is the old line from odl version 0.6.0. #gi /= vf_pwnorm_fac * gi ** (self.exponent - 2) tmp = vf_pwnorm_fac * gi**(self.exponent - 2) gi = np.divide(gi, tmp, where=tmp != 0) return PointwiseInner(self.domain, inner_vf, weighting=self.weights)
def _call(self, vf, out): """Implement ``self(vf, out)``.""" if self.domain.field == ComplexNumbers(): vf[0].multiply(self._vecfield[0].conj(), out=out) else: vf[0].multiply(self._vecfield[0], out=out) if self.is_weighted: out *= self.weights[0] if self.domain.size == 1: return tmp = self.range.element() for vfi, gi, wi in zip(vf[1:], self.vecfield[1:], self.weights[1:]): if self.domain.field == ComplexNumbers(): vfi.multiply(gi.conj(), out=tmp) else: vfi.multiply(gi, out=tmp) if self.is_weighted: tmp *= wi out += tmp
def __init__(self, size, dtype): """Initialize a new instance. Parameters ---------- size : non-negative int Number of entries in a tuple. dtype : Data type for each tuple entry. Can be provided in any way the `numpy.dtype` function understands, most notably as built-in type, as one of NumPy's internal datatype objects or as string. Only scalar data types (numbers) are allowed. """ NtuplesBase.__init__(self, size, dtype) if not is_scalar_dtype(self.dtype): raise TypeError('{!r} is not a scalar data type'.format(dtype)) if is_real_dtype(self.dtype): field = RealNumbers() self.__is_real = True self.__real_dtype = self.dtype self.__real_space = self try: self.__complex_dtype = complex_dtype(self.dtype) except ValueError: self.__complex_dtype = None self.__complex_space = None # Set in first call of astype else: field = ComplexNumbers() self.__is_real = False try: self.__real_dtype = real_dtype(self.dtype) except ValueError: self.__real_dtype = None self.__real_space = None # Set in first call of astype self.__complex_dtype = self.dtype self.__complex_space = self self.__is_floating = is_floating_dtype(self.dtype) LinearSpace.__init__(self, field)
def cn(size, dtype=None, impl='numpy', **kwargs): """Return the complex vector space ``C^n``. Parameters ---------- size : positive int The number of dimensions of the space dtype : `object`, optional The data type of the storage array. Can be provided in any way the `numpy.dtype` function understands, most notably as built-in type, as one of NumPy's internal datatype objects or as string. Only complex floating-point data types are allowed. Default: default of the implementation given by calling ``default_dtype(ComplexNumbers())`` on the `FnBase` implementation. impl : string, optional The backend to use. See `odl.space.entry_points.FN_IMPLS` for available options. kwargs : Extra keyword arguments to pass to the implmentation. Returns ------- cn : `FnBase` See Also -------- fn : n-tuples over a field with arbitrary scalar data type. """ cn_impl = FN_IMPLS[impl] if dtype is None: dtype = cn_impl.default_dtype(ComplexNumbers()) cn = cn_impl(size, dtype, **kwargs) if not cn.is_cn: raise TypeError('data type {!r} not a complex floating-point type.' ''.format(dtype)) return cn
def __str__(self): """Return ``str(self)``.""" inner_str = '{}'.format(self.domain) dtype_str = dtype_repr(self.out_dtype) if self.field == RealNumbers(): if self.out_dtype == np.dtype('float64'): pass else: inner_str += ', out_dtype={}'.format(dtype_str) elif self.field == ComplexNumbers(): if self.out_dtype == np.dtype('complex128'): inner_str += ', field={!r}'.format(self.field) else: inner_str += ', out_dtype={}'.format(dtype_str) else: # different field, name explicitly inner_str += ', field={!r}'.format(self.field) inner_str += ', out_dtype={}'.format(dtype_str) return '{}({})'.format(self.__class__.__name__, inner_str)
def __init__(self, domain, field=None, out_dtype=None): """Initialize a new instance. Parameters ---------- domain : `Set` The domain of the functions field : `Field`, optional The range of the functions, usually the `RealNumbers` or `ComplexNumbers`. If not given, the field is either inferred from ``out_dtype``, or, if the latter is also ``None``, set to ``RealNumbers()``. out_dtype : optional Data type of the return value of a function in this space. Can be given in any way `numpy.dtype` understands, e.g. as string (``'float64'``) or data type (``float``). By default, ``'float64'`` is used for real and ``'complex128'`` for complex spaces. """ if not isinstance(domain, Set): raise TypeError('`domain` {!r} not a Set instance'.format(domain)) if field is not None and not isinstance(field, Field): raise TypeError('`field` {!r} not a `Field` instance' ''.format(field)) # Data type: check if consistent with field, take default for None dtype, dtype_in = np.dtype(out_dtype), out_dtype # Default for both None if field is None and out_dtype is None: field = RealNumbers() out_dtype = np.dtype('float64') # field None, dtype given -> infer field elif field is None: if is_real_dtype(dtype): field = RealNumbers() elif is_complex_floating_dtype(dtype): field = ComplexNumbers() else: raise ValueError('{} is not a scalar data type' ''.format(dtype_in)) # field given -> infer dtype if not given, else check consistency elif field == RealNumbers(): if out_dtype is None: out_dtype = np.dtype('float64') elif not is_real_dtype(dtype): raise ValueError('{} is not a real data type' ''.format(dtype_in)) elif field == ComplexNumbers(): if out_dtype is None: out_dtype = np.dtype('complex128') elif not is_complex_floating_dtype(dtype): raise ValueError('{} is not a complex data type' ''.format(dtype_in)) # Else: keep out_dtype=None, which results in lazy dtype determination LinearSpace.__init__(self, field) FunctionSet.__init__(self, domain, field, out_dtype) # Init cache attributes for real / complex variants if self.field == RealNumbers(): self.__real_out_dtype = self.out_dtype self.__real_space = self self.__complex_out_dtype = complex_dtype(self.out_dtype, default=np.dtype(object)) self.__complex_space = None elif self.field == ComplexNumbers(): self.__real_out_dtype = real_dtype(self.out_dtype) self.__real_space = None self.__complex_out_dtype = self.out_dtype self.__complex_space = self else: self.__real_out_dtype = None self.__real_space = None self.__complex_out_dtype = None self.__complex_space = None
def __init__(self, adjoint, vfspace, vecfield, weighting=None): """Initialize a new instance. All parameters are given according to the specifics of the "usual" operator. The ``adjoint`` parameter is used to control conversions for the inverse transform. Parameters ---------- adjoint : bool ``True`` if the operator should be the adjoint, ``False`` otherwise. vfspace : `ProductSpace` Space of vector fields on which the operator acts. It has to be a product space of identical spaces, i.e. a power space. vecfield : ``vfspace`` `element-like` Vector field with which to calculate the point-wise inner product of an input vector field weighting : `array-like` or float, optional Weighting array or constant for the norm. If an array is given, its length must be equal to ``domain.size``. By default, the weights are is taken from ``domain.weighting``. Note that this excludes unusual weightings with custom inner product, norm or dist. """ if not isinstance(vfspace, ProductSpace): raise TypeError('`vfsoace` {!r} is not a ProductSpace ' 'instance'.format(vfspace)) if adjoint: super().__init__(domain=vfspace[0], range=vfspace, base_space=vfspace[0], linear=True) else: super().__init__(domain=vfspace, range=vfspace[0], base_space=vfspace[0], linear=True) # Bail out if the space is complex but we cannot take the complex # conjugate. if (vfspace.field == ComplexNumbers() and not hasattr(self.base_space.element_type, 'conj')): raise NotImplementedError( 'base space element type {!r} does not implement conj() ' 'method required for complex inner products' ''.format(self.base_space.element_type)) self._vecfield = vfspace.element(vecfield) # Handle weighting, including sanity checks if weighting is None: if hasattr(vfspace.weighting, 'array'): self.__weights = vfspace.weighting.array elif hasattr(vfspace.weighting, 'const'): self.__weights = (vfspace.weighting.const * np.ones(len(vfspace))) else: raise ValueError('weighting scheme {!r} of the domain does ' 'not define a weighting array or constant' ''.format(vfspace.weighting)) elif np.isscalar(weighting): self.__weights = float(weighting) * np.ones(vfspace.size) else: self.__weights = np.asarray(weighting, dtype='float64') self.__is_weighted = not np.array_equiv(self.weights, 1.0)
def cn(shape, dtype=None, impl='numpy', **kwargs): """Return a space of complex tensors. Parameters ---------- shape : positive int or sequence of positive ints Number of entries per axis for elements in this space. A single integer results in a space with 1 axis. dtype : optional Data type of each element. Can be provided in any way the `numpy.dtype` function understands, e.g. as built-in type or as a string. Only complex floating-point data types are allowed. For ``None``, the `TensorSpace.default_dtype` of the created space is used in the form ``default_dtype(ComplexNumbers())``. impl : str, optional Impmlementation back-end for the space. See `odl.space.entry_points.tensor_space_impl_names` for available options. kwargs : Extra keyword arguments passed to the space constructor. Returns ------- cn : `TensorSpace` Examples -------- Space of complex 3-tuples with ``complex64`` entries: >>> odl.cn(3, dtype='complex64') cn(3, dtype='complex64') Complex 2x3 tensors with ``complex64`` entries: >>> odl.cn((2, 3), dtype='complex64') cn((2, 3), dtype='complex64') The default data type depends on the implementation. For ``impl='numpy'``, it is ``'complex128'``: >>> space = odl.cn((2, 3)) >>> space cn((2, 3)) >>> space.dtype dtype('complex128') See Also -------- tensor_space : Space of tensors with arbitrary scalar data type. rn : Real tensor space. """ cn_cls = tensor_space_impl(impl) if dtype is None: dtype = cn_cls.default_dtype(ComplexNumbers()) # Use args by keyword since the constructor may take other arguments # by position cn = cn_cls(shape=shape, dtype=dtype, **kwargs) if not cn.is_complex: raise ValueError('data type {!r} not a complex floating-point type.' ''.format(dtype)) return cn