class LineSearch(with_metaclass(ABCMeta, object)): """Abstract base class for line search step length methods.""" @abstractmethod def __call__(self, x, direction, dir_derivative): """Calculate step length in direction.
class StepLength(with_metaclass(ABCMeta, object)): """Abstract base class for step length methods.""" # TODO: change signature so it reflects the requirements for e.g. # Barzilai-Borwein @abstractmethod def __call__(self, x, direction, dir_derivative): """Calculate the step length at a point.
class Geometry(with_metaclass(ABCMeta, object)): """Abstract geometry class. A geometry is described by * a detector, * a set of detector motion parameters, * a function mapping motion parameters to the location of a reference point (e.g. the center of the detector surface), * a rotation applied to the detector surface, depending on the motion parameters, * a mapping from the motion and surface parameters to the detector pixel direction to the source, * optionally a mapping from the motion parameters to the source position """ def __init__(self, ndim, motion_part, detector): """Initialize a new instance. Parameters ---------- ndim : positive int Number of dimensions of this geometry, i.e. dimensionality of the physical space in which this geometry is embedded motion_part : `RectPartition` Partition for the set of "motion" parameters detector : `Detector` The detector of this geometry """ if int(ndim) <= 0: raise ValueError('number of dimensions {} is not positive' ''.format(ndim)) if not isinstance(motion_part, RectPartition): raise TypeError('`motion_part` {!r} not a RectPartition instance' ''.format(motion_part)) if not isinstance(detector, Detector): raise TypeError('`detector` {!r} not a Detector instance' ''.format(detector)) self._ndim = int(ndim) self._motion_part = motion_part self._detector = detector self._implementation_cache = {} @property def ndim(self): """Number of dimensions of the geometry.""" return self._ndim @property def motion_partition(self): """Partition of the motion parameter set into subsets.""" return self._motion_part @property def motion_params(self): """Continuous motion parameter range, an `IntervalProd`.""" return self.motion_partition.set @property def motion_grid(self): """Sampling grid of `motion_params`.""" return self.motion_partition.grid @property def detector(self): """Detector representation of this geometry.""" return self._detector @property def det_partition(self): """Partition of the detector parameter set into subsets.""" return self.detector.partition @property def det_params(self): """Continuous detector parameter range, an `IntervalProd`.""" return self.detector.params @property def det_grid(self): """Sampling grid of `det_params`.""" return self.detector.grid @property def partition(self): """Joined parameter set partition for motion and detector. Returns a `RectPartition` with the detector partition inserted after the motion partition. """ # TODO: change when RectPartition.append is implemented return self.det_partition.insert(0, self.motion_partition) @property def params(self): """Joined parameter set for motion and detector. By convention, the motion parameters come before the detector parameters. """ return self.partition.set @property def grid(self): """Joined sampling grid for motion and detector. By convention, the motion grid comes before the detector grid. """ return self.partition.grid @abstractmethod def det_refpoint(self, mpar): """Detector reference point function. Parameters ---------- mpar : `motion_params` element Motion parameter for which to calculate the detector reference point Returns ------- point : `numpy.ndarray`, shape (`ndim`,) The reference point, an `ndim`-dimensional vector """ @abstractmethod def rotation_matrix(self, mpar): """Detector rotation function for calculating the detector reference position. Parameters ---------- mpar : `motion_params` element Motion parameter for which to calculate the detector reference rotation Returns ------- rot : `numpy.ndarray`, shape (`ndim`, `ndim`) The rotation matrix mapping the standard basis vectors in the fixed ("lab") coordinate system to the basis vectors of the local coordinate system of the detector reference point, expressed in the fixed system. """ def det_to_src(self, mpar, dpar, normalized=True): """Vector pointing from a detector location to the source. A function of the motion and detector parameters. Parameters ---------- mpar : `motion_params` element Motion parameter at which to evaluate dpar : `det_params` element Detector parameter at which to evaluate normalized : bool, optional If ``True``, return a normalized (unit) vector. Returns ------- vec : `numpy.ndarray`, shape (`ndim`,) (Unit) vector pointing from the detector to the source """ raise NotImplementedError('abstract method') def det_point_position(self, mpar, dpar): """Detector point position function. Parameters ---------- mpar : `motion_params` element Motion parameter at which to evaluate dpar : `det_params` element Detector parameter at which to evaluate Returns ------- pos : `numpy.ndarray` (shape (`ndim`,)) Source position, an `ndim`-dimensional vector """ # TODO: check and write test return np.asarray( (self.det_refpoint(mpar) + self.rotation_matrix(mpar).dot(self.detector.surface(dpar)))) @property def implementation_cache(self): """Dictionary acting as a cache for this geometry. Intended for reuse of computations. Implementations that use this storage should take care of unique naming. Returns ------- implementations : dict """ return self._implementation_cache
class Detector(with_metaclass(ABCMeta, object)): """Abstract detector class. A detector is described by * a set of parameters for surface parametrization (including sampling), * a function mapping a surface parameter to the location of a detector point relative to its reference point, * optionally a surface measure function. """ def __init__(self, part): """Initialize a new instance. Parameters ---------- part : `RectPartition` Partition of the detector parameter set (pixelization). It determines dimension, parameter range and discretization. """ if not isinstance(part, RectPartition): raise TypeError('`part` {!r} is not a RectPartition instance' ''.format(part)) self._part = part @abstractmethod def surface(self, param): """Parametrization of the detector reference surface. Parameters ---------- param : `params` element Parameter value where to evaluate the function Returns ------- point : Spatial location of the detector point corresponding to ``param`` """ @property def partition(self): """Partition of the detector parameter set into subsets.""" return self._part @property def ndim(self): """Number of dimensions of this detector (0, 1 or 2).""" return self.partition.ndim @property def params(self): """Surface parameter set of this detector.""" return self.partition.set @property def grid(self): """Sampling grid of the parameters.""" return self.partition.grid @property def shape(self): """Number of subsets (pixels) of the detector per axis.""" return self.partition.shape @property def size(self): """Total number of pixels.""" return self.partition.size def surface_deriv(self, param): """Partial derivative(s) of the surface parametrization. Parameters ---------- param : `params` element The parameter value where to evaluate the function Returns ------- deriv : Vector (``ndim=1``) or sequence of vectors corresponding to the partial derivatives at ``param`` """ raise NotImplementedError('abstract method') def surface_measure(self, param): """Density function of the surface measure. This is the default implementation relying on the `surface_deriv` method. For ``ndim == 1``, the density is given by the `Arc length`_, for ``ndim == 2``, it is the length of the cross product of the partial derivatives of the parametrization, see Wikipedia's `Surface area`_ article. Parameters ---------- param : `params` element The parameter value where to evaluate the function Returns ------- measure : float The density value at the given parameter .. _Arc length: https://en.wikipedia.org/wiki/Curve#Lengths_of_curves .. _Surface area: https://en.wikipedia.org/wiki/Surface_area """ if param not in self.params: raise ValueError('`param` {} not in the valid range {}' ''.format(param, self.params)) if self.ndim == 1: return float(np.linalg.norm(self.surface_deriv(param))) elif self.ndim == 2: return float(np.linalg.norm(np.cross(*self.surface_deriv(param)))) else: raise NotImplementedError('abstract method')
class NtuplesBaseVector(with_metaclass(ABCMeta, object)): """Abstract class for representation of `NtuplesBase` elements. Defines abstract attributes and concrete ones which are independent of data representation. """ def __init__(self, space, *args, **kwargs): """Initialize a new instance.""" self._space = space @abstractmethod def copy(self): """Create an identical (deep) copy of this vector.""" @abstractmethod def asarray(self, start=None, stop=None, step=None, out=None): """Extract the data of this array as a numpy array. Parameters ---------- start : `int`, optional Start position. `None` means the first element. start : `int`, optional One element past the last element to be extracted. `None` means the last element. start : `int`, optional Step length. `None` means 1. out : `numpy.ndarray` Array to write result to. Returns ------- asarray : `numpy.ndarray` Numpy array of the same type as the space. """ @abstractmethod def __getitem__(self, indices): """Access values of this vector. Parameters ---------- indices : `int` or `slice` The position(s) that should be accessed Returns ------- values : `NtuplesBase.dtype` or `NtuplesBaseVector` The value(s) at the index (indices) """ @abstractmethod def __setitem__(self, indices, values): """Set values of this vector. Parameters ---------- indices : `int` or `slice` The position(s) that should be set values : scalar, `array-like` or `NtuplesBaseVector` The value(s) that are to be assigned. If ``index`` is an integer, ``value`` must be single value. If ``index`` is a slice, ``value`` must be broadcastable to the size of the slice (same size, shape (1,) or single value). """ @abstractmethod def __eq__(self, other): """Return ``self == other``. Returns ------- equals : `bool` `True` if all entries of ``other`` are equal to this vector's entries, `False` otherwise. """ @property def space(self): """Space to which this vector.""" return self._space @property def ndim(self): """Number of dimensions, always 1.""" return 1 @property def dtype(self): """Length of this vector, equal to space size.""" return self.space.dtype @property def size(self): """Length of this vector, equal to space size.""" return self.space.size def __len__(self): """Return ``len(self)``. Return the number of space dimensions. """ return self.space.size @property def shape(self): """Number of entries per axis, equals (size,) for linear storage.""" return self.space.shape @property def itemsize(self): """The size in bytes on one element of this type.""" return self.dtype.itemsize @property def nbytes(self): """The number of bytes this vector uses in memory.""" return self.size * self.itemsize def __array__(self, dtype=None): """Return a numpy array of this ntuple. Parameters ---------- dtype : `object` Specifier for the data type of the output array Returns ------- array : `numpy.ndarray` """ if dtype is None: return self.asarray() else: return self.asarray().astype(dtype, copy=False) def __array_wrap__(self, obj): """Return a new vector from the data in obj. Parameters ---------- obj : `numpy.ndarray` The array that should be wrapped Returns ------- vector : `NtuplesBaseVector` """ if obj.ndim == 0: return self.space.field.element(obj) else: return self.space.element(obj) def __ne__(self, other): """Return ``self != other``.""" return not self.__eq__(other) def __str__(self): """Return ``str(self)``.""" return array1d_str(self) def __repr__(self): """Return ``repr(self)``.""" return '{!r}.element({})'.format(self.space, array1d_repr(self)) @property def ufunc(self): """`NtuplesBaseUFuncs`, access to numpy style ufuncs. These are always available, but may or may not be optimized for the specific space in use. """ return NtuplesBaseUFuncs(self) def show(self, title=None, method='scatter', show=False, fig=None, **kwargs): """Display the function graphically. Parameters ---------- title : `str`, optional Set the title of the figure method : `str`, optional 1d methods: 'plot' : graph plot 'scatter' : point plot show : `bool`, optional If the plot should be showed now or deferred until later. fig : `matplotlib.figure.Figure` The figure to show in. Expected to be of same "style", as the figure given by this function. The most common use case is that ``fig`` is the return value from an earlier call to this function. kwargs : {'figsize', 'saveto', ...} Extra keyword arguments passed on to display method See the Matplotlib functions for documentation of extra options. Returns ------- fig : `matplotlib.figure.Figure` The resulting figure. It is also shown to the user. See Also -------- odl.util.graphics.show_discrete_data : Underlying implementation """ from odl.util.graphics import show_discrete_data from odl.discr import RegularGrid grid = RegularGrid(0, self.size - 1, self.size) return show_discrete_data(self.asarray(), grid, title=title, method=method, show=show, fig=fig, **kwargs)
class Set(with_metaclass(ABCMeta, object)): """An abstract set. **Abstract Methods** Each subclass of `Set` must implement two methods: one to check if an object is contained in the set and one to test if two sets are equal. **Membership test:** ``__contains__(self, other)`` Test if ``other`` is a member of this set. This function provides the operator overload for `in`. **Parameters:** other : `object` The object to be tested for membership **Returns:** contains : `bool` `True` if ``other`` is a member of this set, `False` otherwise. **Equality test:** ``__eq__(self, other)`` Test if ``other`` is the same set as this set, i.e. both sets are of the same type and contain the same elements. This function provides the operator overload for ``==``. **Parameters:** other : `object` The object to be tested for equality. **Returns:** equals : `bool` `True` if both sets are of the same type and contain the same elements, `False` otherwise. A default implementation of the operator overload for ``!=`` via ``__ne__(self, other)`` is provided as ``not self.__eq__(other)``. **Element creation (optional)**: ``element(self, inp=None)`` Create an element of this set, either from scratch or from an input parameter. **Parameters:** inp : `object`, optional The object from which to create the new element **Returns:** element : member of this set If ``inp`` is `None`, return an arbitrary element. Otherwise, return the element created from ``inp``. """ @abstractmethod def __contains__(self, other): """Return ``other in self``.""" def contains_set(self, other): """Test if ``other`` is a subset of this set. Implementing this method is optional. Default it tests for equality. """ return self == other def contains_all(self, other): """Test if all points in ``other`` are contained in this set. This is a default implementation and should be overridden by subclasses. """ return all(x in self for x in other) @abstractmethod def __eq__(self, other): """Return ``self == other``.""" def __ne__(self, other): """Return ``self != other``.""" return not self.__eq__(other) def element(self, inp=None): """Return an element from ``inp`` or from scratch. Implementing this method is optional. """ raise NotImplementedError('`element` method not implemented') @property def examples(self): """Return a `generator` with elements in the set as name-value pairs. Can return a finite set of examples or an infinite set. Optional to implement, intended to be used for diagnostics. By default, the generator yields ``('element()', self.element())``. """ yield ('element()', self.element())
class Set(with_metaclass(ABCMeta, object)): """An abstract set. **Abstract Methods** Each subclass of `Set` must implement two methods: one to check if an object is contained in the set and one to test if two sets are equal. **Membership test:** ``__contains__(self, other)`` Test if ``other`` is a member of this set. This function provides the operator overload for ``in``. **Parameters:** other : Object to be tested for membership **Returns:** contains : bool ``True`` if ``other`` is a member of this set, ``False`` otherwise. **Equality test:** ``__eq__(self, other)`` Test if ``other`` is the same set as this set, i.e. both sets are of the same type and contain the same elements. This function provides the operator overload for ``==``. **Parameters:** other : Object to be tested for equality. **Returns:** equals : bool ``True`` if both sets are of the same type and contain the same elements, ``False`` otherwise. A default implementation of the operator overload for ``!=`` via ``__ne__(self, other)`` is provided as ``not self.__eq__(other)``. **Element creation (optional)**: ``element(self, inp=None)`` Create an element of this set, either from scratch or from an input parameter. **Parameters:** inp : optional Object from which to create the new element **Returns:** element : member of this set If ``inp`` is None, return an arbitrary element. Otherwise, return the element created from ``inp``. """ @abstractmethod def __contains__(self, other): """Return ``other in self``.""" def contains_set(self, other): """Test if ``other`` is a subset of this set. This is a default implementation that simply tests for equality. It should be overridden by subclasses. Returns ------- set_contained : bool ``True`` if ``other`` is contained in this set, ``False`` otherwise. """ return self == other def contains_all(self, other): """Test if all elements in ``other`` are contained in this set. This is a default implementation that assumes ``other`` to be a sequence and tests each elment of ``other`` sequentially. This method should be overridden by subclasses. Returns ------- all_contained : bool ``True`` if all elements of ``other`` are contained in this set, ``False`` otherwise """ return all(x in self for x in other) @abstractmethod def __eq__(self, other): """Return ``self == other``.""" def __ne__(self, other): """Return ``self != other``.""" return not self.__eq__(other) def __cmp__(self, other): """Comparsion not implemented.""" # Stops python 2 from allowing comparsion of arbitrary objects raise TypeError('unorderable types: {}, {}' ''.format(self.__class__.__name__, type(other))) def element(self, inp=None): """Return an element from ``inp`` or from scratch. This method should be overridden by subclasses. """ raise NotImplementedError('`element` method not implemented') @property def examples(self): """Generator creating name-value pairs of set elements. This method is mainly intended for diagnostics and yields elements, either a finite number of times or indefinitely. This default implementation returns ``('element()', self.element())`` and should be overridden by subclasses. """ yield ('element()', self.element()) def __repr__(self): """Return ``repr(self)``.""" return '{}()'.format(self.__class__.__name__) def __str__(self): """Return ``str(self)``.""" return '{}'.format(self.__class__.__name__)
class NtuplesBaseVector(with_metaclass(ABCMeta, object)): """Abstract class for `NtuplesBase` elements. Do not use this class directly -- to create an element of a vector space, call the space's `LinearSpace.element` method instead. """ def __init__(self, space, *args, **kwargs): """Initialize a new instance.""" self.__space = space @abstractmethod def copy(self): """Return an identical (deep) copy of this vector.""" @abstractmethod def asarray(self, start=None, stop=None, step=None, out=None): """Return the data of this vector as a numpy array. Parameters ---------- start : int, optional Index of the first vector entry to be included in the extracted array. ``None`` is equivalent to 0. stop : int, optional Index of the first vector entry to be excluded from the extracted array. ``None`` is equivalent to `size`. step : int, optional Vector index step between consecutive array ellements. ``None`` is equivalent to 1. out : `numpy.ndarray`, optional Array to write the result to. Returns ------- out : `numpy.ndarray` Numpy array of the same `dtype` as this vector. If ``out`` was given, the returned object is a reference to it. """ @abstractmethod def __getitem__(self, indices): """Return ``self[indices]``. Parameters ---------- indices : int or `slice` The position(s) that should be accessed. An integer results in a single entry to be returned. For a slice, the output is a vector of the same type. Returns ------- values : `NtuplesBase.dtype` or `NtuplesBaseVector` Extracted entries according to ``indices``. """ @abstractmethod def __setitem__(self, indices, values): """Implement ``self[indices] = values``. Parameters ---------- indices : int or `slice` The position(s) that should be assigned to. values : scalar, `array-like` or `NtuplesBaseVector` The value(s) that are to be assigned. If ``index`` is an integer, ``value`` must be a single value. If ``index`` is a slice, ``value`` must be broadcastable to the shape of the slice, i.e. same size, shape ``(1,)`` or a single value. """ @abstractmethod def __eq__(self, other): """Return ``self == other``. Returns ------- equals : bool ``True`` if all entries of ``other`` are equal to this vector's entries, False otherwise. """ def __ne__(self, other): """Return ``self != other``.""" return not self.__eq__(other) @property def space(self): """Space to which this vector belongs.""" return self.__space @property def ndim(self): """Number of dimensions of this vector's space, always 1.""" return 1 @property def dtype(self): """Data type of this vector's space.""" return self.space.dtype @property def size(self): """Length of this vector, equal to space size.""" return self.space.size def __len__(self): """Return ``len(self)``. Equal to the number of space dimensions. """ return self.space.size @property def shape(self): """Number of entries per axis, equals ``(size,)``.""" return self.space.shape @property def itemsize(self): """Size in bytes of one element of this vector.""" return self.dtype.itemsize @property def nbytes(self): """Number of bytes this vector uses in memory.""" return self.size * self.itemsize def __array__(self, dtype=None): """Return a Numpy array containing this vector's data. Parameters ---------- dtype : Specifier for the data type of the output array. Returns ------- array : `numpy.ndarray` """ if dtype is None: return self.asarray() else: return self.asarray().astype(dtype, copy=False) def __array_wrap__(self, obj): """Return a new vector from the data in ``obj``. Parameters ---------- obj : `numpy.ndarray` Array that should be wrapped. Returns ------- vector : `NtuplesBaseVector` Numpy array wrapped back into this vector's element type. """ if obj.ndim == 0: return self.space.field.element(obj) else: return self.space.element(obj) def __int__(self): """Return ``int(self)``. Returns ------- int : int Integer representing this vector. Raises ------ TypeError : If the vector is of `size` != 1. """ if self.size != 1: raise TypeError('only size 1 vectors can be converted to int') return int(self[0]) def __long__(self): """Return ``long(self)``. The `long` method is only available in Python 2. Returns ------- long : `long` Integer representing this vector. Raises ------ TypeError : If the vector is of `size` != 1. """ if self.size != 1: raise TypeError('only size 1 vectors can be converted to long') return long(self[0]) def __float__(self): """Return ``float(self)``. Returns ------- float : float Floating point number representing this vector. Raises ------ TypeError : If the vector is of `size` != 1. """ if self.size != 1: raise TypeError('only size 1 vectors can be converted to float') return float(self[0]) def __complex__(self): """Return ``complex(self)``. Returns ------- complex : `complex` Complex floating point number representing this vector. Raises ------ TypeError : If the vector is of `size` != 1. """ if self.size != 1: raise TypeError('only size 1 vectors can be converted to complex') return complex(self[0]) def __str__(self): """Return ``str(self)``.""" return array1d_str(self) def __repr__(self): """Return ``repr(self)``.""" return '{!r}.element({})'.format(self.space, array1d_repr(self)) @property def ufuncs(self): """Internal class for access to Numpy style universal functions. These default ufuncs are always available, but may or may not be optimized for the specific space in use. """ return NtuplesBaseUfuncs(self) def show(self, title=None, method='scatter', force_show=False, fig=None, **kwargs): """Display this vector graphically. Parameters ---------- title : string, optional Set the title of the figure method : string, optional The following plotting methods are available: 'scatter' : point plot 'plot' : graph plot force_show : bool, optional Whether the plot should be forced to be shown now or deferred until later. Note that some backends always displays the plot, regardless of this value. fig : `matplotlib.figure.Figure`, optional Figure to draw into. Expected to be of same "style" as the figure given by this function. The most common use case is that ``fig`` is the return value of an earlier call to this function. kwargs : {'figsize', 'saveto', ...}, optional Extra keyword arguments passed on to the display method. See the Matplotlib functions for documentation of extra options. Returns ------- fig : `matplotlib.figure.Figure` Resulting figure. If ``fig`` was given, the returned object is a reference to it. See Also -------- odl.util.graphics.show_discrete_data : Underlying implementation """ from odl.util.graphics import show_discrete_data from odl.discr import uniform_grid grid = uniform_grid(0, self.size - 1, self.size) return show_discrete_data(self.asarray(), grid, title=title, method=method, force_show=force_show, fig=fig, **kwargs) @property def impl(self): """Implementation of this vector's space.""" return self.space.impl