Source code for ddg.datastructures.nets.net

import itertools as itts
from collections.abc import Callable, Container, Sized, Iterable
import numpy as np
from ddg.datastructures.nets.domain import (Domain,
                                            SmoothDomain,
                                            SmoothRectangularDomain,
                                            SmoothInterval,
                                            DiscreteDomain,
                                            DiscreteInterval,
                                            EmptyDomain)
from ddg.abc import Transformable

[docs]class Net(Transformable, Callable): """ Base class for nets. Parameters ---------- fct : function Function defining the net. domain : ddg.datastructures.nets.domain.Domain or list of intervals Domain of `fct`. name : str, optional Name of the net (mostly used for blender conversion). Attributes ---------- fct : function Function defining the net. domain : ddg.datastructures.nets.domain.Domain Domain of `fct`. name : str Name of the net (mostly used for blender conversion). dimension : int Dimension of the domain. See Also -------- SmoothNet, DiscreteNet, NetCollection """ def __init__(self, fct, domain, *args, name='Net'): self._fct = fct if isinstance(domain, Domain): self.domain = domain else: raise ValueError #self.domain = domain self.name = name super().__init__() @property def fct(self): return lambda *a: self._fct(*a) @property def dimension(self): return self.domain.dimension def __call__(self, *args, **kwargs): raise NotImplementedError
[docs] def push_transformation(self, f): return super().push_transformation(f)
[docs] def pop_transformation(self): return super().pop_transformation()
[docs]class EmptyNet(Net, Iterable): def __init__(self, coordinates, name='Empty'): _coordinates = np.array(coordinates) super().__init__(lambda : _coordinates, EmptyDomain(), name=name) def __iter__(self): return iter(()) def __call__(self): return self.transformation(self.fct())
[docs]class PointNet(Net, Iterable): def __init__(self, value, name='Point'): if isinstance(value, Iterable): tmp = np.array(value) value = lambda : tmp super().__init__(value, SmoothRectangularDomain([]), name=name) def __iter__(self): return iter((self(),)) def __call__(self): return self.transformation(self.fct())
[docs]class SmoothNet(Net): """ Store and manage data of a smooth net. Parameters ---------- fct : function Function defining the net. domain : ddg.datastructures.nets.domain.SmoothDomain or list of intervals Domain of `fct`. periodicity : set, optional Set containing indices of periodic coordinate directions. periodic : bool Alternative way of specifying periodicity for one dimensional net. It precedes periodicity if set. name : str, optional Name of the net (mostly used for blender conversion). See Also -------- Net, NetCollection """ def __new__(cls, fct, domain, *args, **kwargs): #TODO: Better handling of arguments for object() if isinstance(domain, SmoothDomain): tmp = (domain.dimension == 1) elif isinstance(domain, Iterable): tmp = (len(domain) == 1) else: raise TypeError( 'Domain does not support given type: {}'.format( domain.__class__.__name__)) if tmp: return super(SmoothNet, cls).__new__(SmoothCurve) else: return super(SmoothNet, cls).__new__(cls) def __init__(self, fct, domain, *args, name='SmoothNet'): if not isinstance(domain, Domain): domain = SmoothDomain(domain) super().__init__(fct, domain, *args, name=name) def __call__(self, *args): return self.transformation(self.fct(*args))
# TODO: reintroduce domain check with tolerances? # if args in self.domain: # return self.transformation(self.fct(*args)) # else: # raise ValueError('Argument not in the domain of the net')
[docs]class SmoothCurve(SmoothNet): def __new__(cls, *args, **kwargs): return super(SmoothNet, cls).__new__(SmoothCurve) def __init__(self, fct, domain, *args, name='SmoothCurve'): if not isinstance(domain, Domain): domain = SmoothInterval(domain) super().__init__(fct, domain, *args, name=name,)
[docs]class DiscreteNet(Net, Iterable): """ Store and manage data of a discrete net. Parameters ---------- fct : function Function defining the net. domain : ddg.datastructures.nets.domain.DiscreteDomain or list of intervals Domain of 'fct'. periodicity : set, optional Set containing indices of periodic coordinate directions. traverser : ddg.datastructures.nets.traverser.Traverser, optional Traversing scheme of the dicrete net. name : str, optional Name of the net (mostly used for blender conversion). init : iterable or ddg.datastructures.nets.domain.DiscreteDomain, optional Preset initial data or generate all data in the given domain. hint : function, optional Function returning the net points needed to compute the value for a given net point (by default there is no hint function). If specified the values are calculated iteratively. Attributes ---------- traverser : ddg.datastructures.nets.traverser.Traverser The traverser of the Domain. See Also -------- Net, NetCollection Notes ----- * The `hint` function has to return an empty list for all net points which are initialized by `fct`. * The function `fct` will be called repeatedly to calculate the value for the given index. It has to guarantee that the calculation will stop, i.e. that it will hit initial data.:: E.g.: Suppose the missing vertex has the index (i,j). Then `fct` could use the values at vertices ((i-1,j-1), (i,j-1), (i-1, j)) to calculate the missing value (i-1,j) (i,j) input -> o--------o <- missing | | | | | | input -> o--------o <- input (i-1,j-1) (i,j-1) If the standard axis {(i,0) | i int} and {(0,i) | i int} are initial data (either given by `fct` itself or previously set) this definition is well defined. * If `hint` is not given the calculations are made recursively. So it may easyly generate `RecursionError` for exeeding recursition depth. Thus it is important to take the search pattern of `fct` into account for choosing the traverser.:: E.g.: Suppose the inital data is given in a zick-zack pattern in the fourth quadrant like (0,0) o--o | (-1,-1) o--o | (-2,-2) o--o If we suppose `fct` to use the search pattern from the comment above a (finite) linear traverser will generate `RecursitionError` for a relatively small search box (about 80x80). However a diagonal traverser will guarantee a small recursition depth. 1-->2-->3-->4 1 5 8 10 \\ \\ \\ 5-->6-->7 2 6 9 \\ \\ 8-->9 3 7 \\ 10 4 (lin. trav.) (diag. trav.) """ def __new__(cls, fct, domain, hint=None, **kwargs): if isinstance(domain, DiscreteDomain): tmp = (domain.dimension == 1) elif isinstance(domain, Iterable): tmp = (DiscreteDomain(domain).dimension == 1) elif isinstance(domain, EmptyDomain): tmp = (len(np.shape(domain)) == 1) else: raise TypeError( 'Domain does not support given type: {}'.format( domain.__class__.__name__)) if tmp: return super(DiscreteNet, cls).__new__(DiscreteCurve) elif hint is not None: return super(DiscreteNet, cls).__new__(DiscreteIterativeNet) else: return super(DiscreteNet, cls).__new__(DiscreteRecursiveNet) def __init__(self, fct, domain, name='DiscreteNet', traverser=None, init=None): if not isinstance(domain, Domain): domain = DiscreteDomain(domain) super().__init__(fct, domain, name=name) self._init = init self._data = {} @property def traverser(self): """ Get the traverser of the Domain. Returns ------- ddg.datastructures.nets.traverser.Traverser """ return self.domain.traverser def __iter__(self): """ Iterate over the discrete net using the set traverser. Returns ------- Generator """ for idx in self.traverser: yield self[idx] def _get(self, idx): """ Retrive or generate value of the net at `idx`. Parameters ---------- idx : tuple Index of the net point. Returns ------- object Generated object at the given net point. """ raise NotImplementedError def __getitem__(self, idx): try: if isinstance(idx, DiscreteDomain): return (self[i] for i in idx.traverser) if len(idx) == self.dimension: return self._get(idx) elif len(idx) > self.dimension: msg = 'Too many indices for {} dimensional DiscreteNet.' msg = msg.format(self.dimension) raise IndexError(msg) elif len(idx) == 0: raise IndexError('At least one index or slice needed.') else: return self[tuple(idx) + tuple(slice(None) for _ in range(self.dimension-len(idx)))] except TypeError as e: if isinstance(idx, int) or isinstance(idx, slice): return self[(idx,) + tuple(slice(None) for _ in range(self.dimension-1))] else: error_message = ' '.join( ['DiscreteNet indices must be DiscreteDomains,', 'iterable, integers or slices, not {}.']).format( idx.__class__.__name__) raise TypeError(error_message) def __call__(self, *args): return self.transformation(self.fct(*args)) # TODO: reintroduce domain check with tolerances? # if args in self.domain: # return self.transformation(self.fct(*args)) # else: # raise ValueError('Argument not in the domain of the net')
[docs] def evaluate(self): return list(self[self.domain])
[docs]class DiscreteRecursiveNet(DiscreteNet): """Recursive implementation of DiscreteNet. """ def __new__(cls, *args, **kwargs): return super(DiscreteNet, cls).__new__(cls) def _get(self, idx): """ Retrive or generate value of the net at `idx`. Parameters ---------- idx : tuple Index of the net point. Returns ------- object Generated object at the given net point. """ gens, all_int = [], True for el in idx: try: if isinstance(el, int): gens.append((el,)) elif el.stop: gens.append(range(*el.indices(el.stop))) all_int = False else: gens.append(itts.count(el.start, el.step)) all_int = False except AttributeError: raise TypeError('Elements of indices must be integers or\ slices, not {}.'.format(el.__class__.__name__)) if all_int: try: return self.transformation(self._data[idx]) except KeyError: self._data[idx] = self.fct(*idx) return self.transformation(self._data[idx]) else: return (self[i] for i in itts.product(*gens))
[docs]class DiscreteIterativeNet(DiscreteNet): """Iterative implementation of DiscreteNet. """ def __new__(cls, *args, **kwargs): return super(DiscreteNet, cls).__new__(cls) def __init__(self, *args, hint=None, **kwargs): self._hint = hint super().__init__(*args, **kwargs) def _get(self, idx): """ Retrive or generate value of the net at `idx`. Parameters ---------- idx : tuple Index of the net point. Returns ------- object Generated object at the given net point. """ gens, all_int = [], True for el in idx: try: if isinstance(el, int): gens.append((el,)) elif el.stop: gens.append(range(*el.indices(el.stop))) all_int = False else: gens.append(itts.count(el.start, el.step)) all_int = False except AttributeError: raise TypeError('Elements of indices must be integers or\ slices, not {}.'.format(el.__class__.__name__)) if all_int: try: return self.transformation(self._data[idx]) except KeyError: stack = [[idx, False]] while stack: if stack[-1][0] not in self._data: if stack[-1][1]: self._data[stack[-1][0]] = self.fct(*stack[-1][0]) else: stack[-1][1] = True stack.extend([[el, False] for el in self._hint(stack[-1][0])]) else: stack.pop() return self.transformation(self._data[idx]) else: return (self[i] for i in itts.product(*gens))
[docs]class DiscreteCurve(DiscreteRecursiveNet): def __init__(self, fct, domain, *args, name='DiscreteCurve'): if not isinstance(domain, Domain): domain = DiscreteInterval(domain) super().__init__(fct, domain, *args, name=name)
[docs]class SamplingNet(DiscreteRecursiveNet): """Net used in the sampling process. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._sfct = self._fct() #TODO: Better handling if not changed @property def _samplingfcts(self): return self._sfct def _samplingfct(self, *args, **kwargs): return [f(x) for f,x in zip(self._samplingfcts, args)] @property def fct(self): return lambda *a: self._samplingfct(*a) def __getitem__(self, key): return super().__getitem__(key)
[docs]class NetCollection(Sized, Iterable, Container, Transformable): """Collection of Nets Parameters ---------- nets : List of nets Nets to be contained in the collection. name : str (default='NetCollection') Name of the collection. Attributes ---------- name : string Name of the collection dimension : int Dimension of the (first) net inside the collection """ def __init__(self, nets=[], name='NetCollection'): self.nets = nets self.name = name self._added_nets = [] super().__init__() def __contains__(self, net): return net in self._nets def __iter__(self): return iter(self._nets) def __len__(self): return len(self._nets) def __getitem__(self, i): return self._nets[i]
[docs] def add(self, net): """Add a net to the collection. Parameters ---------- net : Net """ # We maintain a list of added nets and concatenate it with _data in # _nets. self._added_nets.append(net)
@property def dimension(self): """Dimension of the collection. Only works if all nets in the collection have the same dimension. Returns ------- int Raises ------ AttributeError * If collection is empty * If nets in collection have different dimensions. """ dims = {n.dimension for n in self._nets} if len(dims) == 1: return dims.pop() elif len(dims) > 1: raise AttributeError( f'Dimension of NetCollection is not well-defined because it ' f'contains nets of dimensions {dims}.' ) elif len(dims) == 0: raise AttributeError('NetCollection is empty.') @property def _nets(self): """Get collection as list. Returns ------- list of Net """ if not hasattr(self, '_data'): self._data = self.nets + self._added_nets # reapply transformations for trafo in self._trafos: for net in self._data: net.push_transformation(trafo) return self._data
[docs] def push_transformation(self, f): """ Add a transformation to the trafo-stack of the net. Returns ------- None """ for net in self: net.push_transformation(f) super().push_transformation(f)
[docs] def pop_transformation(self): """ Pop transformation from the trafo-stack of the net. Returns ------- function """ for net in self: net.pop_transformation() return super().pop_transformation()