Module regpy.vecsps
VectorSpaces on which operators are defined.
The classes in this module implement various vector spaces on which the
Operator
implementations are defined. The base class is VectorSpace
,
which represents plain numpy arrays of some shape and dtype. So far it is assumed that
vectors are always represented by numpy arrays.
VectorSpaces serve the following main purposes:
-
Derived classes can contain additional data like grid coordinates, bundling metadata in one place instead of having every operator generate linspaces / basis functions / whatever on their own.
-
Providing methods for generating elements of the proper shape and dtype, like zero arrays, random arrays or iterators over a basis.
-
Checking whether a given array is an element of the vector space. This is used for consistency checks, e.g. when evaluating operators. The check is only based on shape and dtype, elements do not need to carry additional structure. Real arrays are considered as elements of complex vector spaces.
-
Checking whether two vector spaces are considered equal. This is used in consistency checks e.g. for operator compositions.
All vector spaces are considered as real vector spaces, even if the dtype is complex. This affects iteration over a basis as well as functions returning the dimension or flattening arrays.
Sub-modules
regpy.vecsps.curve
regpy.vecsps.ngsolve
-
Finite element vector spaces using NGSolve …
Classes
class VectorSpace (shape, dtype=builtins.float)
-
Discrete space \mathbb{R}^\text{shape} or \mathbb{C}^\text{shape} (viewed as a real space) without any additional structure.
VectorSpaces can be added, producing
DirectSum
instances.Parameters
shape
:int
ortuple
ofints
- The shape of the arrays representing elements of this vector space.
dtype
:data-type
, optional- The elements' dtype. Should usually be either
float
orcomplex
. Default:float
.
Expand source code
class VectorSpace: r"""Discrete space \(\mathbb{R}^\text{shape}\) or \(\mathbb{C}^\text{shape}\) (viewed as a real space) without any additional structure. VectorSpaces can be added, producing `DirectSum` instances. Parameters ---------- shape : int or tuple of ints The shape of the arrays representing elements of this vector space. dtype : data-type, optional The elements' dtype. Should usually be either `float` or `complex`. Default: `float`. """ log = util.classlogger def __init__(self, shape, dtype=float): # Upcast dtype to represent at least (single-precision) floats, no # bools or ints dtype = np.result_type(np.float32, dtype) # Allow only float and complexfloat, disallow objects, strings, times # or other fancy dtypes assert np.issubdtype(dtype, np.inexact) self.dtype = dtype """The vector space's dtype""" try: shape = tuple(shape) except TypeError: shape = (shape,) self.shape = shape """The vector space's shape""" def zeros(self, dtype=None): """Return the zero element of the space. Parameters ---------- dtype : data-type, optional The dtype of the returned array. Default: the vector space's dtype. """ return np.zeros(self.shape, dtype=dtype or self.dtype) def ones(self, dtype=None): """Return an element of the space initalized to 1. Parameters ---------- dtype : data-type, optional The dtype of the returned array. Default: the vector space's dtype. """ return np.ones(self.shape, dtype=dtype or self.dtype) def empty(self, dtype=None): """Return an uninitalized element of the space. Parameters ---------- dtype : data-type, optional The dtype of the returned array. Default: the vector space's dtype. """ return np.empty(self.shape, dtype=dtype or self.dtype) def iter_basis(self): r"""Generator iterating over the standard basis of the vector space. For efficiency, the same array is returned in each step, and subsequently modified in-place. If you need the array longer than that, perform a copy. In case of complex a vector space after each each array modefied in its place with a real one it returns the same vector with \(1i\) in its place. """ elm = self.zeros() for idx in np.ndindex(self.shape): elm[idx] = 1 yield elm if self.is_complex: elm[idx] = 1j yield elm elm[idx] = 0 def rand(self, rand=np.random.random_sample, dtype=None): """Return a random element of the space. The random generator can be passed as argument. For complex dtypes, real and imaginary parts are generated independently. Parameters ---------- rand : callable, optional The random function to use. Should accept the shape as a tuple and return a real array of that shape. Numpy functions like `numpy.random.standard_normal` conform to this. Default: uniform distribution on `[0, 1)` (`numpy.random.random_sample`). dtype : data-type, optional The dtype of the returned array. Default: the vector space's dtype. """ dtype = dtype or self.dtype r = rand(self.shape) if not np.can_cast(r.dtype, dtype): raise ValueError( 'random generator {} can not produce values of dtype {}'.format(rand, dtype)) if util.is_complex_dtype(dtype) and not util.is_complex_dtype(r.dtype): c = np.empty(self.shape, dtype=dtype) c.real = r c.imag = rand(self.shape) return c else: return np.asarray(r, dtype=dtype) def randn(self, dtype=None): """Like `rand`, but using a standard normal distribution.""" return self.rand(np.random.standard_normal, dtype) @property def is_complex(self): """Boolean indicating whether the dtype is complex""" return util.is_complex_dtype(self.dtype) @property def size(self): """The size of elements (as arrays) of this vector space.""" return np.prod(self.shape) @property def realsize(self): """The dimension of the vector space as a real vector space. For complex dtypes, this is twice the number of array elements. """ if self.is_complex: return 2 * np.prod(self.shape) else: return np.prod(self.shape) @property def ndim(self): """The number of array dimensions, i.e. the length of the shape. """ return len(self.shape) @util.memoized_property def identity(self): """The `regpy.operators.Identity` operator on this vector space. """ return operators.Identity(self) def __contains__(self, x): if not isinstance(x,np.ndarray): return False elif x.shape != self.shape: return False elif util.is_complex_dtype(x.dtype): return self.is_complex elif util.is_real_dtype(x.dtype): return True else: return False def flatten(self, x): """Transform the array `x`, an element of the vector space, into a 1d real array. Inverse to `fromflat`. Parameters ---------- x : array-like The array to transform. Returns ------- array The flattened array. If memory layout allows, it will be a view into `x`. """ x = np.asarray(x) assert self.shape == x.shape if self.is_complex: if util.is_complex_dtype(x.dtype): return util.complex2real(x).ravel() else: aux = self.empty() aux.real = x return util.complex2real(aux).ravel() elif util.is_complex_dtype(x.dtype): raise TypeError('Real vector space can not handle complex vectors') return x.ravel() def fromflat(self, x): """Transform a real 1d array into an element of the vector space. Inverse to `flatten`. Parameters ---------- x : array-like The flat array to transform Returns ------- array The reshaped array. If memory layout allows, this will be a view into `x`. """ x = np.asarray(x) assert util.is_real_dtype(x.dtype) if self.is_complex: return util.real2complex(x.reshape(self.shape + (2,))) else: return x.reshape(self.shape) def complex_space(self): """Compute the corresponding complex vector space. Returns ------- VectorSpace The complex space corresponding to this vector space as a shallow copy with modified dtype. """ other = copy(self) other.dtype = np.result_type(1j, self.dtype) return other def real_space(self): """Compute the corresponding real vector space. Returns ------- VectorSpace The real space corresponding to this vector space as a shallow copy with modified dtype. """ other = copy(self) other.dtype = np.empty(0, dtype=self.dtype).real.dtype return other def __eq__(self, other): if isinstance(other, type(self)): return ( self.shape == other.shape and self.dtype == other.dtype ) else: return False def __add__(self, other): if isinstance(other, VectorSpace): return DirectSum(self, other, flatten=True) else: return NotImplemented def __radd__(self, other): if isinstance(other, VectorSpace): return DirectSum(other, self, flatten=True) else: return NotImplemented def __mul__(self, other): if isinstance(other, VectorSpace): return Prod(self, other) else: return NotImplemented def __rmul__(self, other): if isinstance(other, VectorSpace): return Prod(other, self) else: return NotImplemented def __pow__(self, power): assert isinstance(power, int) domain = self for i in range(power-1): domain = DirectSum(domain, self, flatten=True) return domain
Subclasses
Instance variables
prop log
-
The
logging.Logger
instance. Every subclass has a separate instance, named by its fully qualified name. Subclasses should use it instead ofprint
for any kind of status information to allow users to control output formatting, verbosity and persistence.Expand source code
@property def classlogger(self): """The [`logging.Logger`][1] instance. Every subclass has a separate instance, named by its fully qualified name. Subclasses should use it instead of `print` for any kind of status information to allow users to control output formatting, verbosity and persistence. [1]: https://docs.python.org/3/library/logging.html#logging.Logger """ return getattr(self, '_log', None) or getLogger(type(self).__qualname__)
prop is_complex
-
Boolean indicating whether the dtype is complex
Expand source code
@property def is_complex(self): """Boolean indicating whether the dtype is complex""" return util.is_complex_dtype(self.dtype)
prop size
-
The size of elements (as arrays) of this vector space.
Expand source code
@property def size(self): """The size of elements (as arrays) of this vector space.""" return np.prod(self.shape)
prop realsize
-
The dimension of the vector space as a real vector space. For complex dtypes, this is twice the number of array elements.
Expand source code
@property def realsize(self): """The dimension of the vector space as a real vector space. For complex dtypes, this is twice the number of array elements. """ if self.is_complex: return 2 * np.prod(self.shape) else: return np.prod(self.shape)
prop ndim
-
The number of array dimensions, i.e. the length of the shape.
Expand source code
@property def ndim(self): """The number of array dimensions, i.e. the length of the shape. """ return len(self.shape)
prop identity
-
The
Identity
operator on this vector space.Expand source code
@util.memoized_property def identity(self): """The `regpy.operators.Identity` operator on this vector space. """ return operators.Identity(self)
var dtype
-
The vector space's dtype
var shape
-
The vector space's shape
Methods
def zeros(self, dtype=None)
-
Return the zero element of the space.
Parameters
dtype
:data-type
, optional- The dtype of the returned array. Default: the vector space's dtype.
def ones(self, dtype=None)
-
Return an element of the space initalized to 1.
Parameters
dtype
:data-type
, optional- The dtype of the returned array. Default: the vector space's dtype.
def empty(self, dtype=None)
-
Return an uninitalized element of the space.
Parameters
dtype
:data-type
, optional- The dtype of the returned array. Default: the vector space's dtype.
def iter_basis(self)
-
Generator iterating over the standard basis of the vector space. For efficiency, the same array is returned in each step, and subsequently modified in-place. If you need the array longer than that, perform a copy. In case of complex a vector space after each each array modefied in its place with a real one it returns the same vector with 1i in its place.
def rand(self, rand=<built-in method random_sample of numpy.random.mtrand.RandomState object>, dtype=None)
-
Return a random element of the space.
The random generator can be passed as argument. For complex dtypes, real and imaginary parts are generated independently.
Parameters
rand
:callable
, optional- The random function to use. Should accept the shape as a tuple and return a real
array of that shape. Numpy functions like
numpy.random.standard_normal
conform to this. Default: uniform distribution on[0, 1)
(numpy.random.random_sample
). dtype
:data-type
, optional- The dtype of the returned array. Default: the vector space's dtype.
def randn(self, dtype=None)
-
Like
rand
, but using a standard normal distribution. def flatten(self, x)
-
Transform the array
x
, an element of the vector space, into a 1d real array. Inverse tofromflat
.Parameters
x
:array-like
- The array to transform.
Returns
array
- The flattened array. If memory layout allows, it will be a view into
x
.
def fromflat(self, x)
-
Transform a real 1d array into an element of the vector space. Inverse to
flatten
.Parameters
x
:array-like
- The flat array to transform
Returns
array
- The reshaped array. If memory layout allows, this will be a view into
x
.
def complex_space(self)
-
Compute the corresponding complex vector space.
Returns
VectorSpace
- The complex space corresponding to this vector space as a shallow copy with modified dtype.
def real_space(self)
-
Compute the corresponding real vector space.
Returns
VectorSpace
- The real space corresponding to this vector space as a shallow copy with modified dtype.
class MeasureSpaceFcts (measure=None, shape=None, dtype=builtins.float)
-
Discrete space \mathbb{R}^N or \mathbb{C}^N (viewed as a real space) with an additional measure that is given via a non-negative weight for each element of the space. Either the measure or the shape have to be specified. The measure defaults to the constant 1 measure for each point if it is not given.
Parameters
measure
:np.ndarray
, optional- The non negative array representing the point measures. If it is not given the measures are set to 1 for each point. Default: None
shape
:int
ortuple
ofints
, optional- The shape of the arrays representing elements of this vector space. If it is not given the shape is taken from measure. Default: None
dtype
:data-type
, optional- The elements' dtype. Should usually be either
float
orcomplex
. Default:float
.
Expand source code
class MeasureSpaceFcts(VectorSpace): r"""Discrete space \(\mathbb{R}^N\) or \(\mathbb{C}^N\) (viewed as a real space) with an additional measure that is given via a non-negative weight for each element of the space. Either the measure or the shape have to be specified. The measure defaults to the constant 1 measure for each point if it is not given. Parameters ---------- measure : np.ndarray, optional The non negative array representing the point measures. If it is not given the measures are set to 1 for each point. Default: None shape : int or tuple of ints, optional The shape of the arrays representing elements of this vector space. If it is not given the shape is taken from measure. Default: None dtype : data-type, optional The elements' dtype. Should usually be either `float` or `complex`. Default: `float`. """ def __init__(self,measure=None,shape=None,dtype=float): assert measure is not None or shape is not None if(isinstance(measure, np.ndarray)): assert np.issubdtype(measure.dtype, np.floating) assert np.min(measure)>=0 shape = measure.shape elif(np.isscalar(measure)): assert isinstance(measure, int) or isinstance(measure,float) assert measure>=0 assert shape!=None elif(measure==None): measure=1 #TODO: Make a default case super().__init__(shape,dtype) self.measure=measure r""" Stores values of point measures """ @property def measure(self): return self._measure @measure.setter def measure(self,new_measure): if np.isscalar(new_measure): assert isinstance(new_measure, int) or isinstance(new_measure,float) or (np.issubdtype(new_measure.dtype,np.number) and np.isrealobj(new_measure)) else: assert new_measure.shape==self.shape and np.issubdtype(new_measure.dtype, np.number) and np.isrealobj(new_measure) assert np.min(new_measure)>=0 self._measure=new_measure def __eq__(self, other): if(not super().__eq__(other)): return False return np.all(self.measure==other.measure)
Ancestors
Subclasses
Instance variables
var measure
-
Stores values of point measures
Expand source code
@property def measure(self): return self._measure
Inherited members
class GridFcts (*coords, axisdata=None, dtype=builtins.float, use_cell_measure=True, boundary_ext='sym', ext_const=None)
-
A vector space representing functions defined on a rectangular grid.
Parameters
*coords
-
Axis specifications, one for each dimension. Each can be either
- an integer
n
, making the axis range from0
ton-1
, - a tuple that is passed as arguments to
numpy.linspace
, or - an array-like containing the axis coordinates.
- an integer
axisdata
:tuple
ofarrays
, optional-
If the axes represent indices into some auxiliary arrays, these can be passed via this parameter. If given, there must be one array for each dimension, the size of the first axis of which must match the respective dimension's length. Besides that, no further structure is imposed or assumed, this parameter exists solely to keep everything related to the vector space in one place.
If
axisdata
is given, thecoords
can be omitted. dtype
:data-type
, optional- The dtype of the vector space.
use_cell_measure
:bool
, optional- If true a measure is calculated using the volume of the grid cells. Else the measure is one for all cells. Defaults to True.
boundary_ext
:string {‘sym’, ‘const’, ‘zero’}
, optional- Defines how the measure is continued at the boundary. Possible modes are 'sym' : The boundary coordinates are assumed to be in the center of their cell 'const': The boundary cells are extended by a constant given in boundary_ext_const 'zero': The boundary coordinates are assumed to be on the outer edge of their cell defaults to 'sym'
boundary_ext_const
:float
ortuple
offloats
, optional- Defines extension of cells at edges of each axis. Can be set to a constant for all axes, one constant for each axis or one constant for the start and one for the end of each axis.
Expand source code
class GridFcts(MeasureSpaceFcts): r"""A vector space representing functions defined on a rectangular grid. Parameters ---------- *coords Axis specifications, one for each dimension. Each can be either - an integer `n`, making the axis range from `0` to `n-1`, - a tuple that is passed as arguments to `numpy.linspace`, or - an array-like containing the axis coordinates. axisdata : tuple of arrays, optional If the axes represent indices into some auxiliary arrays, these can be passed via this parameter. If given, there must be one array for each dimension, the size of the first axis of which must match the respective dimension's length. Besides that, no further structure is imposed or assumed, this parameter exists solely to keep everything related to the vector space in one place. If `axisdata` is given, the `coords` can be omitted. dtype : data-type, optional The dtype of the vector space. use_cell_measure : bool, optional If true a measure is calculated using the volume of the grid cells. Else the measure is one for all cells. Defaults to True. boundary_ext : string {‘sym’, ‘const’, ‘zero’}, optional Defines how the measure is continued at the boundary. Possible modes are 'sym' : The boundary coordinates are assumed to be in the center of their cell 'const': The boundary cells are extended by a constant given in boundary_ext_const 'zero': The boundary coordinates are assumed to be on the outer edge of their cell defaults to 'sym' boundary_ext_const: float or tuple of floats, optional Defines extension of cells at edges of each axis. Can be set to a constant for all axes, one constant for each axis or one constant for the start and one for the end of each axis. """ def __init__(self, *coords, axisdata=None, dtype=float,use_cell_measure=True,boundary_ext='sym',ext_const=None): views = [] if axisdata and not coords: coords = [d.shape[0] for d in axisdata] for n, c in enumerate(coords): if isinstance(c, int): v = np.arange(c) elif isinstance(c, tuple): assert len(c) == 3, "Tuple must be of length 3" assert all([isinstance(c_i, int) or isinstance(c_i,float) for c_i in c]) and isinstance(c[2], int), "The axis must be real" v = np.linspace(*c) else: v = np.asarray(c).view() assert np.issubdtype(v.dtype, np.number) and np.isrealobj(v), "axis must be real" if 1 == v.ndim < len(coords): s = [1] * len(coords) s[n] = -1 v = v.reshape(s) v.flags.writeable = False #assert np.all(v[:-1] <= v[1:]) # ensure coords are ascending views.append(v) self.coords = np.asarray(np.broadcast_arrays(*views)) """The coordinate arrays, broadcast to the shape of the grid. The shape will be `(len(self.shape),) + self.shape`.""" assert self.coords[0].ndim == len(self.coords) axes = [] extents = [] for i in range(self.coords.shape[0]): slc = [0] * self.coords.shape[0] slc[i] = slice(None) axis = self.coords[i][tuple(slc)] axes.append(np.asarray(axis)) extents.append(abs(axis[-1] - axis[0])) self.axes = axes """The axes as 1d arrays""" self.extents = np.asarray(extents) """The lengths of the axes, i.e. `axis[-1] - axis[0]`, for each axis.""" if(use_cell_measure): super().__init__(GridFcts._calc_cell_measure(axes,boundary_ext,ext_const), dtype=dtype) else: super().__init__(shape=self.coords[0].shape, dtype=dtype) if axisdata is not None: axisdata = tuple(axisdata) assert len(axisdata) == len(coords) for i in range(len(axisdata)): assert self.shape[i] == axisdata[i].shape[0] self.axisdata = axisdata """The axisdata, if given.""" def _calc_cell_measure(axes,boundary_ext,ext_const=None): ext_axes=[] if(boundary_ext=="sym"): ext_axes=[np.pad(v,(1,1),mode='reflect',reflect_type='odd') for v in axes] elif(boundary_ext=="zero"): ext_axes=[np.pad(v,(1,1),mode='edge') for v in axes] elif(boundary_ext=="const"): ext_arr=np.zeros((len(axes),2)) if(np.isscalar(ext_const)): ext_const=len(axes)*(ext_const,) assert isinstance(ext_const, tuple) assert len(ext_const)==len(axes) for i, v in enumerate(axes): if isinstance(ext_const[i],tuple): assert np.isscalar(ext_const[i][0]) and np.isscalar(ext_const[i][1]) ext_axes.append(np.pad(v,(1,1),mode='constant',constant_values=(v[0]-ext_const[i][0], v[-1]+ext_const[i][1]))) else: assert np.isscalar(ext_const[i]) ext_axes.append(np.pad(v,(1,1),mode='constant',constant_values=(v[0]-ext_const[i], v[-1]+ext_const[i]))) ax_widths=[0.5*(ext_v[2:]-ext_v[:-2]) for ext_v in ext_axes] assert len(axes)<=26 prod_string=','.join([chr(k) for k in range(65,65+len(axes))]) return np.einsum(prod_string,*ax_widths)#computes product of entries from ax_widths
Ancestors
Subclasses
Instance variables
var coords
-
The coordinate arrays, broadcast to the shape of the grid. The shape will be
(len(self.shape),) + self.shape
. var axes
-
The axes as 1d arrays
var extents
-
The lengths of the axes, i.e.
axis[-1] - axis[0]
, for each axis. var axisdata
-
The axisdata, if given.
Inherited members
class UniformGridFcts (*coords, axisdata=None, dtype=builtins.float, periodic=False)
-
A vector space representing functions defined on a rectangular grid with equidistant axes. The measure is constant. Use
GridFcts
for grids with uniform axes and non-constant measures.All arguments are passed to the
GridFcts
constructor, but an error will be produced if any axis is not uniform.Parameters
*coords
-
Axis specifications, one for each dimension. Each can be either
- an integer
n
, making the axis range from0
ton-1
, - a tuple that is passed as arguments to
numpy.linspace
, or - an array-like containing the axis coordinates.
- an integer
axisdata
:tuple
ofarrays
, optional-
If the axes represent indices into some auxiliary arrays, these can be passed via this parameter. If given, there must be one array for each dimension, the size of the first axis of which must match the respective dimension's length. Besides that, no further structure is imposed or assumed, this parameter exists solely to keep everything related to the vector space in one place.
If
axisdata
is given, thecoords
can be omitted. dtype
:data-type
, optional- The dtype of the vector space.
periodic
:If true, the grid is assumed to be periodic. If coords is a tuple
oftriples
- passed as arguments to numpy.linspace, the right boundaries (second elements of the triples) are reduced such that the difference of the second and first elements represents periodicity lengths.
Expand source code
class UniformGridFcts(GridFcts): """A vector space representing functions defined on a rectangular grid with equidistant axes. The measure is constant. Use `GridFcts` for grids with uniform axes and non-constant measures. All arguments are passed to the `GridFcts` constructor, but an error will be produced if any axis is not uniform. Parameters ---------- *coords Axis specifications, one for each dimension. Each can be either - an integer `n`, making the axis range from `0` to `n-1`, - a tuple that is passed as arguments to `numpy.linspace`, or - an array-like containing the axis coordinates. axisdata : tuple of arrays, optional If the axes represent indices into some auxiliary arrays, these can be passed via this parameter. If given, there must be one array for each dimension, the size of the first axis of which must match the respective dimension's length. Besides that, no further structure is imposed or assumed, this parameter exists solely to keep everything related to the vector space in one place. If `axisdata` is given, the `coords` can be omitted. dtype : data-type, optional The dtype of the vector space. periodic: If true, the grid is assumed to be periodic. If coords is a tuple of triples passed as arguments to numpy.linspace, the right boundaries (second elements of the triples) are reduced such that the difference of the second and first elements represents periodicity lengths. """ def __init__(self, *coords, axisdata=None, dtype=float, periodic = False): if periodic and all(isinstance(c,tuple) for c in coords): coords = tuple((l, (l+(n-1)*r)/n ,n) for (l,r,n) in coords) super().__init__(*coords, axisdata=axisdata,dtype=dtype,use_cell_measure=False) spacing = [] for axis in self.axes: assert util.is_uniform(axis) if(axis.shape[0]==1): spacing.append(1.0) else: spacing.append(axis[1] - axis[0]) self.spacing = np.asarray(spacing) """The spacing along every axis, i.e. `axis[i+1] - axis[i]`""" self.volume_elem = np.prod(self.spacing) """The volumen element, initialized as product of `spacing`""" self.measure = self.volume_elem """ Setting measure to be initialzed by `volume_element`""" @MeasureSpaceFcts.measure.setter def measure(self,new_measure): if np.isscalar(new_measure): assert isinstance(new_measure, int) or isinstance(new_measure,float) or np.issubdtype(new_measure.dtype,np.number) assert new_measure>0 super(UniformGridFcts, self.__class__).measure.fset(self, new_measure) elif(isinstance(new_measure,np.ndarray)): assert np.all(new_measure == new_measure.flat[0]) super(UniformGridFcts, self.__class__).measure.fset(self, new_measure.flat[0]) self.volume_elem=self.measure
Ancestors
Subclasses
Instance variables
var measure
-
Setting measure to be initialzed by
volume_element
Expand source code
@property def measure(self): return self._measure
var spacing
-
The spacing along every axis, i.e.
axis[i+1] - axis[i]
var volume_elem
-
The volumen element, initialized as product of
spacing
Inherited members
class DirectSum (*summands, flatten=False)
-
The direct sum of an arbirtary number of vector spaces.
Elements of the direct sum will always be 1d real arrays.
Note that constructing DirectSum instances can be done more comfortably simply by adding
VectorSpace
instances. However, for generic code, when it's not known whether the summands are themselves direct sums, it's better to avoid the+
overload due theflatten
parameter (see below), since otherwise the number of summands is not fixed.DirectSum instances can be indexed and iterated over, returning / yielding the component vector spaces.
Parameters
*summands
:tuple
ofVectorSpace instances
- The vector spaces to be summed.
flatten
:bool
, optional- Whether summands that are themselves
DirectSum
s should be merged into this instance. If False, DirectSum is not associative, but the join and split methods behave more predictably. Default: False, but will be set to True when constructing the DirectSum via VectorSpace.add, i.e. when using the+
operator, in order to make repeated sums likeA + B + C
unambiguous.
Expand source code
class DirectSum(VectorSpace): """The direct sum of an arbirtary number of vector spaces. Elements of the direct sum will always be 1d real arrays. Note that constructing DirectSum instances can be done more comfortably simply by adding `VectorSpace` instances. However, for generic code, when it's not known whether the summands are themselves direct sums, it's better to avoid the `+` overload due the `flatten` parameter (see below), since otherwise the number of summands is not fixed. DirectSum instances can be indexed and iterated over, returning / yielding the component vector spaces. Parameters ---------- *summands : tuple of VectorSpace instances The vector spaces to be summed. flatten : bool, optional Whether summands that are themselves `DirectSum`s should be merged into this instance. If False, DirectSum is not associative, but the join and split methods behave more predictably. Default: False, but will be set to True when constructing the DirectSum via VectorSpace.__add__, i.e. when using the `+` operator, in order to make repeated sums like `A + B + C` unambiguous. """ def __init__(self, *summands, flatten=False): assert all(isinstance(s, VectorSpace) for s in summands) self.summands = [] for s in summands: if flatten and isinstance(s, type(self)): self.summands.extend(s.summands) else: self.summands.append(s) self.idxs = [0] + list(accumulate(s.realsize for s in self.summands)) super().__init__(self.idxs[-1]) def __eq__(self, other): if isinstance(other, type(self)): return ( len(self.summands) == len(other.summands) and all(s == t for s, t in zip(self.summands, other.summands)) ) else: return NotImplemented def join(self, *xs): """Transform a collection of elements of the summands to an element of the direct sum. Parameters ---------- *xs : tuple of array-like The elements of the summands. The number should match the number of summands, and for all `i`, `xs[i]` should be an element of `self[i]`. Returns ------- 1d array An element of the direct sum """ assert all(x in s for s, x in zip(self.summands, xs)) elm = self.empty() for s, x, start, end in zip(self.summands, xs, self.idxs, self.idxs[1:]): elm[start:end] = s.flatten(x) return elm def split(self, x): """Split an element of the direct sum into a tuple of elements of the summands. The result arrays may be views into `x`, if memory layout allows it. For complex summands, a necessary condition is that the elements' real and imaginary parts are contiguous in memory. Parameters ---------- x : array An array representing an element of the direct sum. Returns ------- tuple of arrays The components of x for the summands. """ assert x in self return tuple( s.fromflat(x[start:end]) for s, start, end in zip(self.summands, self.idxs, self.idxs[1:]) ) def __getitem__(self, item): return self.summands[item] def __iter__(self): return iter(self.summands) def __len__(self): return len(self.summands)
Ancestors
Methods
def join(self, *xs)
-
Transform a collection of elements of the summands to an element of the direct sum.
Parameters
*xs
:tuple
ofarray-like
- The elements of the summands. The number should match the number of summands,
and for all
i
,xs[i]
should be an element ofself[i]
.
Returns
1d array
- An element of the direct sum
def split(self, x)
-
Split an element of the direct sum into a tuple of elements of the summands.
The result arrays may be views into
x
, if memory layout allows it. For complex summands, a necessary condition is that the elements' real and imaginary parts are contiguous in memory.Parameters
x
:array
- An array representing an element of the direct sum.
Returns
tuple
ofarrays
- The components of x for the summands.
Inherited members
class Prod (*factors, flatten=False)
-
The tensor product of an arbitrary number of vector spaces.
Elements of the tensor product will always be arrays with in n-dim where n is number of factors. Representing each coefficient to a basis tensor that are mad up be the tensor product of each basis element from teh factored spaces. Note, that spaces with possible multidimensional elements (e.g.
UniformGridFcts
with multiple dimensions) get flatted.Prod instances can be indexed and iterated over, returning / yielding the component vector spaces.
Parameters
*factors
:tuple
ofVectorSpace instances
- The vector spaces to be factored.
flatten
:bool
, optional- Whether factors that are themselves
Prod
s should be merged into this instance. If False, Prod is not associative, but the product method behaves more predictably. Default: False
Expand source code
class Prod(VectorSpace): """The tensor product of an arbitrary number of vector spaces. Elements of the tensor product will always be arrays with in n-dim where n is number of factors. Representing each coefficient to a basis tensor that are mad up be the tensor product of each basis element from teh factored spaces. Note, that spaces with possible multidimensional elements (e.g. `UniformGridFcts` with multiple dimensions) get flatted. Prod instances can be indexed and iterated over, returning / yielding the component vector spaces. Parameters ---------- *factors : tuple of VectorSpace instances The vector spaces to be factored. flatten : bool, optional Whether factors that are themselves `Prod`s should be merged into this instance. If False, Prod is not associative, but the product method behaves more predictably. Default: False """ def __init__(self, *factors, flatten=False): assert all(isinstance(s, VectorSpace) for s in factors) assert all(s.is_complex for s in factors) or all(not s.is_complex for s in factors) self.factors = [] """List of the `VectorSpaces` to be taken as Product.""" shape = () self.volume_elem = 1 """Poduct of the `volume_elem` of all factors that have defined this property. """ if factors[0].is_complex: dt=np.complex128 else: dt=np.float64 for s in factors: if hasattr(s, 'volume_elem'): self.volume_elem *= s.volume_elem if flatten and isinstance(s, type(self)): self.factors.extend(s.factors) shape += s.shape else: self.factors.append(s) shape += (s.size,) super().__init__(shape,dtype=dt) def __eq__(self, other): return ( isinstance(other, type(self)) and len(self.factors) == len(other.factors) and all(s == t for s, t in zip(self.factors, other.factors)) ) def product(self, *xs): """Transform a collection of elements of the factors into an element of the tensor product by an outer product. Parameters ---------- *xs : tuple of array-like The elements of the factors. The number should match the number of factors, and for all `i`, `xs[i]` should be an element of `self[i]`. Returns ------- n-dim array An element of the tensor product """ assert all(x in s for s, x in zip(self.factors, xs)) elm = 1 for s, x in zip(self.factors, xs): elm = np.ma.outer(elm,x) return elm def __getitem__(self, item): return self.factors[item] def __iter__(self): return iter(self.factors) def __len__(self): return len(self.factors)
Ancestors
Instance variables
var factors
-
List of the
VectorSpaces
to be taken as Product. var volume_elem
-
Poduct of the
volume_elem
of all factors that have defined this property.
Methods
def product(self, *xs)
-
Transform a collection of elements of the factors into an element of the tensor product by an outer product.
Parameters
*xs
:tuple
ofarray-like
- The elements of the factors. The number should match the number of factors,
and for all
i
,xs[i]
should be an element ofself[i]
.
Returns
n-dim array
- An element of the tensor product
Inherited members