Source code for ddg.datastructures.nets.net

import itertools as itts
from collections.abc import Callable, Container, Iterable, Sized

import numpy as np

from ddg.datastructures.nets.domain import (
    DiscreteDomain,
    DiscreteInterval,
    Domain,
    EmptyDomain,
    SmoothDomain,
    SmoothInterval,
    SmoothRectangularDomain,
)


[docs]class Net(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]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.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.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.fct(*args)
[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: 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.fct(*args)
[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._data[idx] except KeyError: self._data[idx] = self.fct(*idx) return 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 NetCollection(Sized, Iterable, Container): """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( "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 return self._data