diff --git a/docs/build/html/.doctrees/classes/spatialpy.core.doctree b/docs/build/html/.doctrees/classes/spatialpy.core.doctree new file mode 100644 index 00000000..372e3575 Binary files /dev/null and b/docs/build/html/.doctrees/classes/spatialpy.core.doctree differ diff --git a/docs/build/html/.doctrees/classes/spatialpy.doctree b/docs/build/html/.doctrees/classes/spatialpy.doctree index 57fb53dd..9c5f3485 100644 Binary files a/docs/build/html/.doctrees/classes/spatialpy.doctree and b/docs/build/html/.doctrees/classes/spatialpy.doctree differ diff --git a/docs/build/html/.doctrees/classes/spatialpy.solvers.doctree b/docs/build/html/.doctrees/classes/spatialpy.solvers.doctree new file mode 100644 index 00000000..a972d000 Binary files /dev/null and b/docs/build/html/.doctrees/classes/spatialpy.solvers.doctree differ diff --git a/docs/build/html/.doctrees/classes/spatialpy.stochss.doctree b/docs/build/html/.doctrees/classes/spatialpy.stochss.doctree index 89e4bdca..f269045d 100644 Binary files a/docs/build/html/.doctrees/classes/spatialpy.stochss.doctree and b/docs/build/html/.doctrees/classes/spatialpy.stochss.doctree differ diff --git a/docs/build/html/.doctrees/environment.pickle b/docs/build/html/.doctrees/environment.pickle index fd68ef46..fa46e008 100644 Binary files a/docs/build/html/.doctrees/environment.pickle and b/docs/build/html/.doctrees/environment.pickle differ diff --git a/docs/build/html/_modules/collections.html b/docs/build/html/_modules/collections.html new file mode 100644 index 00000000..29394292 --- /dev/null +++ b/docs/build/html/_modules/collections.html @@ -0,0 +1,1401 @@ + + + + + + + + collections — SpatialPy 0.5.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + +
+ +

Source code for collections

+'''This module implements specialized container datatypes providing
+alternatives to Python's general purpose built-in containers, dict,
+list, set, and tuple.
+
+* namedtuple   factory function for creating tuple subclasses with named fields
+* deque        list-like container with fast appends and pops on either end
+* ChainMap     dict-like class for creating a single view of multiple mappings
+* Counter      dict subclass for counting hashable objects
+* OrderedDict  dict subclass that remembers the order entries were added
+* defaultdict  dict subclass that calls a factory function to supply missing values
+* UserDict     wrapper around dictionary objects for easier dict subclassing
+* UserList     wrapper around list objects for easier list subclassing
+* UserString   wrapper around string objects for easier string subclassing
+
+'''
+
+__all__ = ['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList',
+            'UserString', 'Counter', 'OrderedDict', 'ChainMap']
+
+import _collections_abc
+from operator import itemgetter as _itemgetter, eq as _eq
+from keyword import iskeyword as _iskeyword
+import sys as _sys
+import heapq as _heapq
+from _weakref import proxy as _proxy
+from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
+from reprlib import recursive_repr as _recursive_repr
+
+try:
+    from _collections import deque
+except ImportError:
+    pass
+else:
+    _collections_abc.MutableSequence.register(deque)
+
+try:
+    from _collections import defaultdict
+except ImportError:
+    pass
+
+
+def __getattr__(name):
+    # For backwards compatibility, continue to make the collections ABCs
+    # through Python 3.6 available through the collections module.
+    # Note, no new collections ABCs were added in Python 3.7
+    if name in _collections_abc.__all__:
+        obj = getattr(_collections_abc, name)
+        import warnings
+        warnings.warn("Using or importing the ABCs from 'collections' instead "
+                      "of from 'collections.abc' is deprecated since Python 3.3, "
+                      "and in 3.10 it will stop working",
+                      DeprecationWarning, stacklevel=2)
+        globals()[name] = obj
+        return obj
+    raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
+
+################################################################################
+### OrderedDict
+################################################################################
+
+class _OrderedDictKeysView(_collections_abc.KeysView):
+
+    def __reversed__(self):
+        yield from reversed(self._mapping)
+
+class _OrderedDictItemsView(_collections_abc.ItemsView):
+
+    def __reversed__(self):
+        for key in reversed(self._mapping):
+            yield (key, self._mapping[key])
+
+class _OrderedDictValuesView(_collections_abc.ValuesView):
+
+    def __reversed__(self):
+        for key in reversed(self._mapping):
+            yield self._mapping[key]
+
+class _Link(object):
+    __slots__ = 'prev', 'next', 'key', '__weakref__'
+
+
[docs]class OrderedDict(dict): + 'Dictionary that remembers insertion order' + # An inherited dict maps keys to values. + # The inherited dict provides __getitem__, __len__, __contains__, and get. + # The remaining methods are order-aware. + # Big-O running times for all methods are the same as regular dictionaries. + + # The internal self.__map dict maps keys to links in a doubly linked list. + # The circular doubly linked list starts and ends with a sentinel element. + # The sentinel element never gets deleted (this simplifies the algorithm). + # The sentinel is in self.__hardroot with a weakref proxy in self.__root. + # The prev links are weakref proxies (to prevent circular references). + # Individual links are kept alive by the hard reference in self.__map. + # Those hard references disappear when a key is deleted from an OrderedDict. + + def __init__(self, other=(), /, **kwds): + '''Initialize an ordered dictionary. The signature is the same as + regular dictionaries. Keyword argument order is preserved. + ''' + try: + self.__root + except AttributeError: + self.__hardroot = _Link() + self.__root = root = _proxy(self.__hardroot) + root.prev = root.next = root + self.__map = {} + self.__update(other, **kwds) + + def __setitem__(self, key, value, + dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link): + 'od.__setitem__(i, y) <==> od[i]=y' + # Setting a new item creates a new link at the end of the linked list, + # and the inherited dictionary is updated with the new key/value pair. + if key not in self: + self.__map[key] = link = Link() + root = self.__root + last = root.prev + link.prev, link.next, link.key = last, root, key + last.next = link + root.prev = proxy(link) + dict_setitem(self, key, value) + + def __delitem__(self, key, dict_delitem=dict.__delitem__): + 'od.__delitem__(y) <==> del od[y]' + # Deleting an existing item uses self.__map to find the link which gets + # removed by updating the links in the predecessor and successor nodes. + dict_delitem(self, key) + link = self.__map.pop(key) + link_prev = link.prev + link_next = link.next + link_prev.next = link_next + link_next.prev = link_prev + link.prev = None + link.next = None + + def __iter__(self): + 'od.__iter__() <==> iter(od)' + # Traverse the linked list in order. + root = self.__root + curr = root.next + while curr is not root: + yield curr.key + curr = curr.next + + def __reversed__(self): + 'od.__reversed__() <==> reversed(od)' + # Traverse the linked list in reverse order. + root = self.__root + curr = root.prev + while curr is not root: + yield curr.key + curr = curr.prev + + def clear(self): + 'od.clear() -> None. Remove all items from od.' + root = self.__root + root.prev = root.next = root + self.__map.clear() + dict.clear(self) + + def popitem(self, last=True): + '''Remove and return a (key, value) pair from the dictionary. + + Pairs are returned in LIFO order if last is true or FIFO order if false. + ''' + if not self: + raise KeyError('dictionary is empty') + root = self.__root + if last: + link = root.prev + link_prev = link.prev + link_prev.next = root + root.prev = link_prev + else: + link = root.next + link_next = link.next + root.next = link_next + link_next.prev = root + key = link.key + del self.__map[key] + value = dict.pop(self, key) + return key, value + + def move_to_end(self, key, last=True): + '''Move an existing element to the end (or beginning if last is false). + + Raise KeyError if the element does not exist. + ''' + link = self.__map[key] + link_prev = link.prev + link_next = link.next + soft_link = link_next.prev + link_prev.next = link_next + link_next.prev = link_prev + root = self.__root + if last: + last = root.prev + link.prev = last + link.next = root + root.prev = soft_link + last.next = link + else: + first = root.next + link.prev = root + link.next = first + first.prev = soft_link + root.next = link + + def __sizeof__(self): + sizeof = _sys.getsizeof + n = len(self) + 1 # number of links including root + size = sizeof(self.__dict__) # instance dictionary + size += sizeof(self.__map) * 2 # internal dict and inherited dict + size += sizeof(self.__hardroot) * n # link objects + size += sizeof(self.__root) * n # proxy objects + return size + + update = __update = _collections_abc.MutableMapping.update + + def keys(self): + "D.keys() -> a set-like object providing a view on D's keys" + return _OrderedDictKeysView(self) + + def items(self): + "D.items() -> a set-like object providing a view on D's items" + return _OrderedDictItemsView(self) + + def values(self): + "D.values() -> an object providing a view on D's values" + return _OrderedDictValuesView(self) + + __ne__ = _collections_abc.MutableMapping.__ne__ + + __marker = object() + + def pop(self, key, default=__marker): + '''od.pop(k[,d]) -> v, remove specified key and return the corresponding + value. If key is not found, d is returned if given, otherwise KeyError + is raised. + + ''' + if key in self: + result = self[key] + del self[key] + return result + if default is self.__marker: + raise KeyError(key) + return default + + def setdefault(self, key, default=None): + '''Insert key with a value of default if key is not in the dictionary. + + Return the value for key if key is in the dictionary, else default. + ''' + if key in self: + return self[key] + self[key] = default + return default + + @_recursive_repr() + def __repr__(self): + 'od.__repr__() <==> repr(od)' + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, list(self.items())) + + def __reduce__(self): + 'Return state information for pickling' + inst_dict = vars(self).copy() + for k in vars(OrderedDict()): + inst_dict.pop(k, None) + return self.__class__, (), inst_dict or None, None, iter(self.items()) + + def copy(self): + 'od.copy() -> a shallow copy of od' + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + '''Create a new ordered dictionary with keys from iterable and values set to value. + ''' + self = cls() + for key in iterable: + self[key] = value + return self + + def __eq__(self, other): + '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive + while comparison to a regular mapping is order-insensitive. + + ''' + if isinstance(other, OrderedDict): + return dict.__eq__(self, other) and all(map(_eq, self, other)) + return dict.__eq__(self, other)
+ + +try: + from _collections import OrderedDict +except ImportError: + # Leave the pure Python version in place. + pass + + +################################################################################ +### namedtuple +################################################################################ + +try: + from _collections import _tuplegetter +except ImportError: + _tuplegetter = lambda index, doc: property(_itemgetter(index), doc=doc) + +def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None): + """Returns a new subclass of tuple with named fields. + + >>> Point = namedtuple('Point', ['x', 'y']) + >>> Point.__doc__ # docstring for the new class + 'Point(x, y)' + >>> p = Point(11, y=22) # instantiate with positional args or keywords + >>> p[0] + p[1] # indexable like a plain tuple + 33 + >>> x, y = p # unpack like a regular tuple + >>> x, y + (11, 22) + >>> p.x + p.y # fields also accessible by name + 33 + >>> d = p._asdict() # convert to a dictionary + >>> d['x'] + 11 + >>> Point(**d) # convert from a dictionary + Point(x=11, y=22) + >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields + Point(x=100, y=22) + + """ + + # Validate the field names. At the user's option, either generate an error + # message or automatically replace the field name with a valid name. + if isinstance(field_names, str): + field_names = field_names.replace(',', ' ').split() + field_names = list(map(str, field_names)) + typename = _sys.intern(str(typename)) + + if rename: + seen = set() + for index, name in enumerate(field_names): + if (not name.isidentifier() + or _iskeyword(name) + or name.startswith('_') + or name in seen): + field_names[index] = f'_{index}' + seen.add(name) + + for name in [typename] + field_names: + if type(name) is not str: + raise TypeError('Type names and field names must be strings') + if not name.isidentifier(): + raise ValueError('Type names and field names must be valid ' + f'identifiers: {name!r}') + if _iskeyword(name): + raise ValueError('Type names and field names cannot be a ' + f'keyword: {name!r}') + + seen = set() + for name in field_names: + if name.startswith('_') and not rename: + raise ValueError('Field names cannot start with an underscore: ' + f'{name!r}') + if name in seen: + raise ValueError(f'Encountered duplicate field name: {name!r}') + seen.add(name) + + field_defaults = {} + if defaults is not None: + defaults = tuple(defaults) + if len(defaults) > len(field_names): + raise TypeError('Got more default values than field names') + field_defaults = dict(reversed(list(zip(reversed(field_names), + reversed(defaults))))) + + # Variables used in the methods and docstrings + field_names = tuple(map(_sys.intern, field_names)) + num_fields = len(field_names) + arg_list = repr(field_names).replace("'", "")[1:-1] + repr_fmt = '(' + ', '.join(f'{name}=%r' for name in field_names) + ')' + tuple_new = tuple.__new__ + _dict, _tuple, _len, _map, _zip = dict, tuple, len, map, zip + + # Create all the named tuple methods to be added to the class namespace + + s = f'def __new__(_cls, {arg_list}): return _tuple_new(_cls, ({arg_list}))' + namespace = {'_tuple_new': tuple_new, '__name__': f'namedtuple_{typename}'} + # Note: exec() has the side-effect of interning the field names + exec(s, namespace) + __new__ = namespace['__new__'] + __new__.__doc__ = f'Create new instance of {typename}({arg_list})' + if defaults is not None: + __new__.__defaults__ = defaults + + @classmethod + def _make(cls, iterable): + result = tuple_new(cls, iterable) + if _len(result) != num_fields: + raise TypeError(f'Expected {num_fields} arguments, got {len(result)}') + return result + + _make.__func__.__doc__ = (f'Make a new {typename} object from a sequence ' + 'or iterable') + + def _replace(self, /, **kwds): + result = self._make(_map(kwds.pop, field_names, self)) + if kwds: + raise ValueError(f'Got unexpected field names: {list(kwds)!r}') + return result + + _replace.__doc__ = (f'Return a new {typename} object replacing specified ' + 'fields with new values') + + def __repr__(self): + 'Return a nicely formatted representation string' + return self.__class__.__name__ + repr_fmt % self + + def _asdict(self): + 'Return a new dict which maps field names to their values.' + return _dict(_zip(self._fields, self)) + + def __getnewargs__(self): + 'Return self as a plain tuple. Used by copy and pickle.' + return _tuple(self) + + # Modify function metadata to help with introspection and debugging + for method in (__new__, _make.__func__, _replace, + __repr__, _asdict, __getnewargs__): + method.__qualname__ = f'{typename}.{method.__name__}' + + # Build-up the class namespace dictionary + # and use type() to build the result class + class_namespace = { + '__doc__': f'{typename}({arg_list})', + '__slots__': (), + '_fields': field_names, + '_field_defaults': field_defaults, + # alternate spelling for backward compatibility + '_fields_defaults': field_defaults, + '__new__': __new__, + '_make': _make, + '_replace': _replace, + '__repr__': __repr__, + '_asdict': _asdict, + '__getnewargs__': __getnewargs__, + } + for index, name in enumerate(field_names): + doc = _sys.intern(f'Alias for field number {index}') + class_namespace[name] = _tuplegetter(index, doc) + + result = type(typename, (tuple,), class_namespace) + + # For pickling to work, the __module__ variable needs to be set to the frame + # where the named tuple is created. Bypass this step in environments where + # sys._getframe is not defined (Jython for example) or sys._getframe is not + # defined for arguments greater than 0 (IronPython), or where the user has + # specified a particular module. + if module is None: + try: + module = _sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + if module is not None: + result.__module__ = module + + return result + + +######################################################################## +### Counter +######################################################################## + +def _count_elements(mapping, iterable): + 'Tally elements from the iterable.' + mapping_get = mapping.get + for elem in iterable: + mapping[elem] = mapping_get(elem, 0) + 1 + +try: # Load C helper function if available + from _collections import _count_elements +except ImportError: + pass + +class Counter(dict): + '''Dict subclass for counting hashable items. Sometimes called a bag + or multiset. Elements are stored as dictionary keys and their counts + are stored as dictionary values. + + >>> c = Counter('abcdeabcdabcaba') # count elements from a string + + >>> c.most_common(3) # three most common elements + [('a', 5), ('b', 4), ('c', 3)] + >>> sorted(c) # list all unique elements + ['a', 'b', 'c', 'd', 'e'] + >>> ''.join(sorted(c.elements())) # list elements with repetitions + 'aaaaabbbbcccdde' + >>> sum(c.values()) # total of all counts + 15 + + >>> c['a'] # count of letter 'a' + 5 + >>> for elem in 'shazam': # update counts from an iterable + ... c[elem] += 1 # by adding 1 to each element's count + >>> c['a'] # now there are seven 'a' + 7 + >>> del c['b'] # remove all 'b' + >>> c['b'] # now there are zero 'b' + 0 + + >>> d = Counter('simsalabim') # make another counter + >>> c.update(d) # add in the second counter + >>> c['a'] # now there are nine 'a' + 9 + + >>> c.clear() # empty the counter + >>> c + Counter() + + Note: If a count is set to zero or reduced to zero, it will remain + in the counter until the entry is deleted or the counter is cleared: + + >>> c = Counter('aaabbc') + >>> c['b'] -= 2 # reduce the count of 'b' by two + >>> c.most_common() # 'b' is still in, but its count is zero + [('a', 3), ('c', 1), ('b', 0)] + + ''' + # References: + # http://en.wikipedia.org/wiki/Multiset + # http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html + # http://www.demo2s.com/Tutorial/Cpp/0380__set-multiset/Catalog0380__set-multiset.htm + # http://code.activestate.com/recipes/259174/ + # Knuth, TAOCP Vol. II section 4.6.3 + + def __init__(self, iterable=None, /, **kwds): + '''Create a new, empty Counter object. And if given, count elements + from an input iterable. Or, initialize the count from another mapping + of elements to their counts. + + >>> c = Counter() # a new, empty counter + >>> c = Counter('gallahad') # a new counter from an iterable + >>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping + >>> c = Counter(a=4, b=2) # a new counter from keyword args + + ''' + super(Counter, self).__init__() + self.update(iterable, **kwds) + + def __missing__(self, key): + 'The count of elements not in the Counter is zero.' + # Needed so that self[missing_item] does not raise KeyError + return 0 + + def most_common(self, n=None): + '''List the n most common elements and their counts from the most + common to the least. If n is None, then list all element counts. + + >>> Counter('abracadabra').most_common(3) + [('a', 5), ('b', 2), ('r', 2)] + + ''' + # Emulate Bag.sortedByCount from Smalltalk + if n is None: + return sorted(self.items(), key=_itemgetter(1), reverse=True) + return _heapq.nlargest(n, self.items(), key=_itemgetter(1)) + + def elements(self): + '''Iterator over elements repeating each as many times as its count. + + >>> c = Counter('ABCABC') + >>> sorted(c.elements()) + ['A', 'A', 'B', 'B', 'C', 'C'] + + # Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1 + >>> prime_factors = Counter({2: 2, 3: 3, 17: 1}) + >>> product = 1 + >>> for factor in prime_factors.elements(): # loop over factors + ... product *= factor # and multiply them + >>> product + 1836 + + Note, if an element's count has been set to zero or is a negative + number, elements() will ignore it. + + ''' + # Emulate Bag.do from Smalltalk and Multiset.begin from C++. + return _chain.from_iterable(_starmap(_repeat, self.items())) + + # Override dict methods where necessary + + @classmethod + def fromkeys(cls, iterable, v=None): + # There is no equivalent method for counters because the semantics + # would be ambiguous in cases such as Counter.fromkeys('aaabbc', v=2). + # Initializing counters to zero values isn't necessary because zero + # is already the default value for counter lookups. Initializing + # to one is easily accomplished with Counter(set(iterable)). For + # more exotic cases, create a dictionary first using a dictionary + # comprehension or dict.fromkeys(). + raise NotImplementedError( + 'Counter.fromkeys() is undefined. Use Counter(iterable) instead.') + + def update(self, iterable=None, /, **kwds): + '''Like dict.update() but add counts instead of replacing them. + + Source can be an iterable, a dictionary, or another Counter instance. + + >>> c = Counter('which') + >>> c.update('witch') # add elements from another iterable + >>> d = Counter('watch') + >>> c.update(d) # add elements from another counter + >>> c['h'] # four 'h' in which, witch, and watch + 4 + + ''' + # The regular dict.update() operation makes no sense here because the + # replace behavior results in the some of original untouched counts + # being mixed-in with all of the other counts for a mismash that + # doesn't have a straight-forward interpretation in most counting + # contexts. Instead, we implement straight-addition. Both the inputs + # and outputs are allowed to contain zero and negative counts. + + if iterable is not None: + if isinstance(iterable, _collections_abc.Mapping): + if self: + self_get = self.get + for elem, count in iterable.items(): + self[elem] = count + self_get(elem, 0) + else: + super(Counter, self).update(iterable) # fast path when counter is empty + else: + _count_elements(self, iterable) + if kwds: + self.update(kwds) + + def subtract(self, iterable=None, /, **kwds): + '''Like dict.update() but subtracts counts instead of replacing them. + Counts can be reduced below zero. Both the inputs and outputs are + allowed to contain zero and negative counts. + + Source can be an iterable, a dictionary, or another Counter instance. + + >>> c = Counter('which') + >>> c.subtract('witch') # subtract elements from another iterable + >>> c.subtract(Counter('watch')) # subtract elements from another counter + >>> c['h'] # 2 in which, minus 1 in witch, minus 1 in watch + 0 + >>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch + -1 + + ''' + if iterable is not None: + self_get = self.get + if isinstance(iterable, _collections_abc.Mapping): + for elem, count in iterable.items(): + self[elem] = self_get(elem, 0) - count + else: + for elem in iterable: + self[elem] = self_get(elem, 0) - 1 + if kwds: + self.subtract(kwds) + + def copy(self): + 'Return a shallow copy.' + return self.__class__(self) + + def __reduce__(self): + return self.__class__, (dict(self),) + + def __delitem__(self, elem): + 'Like dict.__delitem__() but does not raise KeyError for missing values.' + if elem in self: + super().__delitem__(elem) + + def __repr__(self): + if not self: + return '%s()' % self.__class__.__name__ + try: + items = ', '.join(map('%r: %r'.__mod__, self.most_common())) + return '%s({%s})' % (self.__class__.__name__, items) + except TypeError: + # handle case where values are not orderable + return '{0}({1!r})'.format(self.__class__.__name__, dict(self)) + + # Multiset-style mathematical operations discussed in: + # Knuth TAOCP Volume II section 4.6.3 exercise 19 + # and at http://en.wikipedia.org/wiki/Multiset + # + # Outputs guaranteed to only include positive counts. + # + # To strip negative and zero counts, add-in an empty counter: + # c += Counter() + # + # Rich comparison operators for multiset subset and superset tests + # are deliberately omitted due to semantic conflicts with the + # existing inherited dict equality method. Subset and superset + # semantics ignore zero counts and require that p≤q ∧ p≥q → p=q; + # however, that would not be the case for p=Counter(a=1, b=0) + # and q=Counter(a=1) where the dictionaries are not equal. + + def __add__(self, other): + '''Add counts from two counters. + + >>> Counter('abbb') + Counter('bcc') + Counter({'b': 4, 'c': 2, 'a': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + result = Counter() + for elem, count in self.items(): + newcount = count + other[elem] + if newcount > 0: + result[elem] = newcount + for elem, count in other.items(): + if elem not in self and count > 0: + result[elem] = count + return result + + def __sub__(self, other): + ''' Subtract count, but keep only results with positive counts. + + >>> Counter('abbbc') - Counter('bccd') + Counter({'b': 2, 'a': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + result = Counter() + for elem, count in self.items(): + newcount = count - other[elem] + if newcount > 0: + result[elem] = newcount + for elem, count in other.items(): + if elem not in self and count < 0: + result[elem] = 0 - count + return result + + def __or__(self, other): + '''Union is the maximum of value in either of the input counters. + + >>> Counter('abbb') | Counter('bcc') + Counter({'b': 3, 'c': 2, 'a': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + result = Counter() + for elem, count in self.items(): + other_count = other[elem] + newcount = other_count if count < other_count else count + if newcount > 0: + result[elem] = newcount + for elem, count in other.items(): + if elem not in self and count > 0: + result[elem] = count + return result + + def __and__(self, other): + ''' Intersection is the minimum of corresponding counts. + + >>> Counter('abbb') & Counter('bcc') + Counter({'b': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + result = Counter() + for elem, count in self.items(): + other_count = other[elem] + newcount = count if count < other_count else other_count + if newcount > 0: + result[elem] = newcount + return result + + def __pos__(self): + 'Adds an empty counter, effectively stripping negative and zero counts' + result = Counter() + for elem, count in self.items(): + if count > 0: + result[elem] = count + return result + + def __neg__(self): + '''Subtracts from an empty counter. Strips positive and zero counts, + and flips the sign on negative counts. + + ''' + result = Counter() + for elem, count in self.items(): + if count < 0: + result[elem] = 0 - count + return result + + def _keep_positive(self): + '''Internal method to strip elements with a negative or zero count''' + nonpositive = [elem for elem, count in self.items() if not count > 0] + for elem in nonpositive: + del self[elem] + return self + + def __iadd__(self, other): + '''Inplace add from another counter, keeping only positive counts. + + >>> c = Counter('abbb') + >>> c += Counter('bcc') + >>> c + Counter({'b': 4, 'c': 2, 'a': 1}) + + ''' + for elem, count in other.items(): + self[elem] += count + return self._keep_positive() + + def __isub__(self, other): + '''Inplace subtract counter, but keep only results with positive counts. + + >>> c = Counter('abbbc') + >>> c -= Counter('bccd') + >>> c + Counter({'b': 2, 'a': 1}) + + ''' + for elem, count in other.items(): + self[elem] -= count + return self._keep_positive() + + def __ior__(self, other): + '''Inplace union is the maximum of value from either counter. + + >>> c = Counter('abbb') + >>> c |= Counter('bcc') + >>> c + Counter({'b': 3, 'c': 2, 'a': 1}) + + ''' + for elem, other_count in other.items(): + count = self[elem] + if other_count > count: + self[elem] = other_count + return self._keep_positive() + + def __iand__(self, other): + '''Inplace intersection is the minimum of corresponding counts. + + >>> c = Counter('abbb') + >>> c &= Counter('bcc') + >>> c + Counter({'b': 1}) + + ''' + for elem, count in self.items(): + other_count = other[elem] + if other_count < count: + self[elem] = other_count + return self._keep_positive() + + +######################################################################## +### ChainMap +######################################################################## + +class ChainMap(_collections_abc.MutableMapping): + ''' A ChainMap groups multiple dicts (or other mappings) together + to create a single, updateable view. + + The underlying mappings are stored in a list. That list is public and can + be accessed or updated using the *maps* attribute. There is no other + state. + + Lookups search the underlying mappings successively until a key is found. + In contrast, writes, updates, and deletions only operate on the first + mapping. + + ''' + + def __init__(self, *maps): + '''Initialize a ChainMap by setting *maps* to the given mappings. + If no mappings are provided, a single empty dictionary is used. + + ''' + self.maps = list(maps) or [{}] # always at least one map + + def __missing__(self, key): + raise KeyError(key) + + def __getitem__(self, key): + for mapping in self.maps: + try: + return mapping[key] # can't use 'key in mapping' with defaultdict + except KeyError: + pass + return self.__missing__(key) # support subclasses that define __missing__ + + def get(self, key, default=None): + return self[key] if key in self else default + + def __len__(self): + return len(set().union(*self.maps)) # reuses stored hash values if possible + + def __iter__(self): + d = {} + for mapping in reversed(self.maps): + d.update(mapping) # reuses stored hash values if possible + return iter(d) + + def __contains__(self, key): + return any(key in m for m in self.maps) + + def __bool__(self): + return any(self.maps) + + @_recursive_repr() + def __repr__(self): + return f'{self.__class__.__name__}({", ".join(map(repr, self.maps))})' + + @classmethod + def fromkeys(cls, iterable, *args): + 'Create a ChainMap with a single dict created from the iterable.' + return cls(dict.fromkeys(iterable, *args)) + + def copy(self): + 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' + return self.__class__(self.maps[0].copy(), *self.maps[1:]) + + __copy__ = copy + + def new_child(self, m=None): # like Django's Context.push() + '''New ChainMap with a new map followed by all previous maps. + If no map is provided, an empty dict is used. + ''' + if m is None: + m = {} + return self.__class__(m, *self.maps) + + @property + def parents(self): # like Django's Context.pop() + 'New ChainMap from maps[1:].' + return self.__class__(*self.maps[1:]) + + def __setitem__(self, key, value): + self.maps[0][key] = value + + def __delitem__(self, key): + try: + del self.maps[0][key] + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def popitem(self): + 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' + try: + return self.maps[0].popitem() + except KeyError: + raise KeyError('No keys found in the first mapping.') + + def pop(self, key, *args): + 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' + try: + return self.maps[0].pop(key, *args) + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def clear(self): + 'Clear maps[0], leaving maps[1:] intact.' + self.maps[0].clear() + + +################################################################################ +### UserDict +################################################################################ + +class UserDict(_collections_abc.MutableMapping): + + # Start by filling-out the abstract methods + def __init__(*args, **kwargs): + if not args: + raise TypeError("descriptor '__init__' of 'UserDict' object " + "needs an argument") + self, *args = args + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + if args: + dict = args[0] + elif 'dict' in kwargs: + dict = kwargs.pop('dict') + import warnings + warnings.warn("Passing 'dict' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + dict = None + self.data = {} + if dict is not None: + self.update(dict) + if kwargs: + self.update(kwargs) + __init__.__text_signature__ = '($self, dict=None, /, **kwargs)' + + def __len__(self): return len(self.data) + def __getitem__(self, key): + if key in self.data: + return self.data[key] + if hasattr(self.__class__, "__missing__"): + return self.__class__.__missing__(self, key) + raise KeyError(key) + def __setitem__(self, key, item): self.data[key] = item + def __delitem__(self, key): del self.data[key] + def __iter__(self): + return iter(self.data) + + # Modify __contains__ to work correctly when __missing__ is present + def __contains__(self, key): + return key in self.data + + # Now, add the methods in dicts but not in MutableMapping + def __repr__(self): return repr(self.data) + def __copy__(self): + inst = self.__class__.__new__(self.__class__) + inst.__dict__.update(self.__dict__) + # Create a copy and avoid triggering descriptors + inst.__dict__["data"] = self.__dict__["data"].copy() + return inst + + def copy(self): + if self.__class__ is UserDict: + return UserDict(self.data.copy()) + import copy + data = self.data + try: + self.data = {} + c = copy.copy(self) + finally: + self.data = data + c.update(self) + return c + + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + + +################################################################################ +### UserList +################################################################################ + +class UserList(_collections_abc.MutableSequence): + """A more or less complete user-defined wrapper around list objects.""" + def __init__(self, initlist=None): + self.data = [] + if initlist is not None: + # XXX should this accept an arbitrary sequence? + if type(initlist) == type(self.data): + self.data[:] = initlist + elif isinstance(initlist, UserList): + self.data[:] = initlist.data[:] + else: + self.data = list(initlist) + def __repr__(self): return repr(self.data) + def __lt__(self, other): return self.data < self.__cast(other) + def __le__(self, other): return self.data <= self.__cast(other) + def __eq__(self, other): return self.data == self.__cast(other) + def __gt__(self, other): return self.data > self.__cast(other) + def __ge__(self, other): return self.data >= self.__cast(other) + def __cast(self, other): + return other.data if isinstance(other, UserList) else other + def __contains__(self, item): return item in self.data + def __len__(self): return len(self.data) + def __getitem__(self, i): + if isinstance(i, slice): + return self.__class__(self.data[i]) + else: + return self.data[i] + def __setitem__(self, i, item): self.data[i] = item + def __delitem__(self, i): del self.data[i] + def __add__(self, other): + if isinstance(other, UserList): + return self.__class__(self.data + other.data) + elif isinstance(other, type(self.data)): + return self.__class__(self.data + other) + return self.__class__(self.data + list(other)) + def __radd__(self, other): + if isinstance(other, UserList): + return self.__class__(other.data + self.data) + elif isinstance(other, type(self.data)): + return self.__class__(other + self.data) + return self.__class__(list(other) + self.data) + def __iadd__(self, other): + if isinstance(other, UserList): + self.data += other.data + elif isinstance(other, type(self.data)): + self.data += other + else: + self.data += list(other) + return self + def __mul__(self, n): + return self.__class__(self.data*n) + __rmul__ = __mul__ + def __imul__(self, n): + self.data *= n + return self + def __copy__(self): + inst = self.__class__.__new__(self.__class__) + inst.__dict__.update(self.__dict__) + # Create a copy and avoid triggering descriptors + inst.__dict__["data"] = self.__dict__["data"][:] + return inst + def append(self, item): self.data.append(item) + def insert(self, i, item): self.data.insert(i, item) + def pop(self, i=-1): return self.data.pop(i) + def remove(self, item): self.data.remove(item) + def clear(self): self.data.clear() + def copy(self): return self.__class__(self) + def count(self, item): return self.data.count(item) + def index(self, item, *args): return self.data.index(item, *args) + def reverse(self): self.data.reverse() + def sort(self, /, *args, **kwds): self.data.sort(*args, **kwds) + def extend(self, other): + if isinstance(other, UserList): + self.data.extend(other.data) + else: + self.data.extend(other) + + + +################################################################################ +### UserString +################################################################################ + +class UserString(_collections_abc.Sequence): + def __init__(self, seq): + if isinstance(seq, str): + self.data = seq + elif isinstance(seq, UserString): + self.data = seq.data[:] + else: + self.data = str(seq) + def __str__(self): return str(self.data) + def __repr__(self): return repr(self.data) + def __int__(self): return int(self.data) + def __float__(self): return float(self.data) + def __complex__(self): return complex(self.data) + def __hash__(self): return hash(self.data) + def __getnewargs__(self): + return (self.data[:],) + + def __eq__(self, string): + if isinstance(string, UserString): + return self.data == string.data + return self.data == string + def __lt__(self, string): + if isinstance(string, UserString): + return self.data < string.data + return self.data < string + def __le__(self, string): + if isinstance(string, UserString): + return self.data <= string.data + return self.data <= string + def __gt__(self, string): + if isinstance(string, UserString): + return self.data > string.data + return self.data > string + def __ge__(self, string): + if isinstance(string, UserString): + return self.data >= string.data + return self.data >= string + + def __contains__(self, char): + if isinstance(char, UserString): + char = char.data + return char in self.data + + def __len__(self): return len(self.data) + def __getitem__(self, index): return self.__class__(self.data[index]) + def __add__(self, other): + if isinstance(other, UserString): + return self.__class__(self.data + other.data) + elif isinstance(other, str): + return self.__class__(self.data + other) + return self.__class__(self.data + str(other)) + def __radd__(self, other): + if isinstance(other, str): + return self.__class__(other + self.data) + return self.__class__(str(other) + self.data) + def __mul__(self, n): + return self.__class__(self.data*n) + __rmul__ = __mul__ + def __mod__(self, args): + return self.__class__(self.data % args) + def __rmod__(self, template): + return self.__class__(str(template) % self) + # the following methods are defined in alphabetical order: + def capitalize(self): return self.__class__(self.data.capitalize()) + def casefold(self): + return self.__class__(self.data.casefold()) + def center(self, width, *args): + return self.__class__(self.data.center(width, *args)) + def count(self, sub, start=0, end=_sys.maxsize): + if isinstance(sub, UserString): + sub = sub.data + return self.data.count(sub, start, end) + def encode(self, encoding='utf-8', errors='strict'): + encoding = 'utf-8' if encoding is None else encoding + errors = 'strict' if errors is None else errors + return self.data.encode(encoding, errors) + def endswith(self, suffix, start=0, end=_sys.maxsize): + return self.data.endswith(suffix, start, end) + def expandtabs(self, tabsize=8): + return self.__class__(self.data.expandtabs(tabsize)) + def find(self, sub, start=0, end=_sys.maxsize): + if isinstance(sub, UserString): + sub = sub.data + return self.data.find(sub, start, end) + def format(self, /, *args, **kwds): + return self.data.format(*args, **kwds) + def format_map(self, mapping): + return self.data.format_map(mapping) + def index(self, sub, start=0, end=_sys.maxsize): + return self.data.index(sub, start, end) + def isalpha(self): return self.data.isalpha() + def isalnum(self): return self.data.isalnum() + def isascii(self): return self.data.isascii() + def isdecimal(self): return self.data.isdecimal() + def isdigit(self): return self.data.isdigit() + def isidentifier(self): return self.data.isidentifier() + def islower(self): return self.data.islower() + def isnumeric(self): return self.data.isnumeric() + def isprintable(self): return self.data.isprintable() + def isspace(self): return self.data.isspace() + def istitle(self): return self.data.istitle() + def isupper(self): return self.data.isupper() + def join(self, seq): return self.data.join(seq) + def ljust(self, width, *args): + return self.__class__(self.data.ljust(width, *args)) + def lower(self): return self.__class__(self.data.lower()) + def lstrip(self, chars=None): return self.__class__(self.data.lstrip(chars)) + maketrans = str.maketrans + def partition(self, sep): + return self.data.partition(sep) + def replace(self, old, new, maxsplit=-1): + if isinstance(old, UserString): + old = old.data + if isinstance(new, UserString): + new = new.data + return self.__class__(self.data.replace(old, new, maxsplit)) + def rfind(self, sub, start=0, end=_sys.maxsize): + if isinstance(sub, UserString): + sub = sub.data + return self.data.rfind(sub, start, end) + def rindex(self, sub, start=0, end=_sys.maxsize): + return self.data.rindex(sub, start, end) + def rjust(self, width, *args): + return self.__class__(self.data.rjust(width, *args)) + def rpartition(self, sep): + return self.data.rpartition(sep) + def rstrip(self, chars=None): + return self.__class__(self.data.rstrip(chars)) + def split(self, sep=None, maxsplit=-1): + return self.data.split(sep, maxsplit) + def rsplit(self, sep=None, maxsplit=-1): + return self.data.rsplit(sep, maxsplit) + def splitlines(self, keepends=False): return self.data.splitlines(keepends) + def startswith(self, prefix, start=0, end=_sys.maxsize): + return self.data.startswith(prefix, start, end) + def strip(self, chars=None): return self.__class__(self.data.strip(chars)) + def swapcase(self): return self.__class__(self.data.swapcase()) + def title(self): return self.__class__(self.data.title()) + def translate(self, *args): + return self.__class__(self.data.translate(*args)) + def upper(self): return self.__class__(self.data.upper()) + def zfill(self, width): return self.__class__(self.data.zfill(width)) +
+ +
+ + +
+
+
+
+ + + + + Fork me on GitHub + + + + + + \ No newline at end of file diff --git a/docs/build/html/_modules/index.html b/docs/build/html/_modules/index.html index 0e4eee8e..aa973e67 100644 --- a/docs/build/html/_modules/index.html +++ b/docs/build/html/_modules/index.html @@ -85,16 +85,26 @@

Quick search

All modules for which code is available

-
diff --git a/docs/build/html/_modules/plotly/offline/offline.html b/docs/build/html/_modules/plotly/offline/offline.html new file mode 100644 index 00000000..fcb85e08 --- /dev/null +++ b/docs/build/html/_modules/plotly/offline/offline.html @@ -0,0 +1,962 @@ + + + + + + + + plotly.offline.offline — SpatialPy 0.5.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + +
+ +

Source code for plotly.offline.offline

+""" Plotly Offline
+    A module to use Plotly's graphing library with Python
+    without connecting to a public or private plotly enterprise
+    server.
+"""
+from __future__ import absolute_import
+
+import os
+import warnings
+import pkgutil
+import plotly
+import plotly.tools
+
+from plotly.optional_imports import get_module
+from plotly import tools
+from ._plotlyjs_version import __plotlyjs_version__
+
+
+__IMAGE_FORMATS = ["jpeg", "png", "webp", "svg"]
+
+
+def download_plotlyjs(download_url):
+    warnings.warn(
+        """
+        `download_plotlyjs` is deprecated and will be removed in the
+        next release. plotly.js is shipped with this module, it is no
+        longer necessary to download this bundle separately.
+    """,
+        DeprecationWarning,
+    )
+    pass
+
+
+def get_plotlyjs_version():
+    """
+    Returns the version of plotly.js that is bundled with plotly.py.
+
+    Returns
+    -------
+    str
+        Plotly.js version string
+    """
+    return __plotlyjs_version__
+
+
+def get_plotlyjs():
+    """
+    Return the contents of the minified plotly.js library as a string.
+
+    This may be useful when building standalone HTML reports.
+
+    Returns
+    -------
+    str
+        Contents of the minified plotly.js library as a string
+
+    Examples
+    --------
+    Here is an example of creating a standalone HTML report that contains
+    two plotly figures, each in their own div.  The include_plotlyjs argument
+    is set to False when creating the divs so that we don't include multiple
+    copies of the plotly.js library in the output.  Instead, a single copy
+    of plotly.js is included in a script tag in the html head element.
+
+    >>> import plotly.graph_objs as go
+    >>> from plotly.offline import plot, get_plotlyjs
+    >>> fig1 = go.Figure(data=[{'type': 'bar', 'y': [1, 3, 2]}],
+    ...                 layout={'height': 400})
+    >>> fig2 = go.Figure(data=[{'type': 'scatter', 'y': [1, 3, 2]}],
+    ...                  layout={'height': 400})
+    >>> div1 = plot(fig1, output_type='div', include_plotlyjs=False)
+    >>> div2 = plot(fig2, output_type='div', include_plotlyjs=False)
+
+    >>> html = '''
+    ... <html>
+    ...     <head>
+    ...         <script type="text/javascript">{plotlyjs}</script>
+    ...     </head>
+    ...     <body>
+    ...        {div1}
+    ...        {div2}
+    ...     </body>
+    ... </html>
+    ... '''.format(plotlyjs=get_plotlyjs(), div1=div1, div2=div2)
+
+    >>> with open('multi_plot.html', 'w') as f:
+    ...      f.write(html) # doctest: +SKIP
+    """
+    path = os.path.join("package_data", "plotly.min.js")
+    plotlyjs = pkgutil.get_data("plotly", path).decode("utf-8")
+    return plotlyjs
+
+
+def _build_resize_script(plotdivid, plotly_root="Plotly"):
+    resize_script = (
+        '<script type="text/javascript">'
+        'window.addEventListener("resize", function(){{'
+        'if (document.getElementById("{id}")) {{'
+        '{plotly_root}.Plots.resize(document.getElementById("{id}"));'
+        "}};}})"
+        "</script>"
+    ).format(plotly_root=plotly_root, id=plotdivid)
+    return resize_script
+
+
+def _build_mathjax_script(url):
+    return '<script src="{url}?config=TeX-AMS-MML_SVG"></script>'.format(url=url)
+
+
+def _get_jconfig(config=None):
+
+    configkeys = (
+        "staticPlot",
+        "plotlyServerURL",
+        "editable",
+        "edits",
+        "autosizable",
+        "responsive",
+        "queueLength",
+        "fillFrame",
+        "frameMargins",
+        "scrollZoom",
+        "doubleClick",
+        "showTips",
+        "showAxisDragHandles",
+        "showAxisRangeEntryBoxes",
+        "showLink",
+        "sendData",
+        "showSendToCloud",
+        "linkText",
+        "showSources",
+        "displayModeBar",
+        "modeBarButtonsToRemove",
+        "modeBarButtonsToAdd",
+        "modeBarButtons",
+        "toImageButtonOptions",
+        "displaylogo",
+        "watermark",
+        "plotGlPixelRatio",
+        "setBackground",
+        "topojsonURL",
+        "mapboxAccessToken",
+        "logging",
+        "globalTransforms",
+        "locale",
+        "locales",
+        "doubleClickDelay",
+    )
+
+    if config and isinstance(config, dict):
+        # Warn user on unrecognized config options.  We make this a warning
+        # rather than an error since we don't have code generation logic in
+        # place yet to guarantee that the config options in plotly.py are up
+        # to date
+        bad_config = [k for k in config if k not in configkeys]
+        if bad_config:
+            warnings.warn(
+                """
+Unrecognized config options supplied: {bad_config}""".format(
+                    bad_config=bad_config
+                )
+            )
+
+        clean_config = config
+    else:
+        clean_config = {}
+
+    plotly_platform_url = plotly.tools.get_config_plotly_server_url()
+
+    if not clean_config.get("plotlyServerURL", None):
+        clean_config["plotlyServerURL"] = plotly_platform_url
+
+    if (
+        plotly_platform_url != "https://plot.ly"
+        and clean_config.get("linkText", None) == "Export to plot.ly"
+    ):
+        link_domain = plotly_platform_url.replace("https://", "").replace("http://", "")
+        link_text = clean_config["linkText"].replace("plot.ly", link_domain)
+        clean_config["linkText"] = link_text
+
+    return clean_config
+
+
+# Build script to set global PlotlyConfig object. This must execute before
+# plotly.js is loaded.
+_window_plotly_config = """\
+<script type="text/javascript">\
+window.PlotlyConfig = {MathJaxConfig: 'local'};\
+</script>"""
+
+_mathjax_config = """\
+<script type="text/javascript">\
+if (window.MathJax) {MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}\
+</script>"""
+
+
+def get_image_download_script(caller):
+    """
+    This function will return a script that will download an image of a Plotly
+    plot.
+
+    Keyword Arguments:
+    caller ('plot', 'iplot') -- specifies which function made the call for the
+        download script. If `iplot`, then an extra condition is added into the
+        download script to ensure that download prompts aren't initiated on
+        page reloads.
+    """
+
+    if caller == "iplot":
+        check_start = "if(document.readyState == 'complete') {{"
+        check_end = "}}"
+    elif caller == "plot":
+        check_start = ""
+        check_end = ""
+    else:
+        raise ValueError("caller should only be one of `iplot` or `plot`")
+
+    return (
+        "function downloadimage(format, height, width,"
+        " filename) {{"
+        "var p = document.getElementById('{{plot_id}}');"
+        "Plotly.downloadImage(p, {{format: format, height: height, "
+        "width: width, filename: filename}});}};"
+        + check_start
+        + "downloadimage('{format}', {height}, {width}, "
+        "'{filename}');" + check_end
+    )
+
+
+def build_save_image_post_script(
+    image, image_filename, image_height, image_width, caller
+):
+    if image:
+        if image not in __IMAGE_FORMATS:
+            raise ValueError(
+                "The image parameter must be one of the "
+                "following: {}".format(__IMAGE_FORMATS)
+            )
+
+        script = get_image_download_script(caller)
+        post_script = script.format(
+            format=image,
+            width=image_width,
+            height=image_height,
+            filename=image_filename,
+        )
+    else:
+        post_script = None
+
+    return post_script
+
+
+
[docs]def init_notebook_mode(connected=False): + """ + Initialize plotly.js in the browser if it hasn't been loaded into the DOM + yet. This is an idempotent method and can and should be called from any + offline methods that require plotly.js to be loaded into the notebook dom. + + Keyword arguments: + + connected (default=False) -- If True, the plotly.js library will be loaded + from an online CDN. If False, the plotly.js library will be loaded locally + from the plotly python package + + Use `connected=True` if you want your notebooks to have smaller file sizes. + In the case where `connected=False`, the entirety of the plotly.js library + will be loaded into the notebook, which will result in a file-size increase + of a couple megabytes. Additionally, because the library will be downloaded + from the web, you and your viewers must be connected to the internet to be + able to view charts within this notebook. + + Use `connected=False` if you want you and your collaborators to be able to + create and view these charts regardless of the availability of an internet + connection. This is the default option since it is the most predictable. + Note that under this setting the library will be included inline inside + your notebook, resulting in much larger notebook sizes compared to the case + where `connected=True`. + """ + import plotly.io as pio + + ipython = get_module("IPython") + if not ipython: + raise ImportError("`iplot` can only run inside an IPython Notebook.") + + if connected: + pio.renderers.default = "plotly_mimetype+notebook_connected" + else: + pio.renderers.default = "plotly_mimetype+notebook" + + # Trigger immediate activation of notebook. This way the plotly.js + # library reference is available to the notebook immediately + pio.renderers._activate_pending_renderers()
+ + +
[docs]def iplot( + figure_or_data, + show_link=False, + link_text="Export to plot.ly", + validate=True, + image=None, + filename="plot_image", + image_width=800, + image_height=600, + config=None, + auto_play=True, + animation_opts=None, +): + """ + Draw plotly graphs inside an IPython or Jupyter notebook + + figure_or_data -- a plotly.graph_objs.Figure or plotly.graph_objs.Data or + dict or list that describes a Plotly graph. + See https://plot.ly/python/ for examples of + graph descriptions. + + Keyword arguments: + show_link (default=False) -- display a link in the bottom-right corner of + of the chart that will export the chart to + Plotly Cloud or Plotly Enterprise + link_text (default='Export to plot.ly') -- the text of export link + validate (default=True) -- validate that all of the keys in the figure + are valid? omit if your version of plotly.js + has become outdated with your version of + graph_reference.json or if you need to include + extra, unnecessary keys in your figure. + image (default=None |'png' |'jpeg' |'svg' |'webp') -- This parameter sets + the format of the image to be downloaded, if we choose to download an + image. This parameter has a default value of None indicating that no + image should be downloaded. Please note: for higher resolution images + and more export options, consider using plotly.io.write_image. See + https://plot.ly/python/static-image-export/ for more details. + filename (default='plot') -- Sets the name of the file your image + will be saved to. The extension should not be included. + image_height (default=600) -- Specifies the height of the image in `px`. + image_width (default=800) -- Specifies the width of the image in `px`. + config (default=None) -- Plot view options dictionary. Keyword arguments + `show_link` and `link_text` set the associated options in this + dictionary if it doesn't contain them already. + auto_play (default=True) -- Whether to automatically start the animation + sequence on page load, if the figure contains frames. Has no effect if + the figure does not contain frames. + animation_opts (default=None) -- Dict of custom animation parameters that + are used for the automatically started animation on page load. This + dict is passed to the function Plotly.animate in Plotly.js. See + https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js + for available options. Has no effect if the figure + does not contain frames, or auto_play is False. + + Example: + ``` + from plotly.offline import init_notebook_mode, iplot + init_notebook_mode() + iplot([{'x': [1, 2, 3], 'y': [5, 2, 7]}]) + # We can also download an image of the plot by setting the image to the + format you want. e.g. `image='png'` + iplot([{'x': [1, 2, 3], 'y': [5, 2, 7]}], image='png') + ``` + + animation_opts Example: + ``` + from plotly.offline import iplot + figure = {'data': [{'x': [0, 1], 'y': [0, 1]}], + 'layout': {'xaxis': {'range': [0, 5], 'autorange': False}, + 'yaxis': {'range': [0, 5], 'autorange': False}, + 'title': 'Start Title'}, + 'frames': [{'data': [{'x': [1, 2], 'y': [1, 2]}]}, + {'data': [{'x': [1, 4], 'y': [1, 4]}]}, + {'data': [{'x': [3, 4], 'y': [3, 4]}], + 'layout': {'title': 'End Title'}}]} + iplot(figure, animation_opts={'frame': {'duration': 1}}) + ``` + """ + import plotly.io as pio + + ipython = get_module("IPython") + if not ipython: + raise ImportError("`iplot` can only run inside an IPython Notebook.") + + config = dict(config) if config else {} + config.setdefault("showLink", show_link) + config.setdefault("linkText", link_text) + + # Get figure + figure = tools.return_figure_from_figure_or_data(figure_or_data, validate) + + # Handle image request + post_script = build_save_image_post_script( + image, filename, image_height, image_width, "iplot" + ) + + # Show figure + pio.show( + figure, + validate=validate, + config=config, + auto_play=auto_play, + post_script=post_script, + animation_opts=animation_opts, + )
+ + +def plot( + figure_or_data, + show_link=False, + link_text="Export to plot.ly", + validate=True, + output_type="file", + include_plotlyjs=True, + filename="temp-plot.html", + auto_open=True, + image=None, + image_filename="plot_image", + image_width=800, + image_height=600, + config=None, + include_mathjax=False, + auto_play=True, + animation_opts=None, +): + """ Create a plotly graph locally as an HTML document or string. + + Example: + ``` + from plotly.offline import plot + import plotly.graph_objs as go + + plot([go.Scatter(x=[1, 2, 3], y=[3, 2, 6])], filename='my-graph.html') + # We can also download an image of the plot by setting the image parameter + # to the image format we want + plot([go.Scatter(x=[1, 2, 3], y=[3, 2, 6])], filename='my-graph.html', + image='jpeg') + ``` + More examples below. + + figure_or_data -- a plotly.graph_objs.Figure or plotly.graph_objs.Data or + dict or list that describes a Plotly graph. + See https://plot.ly/python/ for examples of + graph descriptions. + + Keyword arguments: + show_link (default=False) -- display a link in the bottom-right corner of + of the chart that will export the chart to Plotly Cloud or + Plotly Enterprise + link_text (default='Export to plot.ly') -- the text of export link + validate (default=True) -- validate that all of the keys in the figure + are valid? omit if your version of plotly.js has become outdated + with your version of graph_reference.json or if you need to include + extra, unnecessary keys in your figure. + output_type ('file' | 'div' - default 'file') -- if 'file', then + the graph is saved as a standalone HTML file and `plot` + returns None. + If 'div', then `plot` returns a string that just contains the + HTML <div> that contains the graph and the script to generate the + graph. + Use 'file' if you want to save and view a single graph at a time + in a standalone HTML file. + Use 'div' if you are embedding these graphs in an HTML file with + other graphs or HTML markup, like a HTML report or an website. + include_plotlyjs (True | False | 'cdn' | 'directory' | path - default=True) + Specifies how the plotly.js library is included in the output html + file or div string. + + If True, a script tag containing the plotly.js source code (~3MB) + is included in the output. HTML files generated with this option are + fully self-contained and can be used offline. + + If 'cdn', a script tag that references the plotly.js CDN is included + in the output. HTML files generated with this option are about 3MB + smaller than those generated with include_plotlyjs=True, but they + require an active internet connection in order to load the plotly.js + library. + + If 'directory', a script tag is included that references an external + plotly.min.js bundle that is assumed to reside in the same + directory as the HTML file. If output_type='file' then the + plotly.min.js bundle is copied into the directory of the resulting + HTML file. If a file named plotly.min.js already exists in the output + directory then this file is left unmodified and no copy is performed. + HTML files generated with this option can be used offline, but they + require a copy of the plotly.min.js bundle in the same directory. + This option is useful when many figures will be saved as HTML files in + the same directory because the plotly.js source code will be included + only once per output directory, rather than once per output file. + + If a string that ends in '.js', a script tag is included that + references the specified path. This approach can be used to point + the resulting HTML file to an alternative CDN. + + If False, no script tag referencing plotly.js is included. This is + useful when output_type='div' and the resulting div string will be + placed inside an HTML document that already loads plotly.js. This + option is not advised when output_type='file' as it will result in + a non-functional html file. + filename (default='temp-plot.html') -- The local filename to save the + outputted chart to. If the filename already exists, it will be + overwritten. This argument only applies if `output_type` is 'file'. + auto_open (default=True) -- If True, open the saved file in a + web browser after saving. + This argument only applies if `output_type` is 'file'. + image (default=None |'png' |'jpeg' |'svg' |'webp') -- This parameter sets + the format of the image to be downloaded, if we choose to download an + image. This parameter has a default value of None indicating that no + image should be downloaded. Please note: for higher resolution images + and more export options, consider making requests to our image servers. + Type: `help(py.image)` for more details. + image_filename (default='plot_image') -- Sets the name of the file your + image will be saved to. The extension should not be included. + image_height (default=600) -- Specifies the height of the image in `px`. + image_width (default=800) -- Specifies the width of the image in `px`. + config (default=None) -- Plot view options dictionary. Keyword arguments + `show_link` and `link_text` set the associated options in this + dictionary if it doesn't contain them already. + include_mathjax (False | 'cdn' | path - default=False) -- + Specifies how the MathJax.js library is included in the output html + file or div string. MathJax is required in order to display labels + with LaTeX typesetting. + + If False, no script tag referencing MathJax.js will be included in the + output. HTML files generated with this option will not be able to + display LaTeX typesetting. + + If 'cdn', a script tag that references a MathJax CDN location will be + included in the output. HTML files generated with this option will be + able to display LaTeX typesetting as long as they have internet access. + + If a string that ends in '.js', a script tag is included that + references the specified path. This approach can be used to point the + resulting HTML file to an alternative CDN. + auto_play (default=True) -- Whether to automatically start the animation + sequence on page load if the figure contains frames. Has no effect if + the figure does not contain frames. + animation_opts (default=None) -- Dict of custom animation parameters that + are used for the automatically started animation on page load. This + dict is passed to the function Plotly.animate in Plotly.js. See + https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js + for available options. Has no effect if the figure + does not contain frames, or auto_play is False. + + Example: + ``` + from plotly.offline import plot + figure = {'data': [{'x': [0, 1], 'y': [0, 1]}], + 'layout': {'xaxis': {'range': [0, 5], 'autorange': False}, + 'yaxis': {'range': [0, 5], 'autorange': False}, + 'title': 'Start Title'}, + 'frames': [{'data': [{'x': [1, 2], 'y': [1, 2]}]}, + {'data': [{'x': [1, 4], 'y': [1, 4]}]}, + {'data': [{'x': [3, 4], 'y': [3, 4]}], + 'layout': {'title': 'End Title'}}]} + plot(figure, animation_opts={'frame': {'duration': 1}}) + ``` + """ + import plotly.io as pio + + # Output type + if output_type not in ["div", "file"]: + raise ValueError( + "`output_type` argument must be 'div' or 'file'. " + "You supplied `" + output_type + "``" + ) + if not filename.endswith(".html") and output_type == "file": + warnings.warn( + "Your filename `" + filename + "` didn't end with .html. " + "Adding .html to the end of your file." + ) + filename += ".html" + + # Config + config = dict(config) if config else {} + config.setdefault("showLink", show_link) + config.setdefault("linkText", link_text) + + figure = tools.return_figure_from_figure_or_data(figure_or_data, validate) + width = figure.get("layout", {}).get("width", "100%") + height = figure.get("layout", {}).get("height", "100%") + + if width == "100%" or height == "100%": + config.setdefault("responsive", True) + + # Handle image request + post_script = build_save_image_post_script( + image, image_filename, image_height, image_width, "plot" + ) + + if output_type == "file": + pio.write_html( + figure, + filename, + config=config, + auto_play=auto_play, + include_plotlyjs=include_plotlyjs, + include_mathjax=include_mathjax, + post_script=post_script, + full_html=True, + validate=validate, + animation_opts=animation_opts, + auto_open=auto_open, + ) + return filename + else: + return pio.to_html( + figure, + config=config, + auto_play=auto_play, + include_plotlyjs=include_plotlyjs, + include_mathjax=include_mathjax, + post_script=post_script, + full_html=False, + validate=validate, + animation_opts=animation_opts, + ) + + +def plot_mpl( + mpl_fig, + resize=False, + strip_style=False, + verbose=False, + show_link=False, + link_text="Export to plot.ly", + validate=True, + output_type="file", + include_plotlyjs=True, + filename="temp-plot.html", + auto_open=True, + image=None, + image_filename="plot_image", + image_height=600, + image_width=800, +): + """ + Convert a matplotlib figure to a Plotly graph stored locally as HTML. + + For more information on converting matplotlib visualizations to plotly + graphs, call help(plotly.tools.mpl_to_plotly) + + For more information on creating plotly charts locally as an HTML document + or string, call help(plotly.offline.plot) + + mpl_fig -- a matplotlib figure object to convert to a plotly graph + + Keyword arguments: + resize (default=False) -- allow plotly to choose the figure size. + strip_style (default=False) -- allow plotly to choose style options. + verbose (default=False) -- print message. + show_link (default=False) -- display a link in the bottom-right corner of + of the chart that will export the chart to Plotly Cloud or + Plotly Enterprise + link_text (default='Export to plot.ly') -- the text of export link + validate (default=True) -- validate that all of the keys in the figure + are valid? omit if your version of plotly.js has become outdated + with your version of graph_reference.json or if you need to include + extra, unnecessary keys in your figure. + output_type ('file' | 'div' - default 'file') -- if 'file', then + the graph is saved as a standalone HTML file and `plot` + returns None. + If 'div', then `plot` returns a string that just contains the + HTML <div> that contains the graph and the script to generate the + graph. + Use 'file' if you want to save and view a single graph at a time + in a standalone HTML file. + Use 'div' if you are embedding these graphs in an HTML file with + other graphs or HTML markup, like a HTML report or an website. + include_plotlyjs (default=True) -- If True, include the plotly.js + source code in the output file or string. + Set as False if your HTML file already contains a copy of the plotly.js + library. + filename (default='temp-plot.html') -- The local filename to save the + outputted chart to. If the filename already exists, it will be + overwritten. This argument only applies if `output_type` is 'file'. + auto_open (default=True) -- If True, open the saved file in a + web browser after saving. + This argument only applies if `output_type` is 'file'. + image (default=None |'png' |'jpeg' |'svg' |'webp') -- This parameter sets + the format of the image to be downloaded, if we choose to download an + image. This parameter has a default value of None indicating that no + image should be downloaded. + image_filename (default='plot_image') -- Sets the name of the file your + image will be saved to. The extension should not be included. + image_height (default=600) -- Specifies the height of the image in `px`. + image_width (default=800) -- Specifies the width of the image in `px`. + + Example: + ``` + from plotly.offline import init_notebook_mode, plot_mpl + import matplotlib.pyplot as plt + + init_notebook_mode() + + fig = plt.figure() + x = [10, 15, 20, 25, 30] + y = [100, 250, 200, 150, 300] + plt.plot(x, y, "o") + + plot_mpl(fig) + # If you want to to download an image of the figure as well + plot_mpl(fig, image='png') + ``` + """ + plotly_plot = plotly.tools.mpl_to_plotly(mpl_fig, resize, strip_style, verbose) + return plot( + plotly_plot, + show_link, + link_text, + validate, + output_type, + include_plotlyjs, + filename, + auto_open, + image=image, + image_filename=image_filename, + image_height=image_height, + image_width=image_width, + ) + + +def iplot_mpl( + mpl_fig, + resize=False, + strip_style=False, + verbose=False, + show_link=False, + link_text="Export to plot.ly", + validate=True, + image=None, + image_filename="plot_image", + image_height=600, + image_width=800, +): + """ + Convert a matplotlib figure to a plotly graph and plot inside an IPython + notebook without connecting to an external server. + + To save the chart to Plotly Cloud or Plotly Enterprise, use + `plotly.plotly.plot_mpl`. + + For more information on converting matplotlib visualizations to plotly + graphs call `help(plotly.tools.mpl_to_plotly)` + + For more information on plotting plotly charts offline in an Ipython + notebook call `help(plotly.offline.iplot)` + + mpl_fig -- a matplotlib.figure to convert to a plotly graph + + Keyword arguments: + resize (default=False) -- allow plotly to choose the figure size. + strip_style (default=False) -- allow plotly to choose style options. + verbose (default=False) -- print message. + show_link (default=False) -- display a link in the bottom-right corner of + of the chart that will export the chart to + Plotly Cloud or Plotly Enterprise + link_text (default='Export to plot.ly') -- the text of export link + validate (default=True) -- validate that all of the keys in the figure + are valid? omit if your version of plotly.js + has become outdated with your version of + graph_reference.json or if you need to include + extra, unnecessary keys in your figure. + image (default=None |'png' |'jpeg' |'svg' |'webp') -- This parameter sets + the format of the image to be downloaded, if we choose to download an + image. This parameter has a default value of None indicating that no + image should be downloaded. + image_filename (default='plot_image') -- Sets the name of the file your + image will be saved to. The extension should not be included. + image_height (default=600) -- Specifies the height of the image in `px`. + image_width (default=800) -- Specifies the width of the image in `px`. + + Example: + ``` + from plotly.offline import init_notebook_mode, iplot_mpl + import matplotlib.pyplot as plt + + fig = plt.figure() + x = [10, 15, 20, 25, 30] + y = [100, 250, 200, 150, 300] + plt.plot(x, y, "o") + + init_notebook_mode() + iplot_mpl(fig) + # and if you want to download an image of the figure as well + iplot_mpl(fig, image='jpeg') + ``` + """ + plotly_plot = plotly.tools.mpl_to_plotly(mpl_fig, resize, strip_style, verbose) + return iplot( + plotly_plot, + show_link, + link_text, + validate, + image=image, + filename=image_filename, + image_height=image_height, + image_width=image_width, + ) + + +def enable_mpl_offline( + resize=False, + strip_style=False, + verbose=False, + show_link=False, + link_text="Export to plot.ly", + validate=True, +): + """ + Convert mpl plots to locally hosted HTML documents. + + This function should be used with the inline matplotlib backend + that ships with IPython that can be enabled with `%pylab inline` + or `%matplotlib inline`. This works by adding an HTML formatter + for Figure objects; the existing SVG/PNG formatters will remain + enabled. + + (idea taken from `mpld3._display.enable_notebook`) + + Example: + ``` + from plotly.offline import enable_mpl_offline + import matplotlib.pyplot as plt + + enable_mpl_offline() + + fig = plt.figure() + x = [10, 15, 20, 25, 30] + y = [100, 250, 200, 150, 300] + plt.plot(x, y, "o") + fig + ``` + """ + init_notebook_mode() + ipython = get_module("IPython") + matplotlib = get_module("matplotlib") + + ip = ipython.core.getipython.get_ipython() + formatter = ip.display_formatter.formatters["text/html"] + formatter.for_type( + matplotlib.figure.Figure, + lambda fig: iplot_mpl( + fig, resize, strip_style, verbose, show_link, link_text, validate + ), + ) +
+ +
+ + +
+
+
+
+ + + + + Fork me on GitHub + + + + + + \ No newline at end of file diff --git a/docs/build/html/_modules/scipy/spatial/kdtree.html b/docs/build/html/_modules/scipy/spatial/kdtree.html new file mode 100644 index 00000000..690a83ca --- /dev/null +++ b/docs/build/html/_modules/scipy/spatial/kdtree.html @@ -0,0 +1,1121 @@ + + + + + + + + scipy.spatial.kdtree — SpatialPy 0.5.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + +
+ +

Source code for scipy.spatial.kdtree

+# Copyright Anne M. Archibald 2008
+# Released under the scipy license
+import numpy as np
+from heapq import heappush, heappop
+import scipy.sparse
+
+__all__ = ['minkowski_distance_p', 'minkowski_distance',
+           'distance_matrix',
+           'Rectangle', 'KDTree']
+
+
+def minkowski_distance_p(x, y, p=2):
+    """
+    Compute the pth power of the L**p distance between two arrays.
+
+    For efficiency, this function computes the L**p distance but does
+    not extract the pth root. If `p` is 1 or infinity, this is equal to
+    the actual L**p distance.
+
+    Parameters
+    ----------
+    x : (M, K) array_like
+        Input array.
+    y : (N, K) array_like
+        Input array.
+    p : float, 1 <= p <= infinity
+        Which Minkowski p-norm to use.
+
+    Examples
+    --------
+    >>> from scipy.spatial import minkowski_distance_p
+    >>> minkowski_distance_p([[0,0],[0,0]], [[1,1],[0,1]])
+    array([2, 1])
+
+    """
+    x = np.asarray(x)
+    y = np.asarray(y)
+
+    # Find smallest common datatype with float64 (return type of this function) - addresses #10262.
+    # Don't just cast to float64 for complex input case.
+    common_datatype = np.promote_types(np.promote_types(x.dtype, y.dtype), 'float64')
+
+    # Make sure x and y are NumPy arrays of correct datatype.
+    x = x.astype(common_datatype)
+    y = y.astype(common_datatype)
+
+    if p == np.inf:
+        return np.amax(np.abs(y-x), axis=-1)
+    elif p == 1:
+        return np.sum(np.abs(y-x), axis=-1)
+    else:
+        return np.sum(np.abs(y-x)**p, axis=-1)
+
+
+def minkowski_distance(x, y, p=2):
+    """
+    Compute the L**p distance between two arrays.
+
+    Parameters
+    ----------
+    x : (M, K) array_like
+        Input array.
+    y : (N, K) array_like
+        Input array.
+    p : float, 1 <= p <= infinity
+        Which Minkowski p-norm to use.
+
+    Examples
+    --------
+    >>> from scipy.spatial import minkowski_distance
+    >>> minkowski_distance([[0,0],[0,0]], [[1,1],[0,1]])
+    array([ 1.41421356,  1.        ])
+
+    """
+    x = np.asarray(x)
+    y = np.asarray(y)
+    if p == np.inf or p == 1:
+        return minkowski_distance_p(x, y, p)
+    else:
+        return minkowski_distance_p(x, y, p)**(1./p)
+
+
+class Rectangle(object):
+    """Hyperrectangle class.
+
+    Represents a Cartesian product of intervals.
+    """
+    def __init__(self, maxes, mins):
+        """Construct a hyperrectangle."""
+        self.maxes = np.maximum(maxes,mins).astype(float)
+        self.mins = np.minimum(maxes,mins).astype(float)
+        self.m, = self.maxes.shape
+
+    def __repr__(self):
+        return "<Rectangle %s>" % list(zip(self.mins, self.maxes))
+
+    def volume(self):
+        """Total volume."""
+        return np.prod(self.maxes-self.mins)
+
+    def split(self, d, split):
+        """
+        Produce two hyperrectangles by splitting.
+
+        In general, if you need to compute maximum and minimum
+        distances to the children, it can be done more efficiently
+        by updating the maximum and minimum distances to the parent.
+
+        Parameters
+        ----------
+        d : int
+            Axis to split hyperrectangle along.
+        split : float
+            Position along axis `d` to split at.
+
+        """
+        mid = np.copy(self.maxes)
+        mid[d] = split
+        less = Rectangle(self.mins, mid)
+        mid = np.copy(self.mins)
+        mid[d] = split
+        greater = Rectangle(mid, self.maxes)
+        return less, greater
+
+    def min_distance_point(self, x, p=2.):
+        """
+        Return the minimum distance between input and points in the hyperrectangle.
+
+        Parameters
+        ----------
+        x : array_like
+            Input.
+        p : float, optional
+            Input.
+
+        """
+        return minkowski_distance(0, np.maximum(0,np.maximum(self.mins-x,x-self.maxes)),p)
+
+    def max_distance_point(self, x, p=2.):
+        """
+        Return the maximum distance between input and points in the hyperrectangle.
+
+        Parameters
+        ----------
+        x : array_like
+            Input array.
+        p : float, optional
+            Input.
+
+        """
+        return minkowski_distance(0, np.maximum(self.maxes-x,x-self.mins),p)
+
+    def min_distance_rectangle(self, other, p=2.):
+        """
+        Compute the minimum distance between points in the two hyperrectangles.
+
+        Parameters
+        ----------
+        other : hyperrectangle
+            Input.
+        p : float
+            Input.
+
+        """
+        return minkowski_distance(0, np.maximum(0,np.maximum(self.mins-other.maxes,other.mins-self.maxes)),p)
+
+    def max_distance_rectangle(self, other, p=2.):
+        """
+        Compute the maximum distance between points in the two hyperrectangles.
+
+        Parameters
+        ----------
+        other : hyperrectangle
+            Input.
+        p : float, optional
+            Input.
+
+        """
+        return minkowski_distance(0, np.maximum(self.maxes-other.mins,other.maxes-self.mins),p)
+
+
+
[docs]class KDTree(object): + """ + kd-tree for quick nearest-neighbor lookup + + This class provides an index into a set of k-D points which + can be used to rapidly look up the nearest neighbors of any point. + + Parameters + ---------- + data : (N,K) array_like + The data points to be indexed. This array is not copied, and + so modifying this data will result in bogus results. + leafsize : int, optional + The number of points at which the algorithm switches over to + brute-force. Has to be positive. + + Raises + ------ + RuntimeError + The maximum recursion limit can be exceeded for large data + sets. If this happens, either increase the value for the `leafsize` + parameter or increase the recursion limit by:: + + >>> import sys + >>> sys.setrecursionlimit(10000) + + See Also + -------- + cKDTree : Implementation of `KDTree` in Cython + + Notes + ----- + The algorithm used is described in Maneewongvatana and Mount 1999. + The general idea is that the kd-tree is a binary tree, each of whose + nodes represents an axis-aligned hyperrectangle. Each node specifies + an axis and splits the set of points based on whether their coordinate + along that axis is greater than or less than a particular value. + + During construction, the axis and splitting point are chosen by the + "sliding midpoint" rule, which ensures that the cells do not all + become long and thin. + + The tree can be queried for the r closest neighbors of any given point + (optionally returning only those within some maximum distance of the + point). It can also be queried, with a substantial gain in efficiency, + for the r approximate closest neighbors. + + For large dimensions (20 is already large) do not expect this to run + significantly faster than brute force. High-dimensional nearest-neighbor + queries are a substantial open problem in computer science. + + The tree also supports all-neighbors queries, both with arrays of points + and with other kd-trees. These do use a reasonably efficient algorithm, + but the kd-tree is not necessarily the best data structure for this + sort of calculation. + + """ + def __init__(self, data, leafsize=10): + self.data = np.asarray(data) + if self.data.dtype.kind == 'c': + raise TypeError("KDTree does not work with complex data") + + self.n, self.m = np.shape(self.data) + self.leafsize = int(leafsize) + if self.leafsize < 1: + raise ValueError("leafsize must be at least 1") + self.maxes = np.amax(self.data,axis=0) + self.mins = np.amin(self.data,axis=0) + + self.tree = self.__build(np.arange(self.n), self.maxes, self.mins) + +
[docs] class node(object): + def __lt__(self, other): + return id(self) < id(other) + + def __gt__(self, other): + return id(self) > id(other) + + def __le__(self, other): + return id(self) <= id(other) + + def __ge__(self, other): + return id(self) >= id(other) + + def __eq__(self, other): + return id(self) == id(other)
+ +
[docs] class leafnode(node): + def __init__(self, idx): + self.idx = idx + self.children = len(idx)
+ +
[docs] class innernode(node): + def __init__(self, split_dim, split, less, greater): + self.split_dim = split_dim + self.split = split + self.less = less + self.greater = greater + self.children = less.children+greater.children
+ + def __build(self, idx, maxes, mins): + if len(idx) <= self.leafsize: + return KDTree.leafnode(idx) + else: + data = self.data[idx] + # maxes = np.amax(data,axis=0) + # mins = np.amin(data,axis=0) + d = np.argmax(maxes-mins) + maxval = maxes[d] + minval = mins[d] + if maxval == minval: + # all points are identical; warn user? + return KDTree.leafnode(idx) + data = data[:,d] + + # sliding midpoint rule; see Maneewongvatana and Mount 1999 + # for arguments that this is a good idea. + split = (maxval+minval)/2 + less_idx = np.nonzero(data <= split)[0] + greater_idx = np.nonzero(data > split)[0] + if len(less_idx) == 0: + split = np.amin(data) + less_idx = np.nonzero(data <= split)[0] + greater_idx = np.nonzero(data > split)[0] + if len(greater_idx) == 0: + split = np.amax(data) + less_idx = np.nonzero(data < split)[0] + greater_idx = np.nonzero(data >= split)[0] + if len(less_idx) == 0: + # _still_ zero? all must have the same value + if not np.all(data == data[0]): + raise ValueError("Troublesome data array: %s" % data) + split = data[0] + less_idx = np.arange(len(data)-1) + greater_idx = np.array([len(data)-1]) + + lessmaxes = np.copy(maxes) + lessmaxes[d] = split + greatermins = np.copy(mins) + greatermins[d] = split + return KDTree.innernode(d, split, + self.__build(idx[less_idx],lessmaxes,mins), + self.__build(idx[greater_idx],maxes,greatermins)) + + def __query(self, x, k=1, eps=0, p=2, distance_upper_bound=np.inf): + + side_distances = np.maximum(0,np.maximum(x-self.maxes,self.mins-x)) + if p != np.inf: + side_distances **= p + min_distance = np.sum(side_distances) + else: + min_distance = np.amax(side_distances) + + # priority queue for chasing nodes + # entries are: + # minimum distance between the cell and the target + # distances between the nearest side of the cell and the target + # the head node of the cell + q = [(min_distance, + tuple(side_distances), + self.tree)] + # priority queue for the nearest neighbors + # furthest known neighbor first + # entries are (-distance**p, i) + neighbors = [] + + if eps == 0: + epsfac = 1 + elif p == np.inf: + epsfac = 1/(1+eps) + else: + epsfac = 1/(1+eps)**p + + if p != np.inf and distance_upper_bound != np.inf: + distance_upper_bound = distance_upper_bound**p + + while q: + min_distance, side_distances, node = heappop(q) + if isinstance(node, KDTree.leafnode): + # brute-force + data = self.data[node.idx] + ds = minkowski_distance_p(data,x[np.newaxis,:],p) + for i in range(len(ds)): + if ds[i] < distance_upper_bound: + if len(neighbors) == k: + heappop(neighbors) + heappush(neighbors, (-ds[i], node.idx[i])) + if len(neighbors) == k: + distance_upper_bound = -neighbors[0][0] + else: + # we don't push cells that are too far onto the queue at all, + # but since the distance_upper_bound decreases, we might get + # here even if the cell's too far + if min_distance > distance_upper_bound*epsfac: + # since this is the nearest cell, we're done, bail out + break + # compute minimum distances to the children and push them on + if x[node.split_dim] < node.split: + near, far = node.less, node.greater + else: + near, far = node.greater, node.less + + # near child is at the same distance as the current node + heappush(q,(min_distance, side_distances, near)) + + # far child is further by an amount depending only + # on the split value + sd = list(side_distances) + if p == np.inf: + min_distance = max(min_distance, abs(node.split-x[node.split_dim])) + elif p == 1: + sd[node.split_dim] = np.abs(node.split-x[node.split_dim]) + min_distance = min_distance - side_distances[node.split_dim] + sd[node.split_dim] + else: + sd[node.split_dim] = np.abs(node.split-x[node.split_dim])**p + min_distance = min_distance - side_distances[node.split_dim] + sd[node.split_dim] + + # far child might be too far, if so, don't bother pushing it + if min_distance <= distance_upper_bound*epsfac: + heappush(q,(min_distance, tuple(sd), far)) + + if p == np.inf: + return sorted([(-d,i) for (d,i) in neighbors]) + else: + return sorted([((-d)**(1./p),i) for (d,i) in neighbors]) + +
[docs] def query(self, x, k=1, eps=0, p=2, distance_upper_bound=np.inf): + """ + Query the kd-tree for nearest neighbors + + Parameters + ---------- + x : array_like, last dimension self.m + An array of points to query. + k : int, optional + The number of nearest neighbors to return. + eps : nonnegative float, optional + Return approximate nearest neighbors; the kth returned value + is guaranteed to be no further than (1+eps) times the + distance to the real kth nearest neighbor. + p : float, 1<=p<=infinity, optional + Which Minkowski p-norm to use. + 1 is the sum-of-absolute-values "Manhattan" distance + 2 is the usual Euclidean distance + infinity is the maximum-coordinate-difference distance + distance_upper_bound : nonnegative float, optional + Return only neighbors within this distance. This is used to prune + tree searches, so if you are doing a series of nearest-neighbor + queries, it may help to supply the distance to the nearest neighbor + of the most recent point. + + Returns + ------- + d : float or array of floats + The distances to the nearest neighbors. + If x has shape tuple+(self.m,), then d has shape tuple if + k is one, or tuple+(k,) if k is larger than one. Missing + neighbors (e.g. when k > n or distance_upper_bound is + given) are indicated with infinite distances. If k is None, + then d is an object array of shape tuple, containing lists + of distances. In either case the hits are sorted by distance + (nearest first). + i : integer or array of integers + The locations of the neighbors in self.data. i is the same + shape as d. + + Examples + -------- + >>> from scipy import spatial + >>> x, y = np.mgrid[0:5, 2:8] + >>> tree = spatial.KDTree(list(zip(x.ravel(), y.ravel()))) + >>> tree.data + array([[0, 2], + [0, 3], + [0, 4], + [0, 5], + [0, 6], + [0, 7], + [1, 2], + [1, 3], + [1, 4], + [1, 5], + [1, 6], + [1, 7], + [2, 2], + [2, 3], + [2, 4], + [2, 5], + [2, 6], + [2, 7], + [3, 2], + [3, 3], + [3, 4], + [3, 5], + [3, 6], + [3, 7], + [4, 2], + [4, 3], + [4, 4], + [4, 5], + [4, 6], + [4, 7]]) + >>> pts = np.array([[0, 0], [2.1, 2.9]]) + >>> tree.query(pts) + (array([ 2. , 0.14142136]), array([ 0, 13])) + >>> tree.query(pts[0]) + (2.0, 0) + + """ + x = np.asarray(x) + if x.dtype.kind == 'c': + raise TypeError("KDTree does not work with complex data") + if np.shape(x)[-1] != self.m: + raise ValueError("x must consist of vectors of length %d but has shape %s" % (self.m, np.shape(x))) + if p < 1: + raise ValueError("Only p-norms with 1<=p<=infinity permitted") + retshape = np.shape(x)[:-1] + if retshape != (): + if k is None: + dd = np.empty(retshape,dtype=object) + ii = np.empty(retshape,dtype=object) + elif k > 1: + dd = np.empty(retshape+(k,),dtype=float) + dd.fill(np.inf) + ii = np.empty(retshape+(k,),dtype=int) + ii.fill(self.n) + elif k == 1: + dd = np.empty(retshape,dtype=float) + dd.fill(np.inf) + ii = np.empty(retshape,dtype=int) + ii.fill(self.n) + else: + raise ValueError("Requested %s nearest neighbors; acceptable numbers are integers greater than or equal to one, or None") + for c in np.ndindex(retshape): + hits = self.__query(x[c], k=k, eps=eps, p=p, distance_upper_bound=distance_upper_bound) + if k is None: + dd[c] = [d for (d,i) in hits] + ii[c] = [i for (d,i) in hits] + elif k > 1: + for j in range(len(hits)): + dd[c+(j,)], ii[c+(j,)] = hits[j] + elif k == 1: + if len(hits) > 0: + dd[c], ii[c] = hits[0] + else: + dd[c] = np.inf + ii[c] = self.n + return dd, ii + else: + hits = self.__query(x, k=k, eps=eps, p=p, distance_upper_bound=distance_upper_bound) + if k is None: + return [d for (d,i) in hits], [i for (d,i) in hits] + elif k == 1: + if len(hits) > 0: + return hits[0] + else: + return np.inf, self.n + elif k > 1: + dd = np.empty(k,dtype=float) + dd.fill(np.inf) + ii = np.empty(k,dtype=int) + ii.fill(self.n) + for j in range(len(hits)): + dd[j], ii[j] = hits[j] + return dd, ii + else: + raise ValueError("Requested %s nearest neighbors; acceptable numbers are integers greater than or equal to one, or None")
+ + def __query_ball_point(self, x, r, p=2., eps=0): + R = Rectangle(self.maxes, self.mins) + + def traverse_checking(node, rect): + if rect.min_distance_point(x, p) > r / (1. + eps): + return [] + elif rect.max_distance_point(x, p) < r * (1. + eps): + return traverse_no_checking(node) + elif isinstance(node, KDTree.leafnode): + d = self.data[node.idx] + return node.idx[minkowski_distance(d, x, p) <= r].tolist() + else: + less, greater = rect.split(node.split_dim, node.split) + return traverse_checking(node.less, less) + \ + traverse_checking(node.greater, greater) + + def traverse_no_checking(node): + if isinstance(node, KDTree.leafnode): + return node.idx.tolist() + else: + return traverse_no_checking(node.less) + \ + traverse_no_checking(node.greater) + + return traverse_checking(self.tree, R) + +
[docs] def query_ball_point(self, x, r, p=2., eps=0): + """Find all points within distance r of point(s) x. + + Parameters + ---------- + x : array_like, shape tuple + (self.m,) + The point or points to search for neighbors of. + r : positive float + The radius of points to return. + p : float, optional + Which Minkowski p-norm to use. Should be in the range [1, inf]. + eps : nonnegative float, optional + Approximate search. Branches of the tree are not explored if their + nearest points are further than ``r / (1 + eps)``, and branches are + added in bulk if their furthest points are nearer than + ``r * (1 + eps)``. + + Returns + ------- + results : list or array of lists + If `x` is a single point, returns a list of the indices of the + neighbors of `x`. If `x` is an array of points, returns an object + array of shape tuple containing lists of neighbors. + + Notes + ----- + If you have many points whose neighbors you want to find, you may save + substantial amounts of time by putting them in a KDTree and using + query_ball_tree. + + Examples + -------- + >>> from scipy import spatial + >>> x, y = np.mgrid[0:5, 0:5] + >>> points = np.c_[x.ravel(), y.ravel()] + >>> tree = spatial.KDTree(points) + >>> tree.query_ball_point([2, 0], 1) + [5, 10, 11, 15] + + Query multiple points and plot the results: + + >>> import matplotlib.pyplot as plt + >>> points = np.asarray(points) + >>> plt.plot(points[:,0], points[:,1], '.') + >>> for results in tree.query_ball_point(([2, 0], [3, 3]), 1): + ... nearby_points = points[results] + ... plt.plot(nearby_points[:,0], nearby_points[:,1], 'o') + >>> plt.margins(0.1, 0.1) + >>> plt.show() + + """ + x = np.asarray(x) + if x.dtype.kind == 'c': + raise TypeError("KDTree does not work with complex data") + if x.shape[-1] != self.m: + raise ValueError("Searching for a %d-dimensional point in a " + "%d-dimensional KDTree" % (x.shape[-1], self.m)) + if len(x.shape) == 1: + return self.__query_ball_point(x, r, p, eps) + else: + retshape = x.shape[:-1] + result = np.empty(retshape, dtype=object) + for c in np.ndindex(retshape): + result[c] = self.__query_ball_point(x[c], r, p=p, eps=eps) + return result
+ +
[docs] def query_ball_tree(self, other, r, p=2., eps=0): + """Find all pairs of points whose distance is at most r + + Parameters + ---------- + other : KDTree instance + The tree containing points to search against. + r : float + The maximum distance, has to be positive. + p : float, optional + Which Minkowski norm to use. `p` has to meet the condition + ``1 <= p <= infinity``. + eps : float, optional + Approximate search. Branches of the tree are not explored + if their nearest points are further than ``r/(1+eps)``, and + branches are added in bulk if their furthest points are nearer + than ``r * (1+eps)``. `eps` has to be non-negative. + + Returns + ------- + results : list of lists + For each element ``self.data[i]`` of this tree, ``results[i]`` is a + list of the indices of its neighbors in ``other.data``. + + """ + results = [[] for i in range(self.n)] + + def traverse_checking(node1, rect1, node2, rect2): + if rect1.min_distance_rectangle(rect2, p) > r/(1.+eps): + return + elif rect1.max_distance_rectangle(rect2, p) < r*(1.+eps): + traverse_no_checking(node1, node2) + elif isinstance(node1, KDTree.leafnode): + if isinstance(node2, KDTree.leafnode): + d = other.data[node2.idx] + for i in node1.idx: + results[i] += node2.idx[minkowski_distance(d,self.data[i],p) <= r].tolist() + else: + less, greater = rect2.split(node2.split_dim, node2.split) + traverse_checking(node1,rect1,node2.less,less) + traverse_checking(node1,rect1,node2.greater,greater) + elif isinstance(node2, KDTree.leafnode): + less, greater = rect1.split(node1.split_dim, node1.split) + traverse_checking(node1.less,less,node2,rect2) + traverse_checking(node1.greater,greater,node2,rect2) + else: + less1, greater1 = rect1.split(node1.split_dim, node1.split) + less2, greater2 = rect2.split(node2.split_dim, node2.split) + traverse_checking(node1.less,less1,node2.less,less2) + traverse_checking(node1.less,less1,node2.greater,greater2) + traverse_checking(node1.greater,greater1,node2.less,less2) + traverse_checking(node1.greater,greater1,node2.greater,greater2) + + def traverse_no_checking(node1, node2): + if isinstance(node1, KDTree.leafnode): + if isinstance(node2, KDTree.leafnode): + for i in node1.idx: + results[i] += node2.idx.tolist() + else: + traverse_no_checking(node1, node2.less) + traverse_no_checking(node1, node2.greater) + else: + traverse_no_checking(node1.less, node2) + traverse_no_checking(node1.greater, node2) + + traverse_checking(self.tree, Rectangle(self.maxes, self.mins), + other.tree, Rectangle(other.maxes, other.mins)) + return results
+ +
[docs] def query_pairs(self, r, p=2., eps=0): + """ + Find all pairs of points within a distance. + + Parameters + ---------- + r : positive float + The maximum distance. + p : float, optional + Which Minkowski norm to use. `p` has to meet the condition + ``1 <= p <= infinity``. + eps : float, optional + Approximate search. Branches of the tree are not explored + if their nearest points are further than ``r/(1+eps)``, and + branches are added in bulk if their furthest points are nearer + than ``r * (1+eps)``. `eps` has to be non-negative. + + Returns + ------- + results : set + Set of pairs ``(i,j)``, with ``i < j``, for which the corresponding + positions are close. + + """ + results = set() + + def traverse_checking(node1, rect1, node2, rect2): + if rect1.min_distance_rectangle(rect2, p) > r/(1.+eps): + return + elif rect1.max_distance_rectangle(rect2, p) < r*(1.+eps): + traverse_no_checking(node1, node2) + elif isinstance(node1, KDTree.leafnode): + if isinstance(node2, KDTree.leafnode): + # Special care to avoid duplicate pairs + if id(node1) == id(node2): + d = self.data[node2.idx] + for i in node1.idx: + for j in node2.idx[minkowski_distance(d,self.data[i],p) <= r]: + if i < j: + results.add((i,j)) + else: + d = self.data[node2.idx] + for i in node1.idx: + for j in node2.idx[minkowski_distance(d,self.data[i],p) <= r]: + if i < j: + results.add((i,j)) + elif j < i: + results.add((j,i)) + else: + less, greater = rect2.split(node2.split_dim, node2.split) + traverse_checking(node1,rect1,node2.less,less) + traverse_checking(node1,rect1,node2.greater,greater) + elif isinstance(node2, KDTree.leafnode): + less, greater = rect1.split(node1.split_dim, node1.split) + traverse_checking(node1.less,less,node2,rect2) + traverse_checking(node1.greater,greater,node2,rect2) + else: + less1, greater1 = rect1.split(node1.split_dim, node1.split) + less2, greater2 = rect2.split(node2.split_dim, node2.split) + traverse_checking(node1.less,less1,node2.less,less2) + traverse_checking(node1.less,less1,node2.greater,greater2) + + # Avoid traversing (node1.less, node2.greater) and + # (node1.greater, node2.less) (it's the same node pair twice + # over, which is the source of the complication in the + # original KDTree.query_pairs) + if id(node1) != id(node2): + traverse_checking(node1.greater,greater1,node2.less,less2) + + traverse_checking(node1.greater,greater1,node2.greater,greater2) + + def traverse_no_checking(node1, node2): + if isinstance(node1, KDTree.leafnode): + if isinstance(node2, KDTree.leafnode): + # Special care to avoid duplicate pairs + if id(node1) == id(node2): + for i in node1.idx: + for j in node2.idx: + if i < j: + results.add((i,j)) + else: + for i in node1.idx: + for j in node2.idx: + if i < j: + results.add((i,j)) + elif j < i: + results.add((j,i)) + else: + traverse_no_checking(node1, node2.less) + traverse_no_checking(node1, node2.greater) + else: + # Avoid traversing (node1.less, node2.greater) and + # (node1.greater, node2.less) (it's the same node pair twice + # over, which is the source of the complication in the + # original KDTree.query_pairs) + if id(node1) == id(node2): + traverse_no_checking(node1.less, node2.less) + traverse_no_checking(node1.less, node2.greater) + traverse_no_checking(node1.greater, node2.greater) + else: + traverse_no_checking(node1.less, node2) + traverse_no_checking(node1.greater, node2) + + traverse_checking(self.tree, Rectangle(self.maxes, self.mins), + self.tree, Rectangle(self.maxes, self.mins)) + return results
+ +
[docs] def count_neighbors(self, other, r, p=2.): + """ + Count how many nearby pairs can be formed. + + Count the number of pairs (x1,x2) can be formed, with x1 drawn + from self and x2 drawn from ``other``, and where + ``distance(x1, x2, p) <= r``. + This is the "two-point correlation" described in Gray and Moore 2000, + "N-body problems in statistical learning", and the code here is based + on their algorithm. + + Parameters + ---------- + other : KDTree instance + The other tree to draw points from. + r : float or one-dimensional array of floats + The radius to produce a count for. Multiple radii are searched with + a single tree traversal. + p : float, 1<=p<=infinity, optional + Which Minkowski p-norm to use + + Returns + ------- + result : int or 1-D array of ints + The number of pairs. Note that this is internally stored in a numpy + int, and so may overflow if very large (2e9). + + """ + def traverse(node1, rect1, node2, rect2, idx): + min_r = rect1.min_distance_rectangle(rect2,p) + max_r = rect1.max_distance_rectangle(rect2,p) + c_greater = r[idx] > max_r + result[idx[c_greater]] += node1.children*node2.children + idx = idx[(min_r <= r[idx]) & (r[idx] <= max_r)] + if len(idx) == 0: + return + + if isinstance(node1,KDTree.leafnode): + if isinstance(node2,KDTree.leafnode): + ds = minkowski_distance(self.data[node1.idx][:,np.newaxis,:], + other.data[node2.idx][np.newaxis,:,:], + p).ravel() + ds.sort() + result[idx] += np.searchsorted(ds,r[idx],side='right') + else: + less, greater = rect2.split(node2.split_dim, node2.split) + traverse(node1, rect1, node2.less, less, idx) + traverse(node1, rect1, node2.greater, greater, idx) + else: + if isinstance(node2,KDTree.leafnode): + less, greater = rect1.split(node1.split_dim, node1.split) + traverse(node1.less, less, node2, rect2, idx) + traverse(node1.greater, greater, node2, rect2, idx) + else: + less1, greater1 = rect1.split(node1.split_dim, node1.split) + less2, greater2 = rect2.split(node2.split_dim, node2.split) + traverse(node1.less,less1,node2.less,less2,idx) + traverse(node1.less,less1,node2.greater,greater2,idx) + traverse(node1.greater,greater1,node2.less,less2,idx) + traverse(node1.greater,greater1,node2.greater,greater2,idx) + + R1 = Rectangle(self.maxes, self.mins) + R2 = Rectangle(other.maxes, other.mins) + if np.shape(r) == (): + r = np.array([r]) + result = np.zeros(1,dtype=int) + traverse(self.tree, R1, other.tree, R2, np.arange(1)) + return result[0] + elif len(np.shape(r)) == 1: + r = np.asarray(r) + n, = r.shape + result = np.zeros(n,dtype=int) + traverse(self.tree, R1, other.tree, R2, np.arange(n)) + return result + else: + raise ValueError("r must be either a single value or a one-dimensional array of values")
+ +
[docs] def sparse_distance_matrix(self, other, max_distance, p=2.): + """ + Compute a sparse distance matrix + + Computes a distance matrix between two KDTrees, leaving as zero + any distance greater than max_distance. + + Parameters + ---------- + other : KDTree + + max_distance : positive float + + p : float, optional + + Returns + ------- + result : dok_matrix + Sparse matrix representing the results in "dictionary of keys" format. + + """ + result = scipy.sparse.dok_matrix((self.n,other.n)) + + def traverse(node1, rect1, node2, rect2): + if rect1.min_distance_rectangle(rect2, p) > max_distance: + return + elif isinstance(node1, KDTree.leafnode): + if isinstance(node2, KDTree.leafnode): + for i in node1.idx: + for j in node2.idx: + d = minkowski_distance(self.data[i],other.data[j],p) + if d <= max_distance: + result[i,j] = d + else: + less, greater = rect2.split(node2.split_dim, node2.split) + traverse(node1,rect1,node2.less,less) + traverse(node1,rect1,node2.greater,greater) + elif isinstance(node2, KDTree.leafnode): + less, greater = rect1.split(node1.split_dim, node1.split) + traverse(node1.less,less,node2,rect2) + traverse(node1.greater,greater,node2,rect2) + else: + less1, greater1 = rect1.split(node1.split_dim, node1.split) + less2, greater2 = rect2.split(node2.split_dim, node2.split) + traverse(node1.less,less1,node2.less,less2) + traverse(node1.less,less1,node2.greater,greater2) + traverse(node1.greater,greater1,node2.less,less2) + traverse(node1.greater,greater1,node2.greater,greater2) + traverse(self.tree, Rectangle(self.maxes, self.mins), + other.tree, Rectangle(other.maxes, other.mins)) + + return result
+ + +def distance_matrix(x, y, p=2, threshold=1000000): + """ + Compute the distance matrix. + + Returns the matrix of all pair-wise distances. + + Parameters + ---------- + x : (M, K) array_like + Matrix of M vectors in K dimensions. + y : (N, K) array_like + Matrix of N vectors in K dimensions. + p : float, 1 <= p <= infinity + Which Minkowski p-norm to use. + threshold : positive int + If ``M * N * K`` > `threshold`, algorithm uses a Python loop instead + of large temporary arrays. + + Returns + ------- + result : (M, N) ndarray + Matrix containing the distance from every vector in `x` to every vector + in `y`. + + Examples + -------- + >>> from scipy.spatial import distance_matrix + >>> distance_matrix([[0,0],[0,1]], [[1,0],[1,1]]) + array([[ 1. , 1.41421356], + [ 1.41421356, 1. ]]) + + """ + + x = np.asarray(x) + m, k = x.shape + y = np.asarray(y) + n, kk = y.shape + + if k != kk: + raise ValueError("x contains %d-dimensional vectors but y contains %d-dimensional vectors" % (k, kk)) + + if m*n*k <= threshold: + return minkowski_distance(x[:,np.newaxis,:],y[np.newaxis,:,:],p) + else: + result = np.empty((m,n),dtype=float) # FIXME: figure out the best dtype + if m < n: + for i in range(m): + result[i,:] = minkowski_distance(x[i],y,p) + else: + for j in range(n): + result[:,j] = minkowski_distance(x,y[j],p) + return result +
+ +
+ + +
+
+
+
+ + + + + Fork me on GitHub + + + + + + \ No newline at end of file diff --git a/docs/build/html/_modules/spatialpy/BoundaryCondition.html b/docs/build/html/_modules/spatialpy/BoundaryCondition.html deleted file mode 100644 index f704d517..00000000 --- a/docs/build/html/_modules/spatialpy/BoundaryCondition.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - - spatialpy.BoundaryCondition — SpatialPy 0.5.1 documentation - - - - - - - - - - - - - - - - - - -
- - -
-
- - -
- -

Source code for spatialpy.BoundaryCondition

-'''
-SpatialPy is a Python 3 package for simulation of
-spatial deterministic/stochastic reaction-diffusion-advection problems
-Copyright (C) 2021 SpatialPy developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU GENERAL PUBLIC LICENSE Version 3 as
-published by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU GENERAL PUBLIC LICENSE Version 3 for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-'''
-
-import numpy
-from spatialpy.Model import ModelError
-
-
-
[docs]class BoundaryCondition(): - """ Set spatial regions of the domain where a property of - particles are held constant (updated each simulation step) - - Conditions (one or more of the following must be set): - - xmin, xmax: (float) min or max value in the x dimension - - ymin, ymax: (float) min or max value in the y dimension - - zmin, zmax: (float) min or max value in the z dimension - - type_id: type (subdomain) of the partciles - Targets (one of the following must be set): - property: (str), 'nu', 'rho','v' - species: (str) name of a chemical species. - Must also set deterministic=True/False flag. - - :param xmin: x-axis coordinate lower bound of **condition** - :type xmin: float - - :param xmax: x-axis coordinate upper bound of **condition** - :type xmax: float - - :param ymin: y-axis coordinate lower bound of **condition** - :type ymin: float - - :param ymax: y-axis coordinate upper bound of **condition** - :type ymax: float - - :param zmin: z-axis coordinate lower bound of **condition** - :type zmin: float - - :param zmax: z-axis coordinate upper bound of **condition** - :type zmax: float - - :param typeid: Set **condition** to particle type id - :type typeid: int - - :param species: Set **target** of boundary condition to target Species. If set, determinstic must also be set to True/False. - :type species: str - - :param deterministic: **Must be set if target is Species.** Set True if boundary condition target is species \ - and applies to deterministic simulation. **BoundaryCondition not yet implemeneted for Stochastic Species**. - :type deterministic: bool - - :param property: Set **target** to properties, can be 'nu' 'rho' or 'v' - :type property: str - - :param value: Value property will take in region defined by the conditions - :type value: float or float[3] - - :param model: Target model of boundary condition - :type model: spatialpy.Model.Model - """ - def __init__(self, - xmin=None, xmax=None, - ymin=None, ymax=None, - zmin=None, zmax=None, - type_id=None, - species=None, - deterministic=True, - property=None, - value=None, - model=None): - - self.xmin = xmin - self.xmax = xmax - self.ymin = ymin - self.ymax = ymax - self.zmin = zmin - self.zmax = zmax - self.type_id = type_id - self.species = species - self.property = property - self.deterministic = deterministic - self.value = value - self.model = model - - -
[docs] def expression(self): - """ Creates evaluable string expression of boundary condition. - - :rtype: str - """ - if( self.species is not None and self.property is not None): - raise ModelError("Can not set both species and property") - if self.value is None: - raise ModelError("Must set value") - cond=[] - if(self.xmin is not None): cond.append("(me->x[0] >= {0})".format(self.xmin)) - if(self.xmax is not None): cond.append("(me->x[0] <= {0})".format(self.xmax)) - if(self.ymin is not None): cond.append("(me->x[1] >= {0})".format(self.ymin)) - if(self.ymax is not None): cond.append("(me->x[1] <= {0})".format(self.ymax)) - if(self.zmin is not None): cond.append("(me->x[2] >= {0})".format(self.zmin)) - if(self.zmax is not None): cond.append("(me->x[2] <= {0})".format(self.zmax)) - if(self.type_id is not None): cond.append("(me->type == {0})".format(int(self.type_id))) - if(len(cond)==0): raise ModelError('need at least one condition on the BoundaryCondition') - bcstr = "if(" + '&&'.join(cond) + "){" - if self.species is not None: - if self.deterministic: - s_ndx = self.model.species_map[self.model.listOfSpecies[self.species]] - bcstr += "me->C[{0}] = {1};".format(s_ndx,self.value) - else: - raise Exception("BoundaryConditions don't work for stochastic species yet") - elif self.property is not None: - if(self.property == 'v'): - for i in range(3): - bcstr+= "me->v[{0}]={1};".format(i,self.value[i]) - elif(self.property == 'nu'): - bcstr+= "me->nu={0};".format(self.value) - elif(self.property == 'rho'): - bcstr+= "me->rho={0};".format(self.value) - else: - raise Exception("Unable handle boundary condition for property '{0}'".format(self.property)) - bcstr+= "}" - return bcstr
-
- -
- - -
-
-
-
- - - - - Fork me on GitHub - - - - - - \ No newline at end of file diff --git a/docs/build/html/_modules/spatialpy/Domain.html b/docs/build/html/_modules/spatialpy/Domain.html deleted file mode 100644 index d4bf4863..00000000 --- a/docs/build/html/_modules/spatialpy/Domain.html +++ /dev/null @@ -1,792 +0,0 @@ - - - - - - - - spatialpy.Domain — SpatialPy 0.5.1 documentation - - - - - - - - - - - - - - - - - - -
- - -
-
- - -
- -

Source code for spatialpy.Domain

-'''
-SpatialPy is a Python 3 package for simulation of
-spatial deterministic/stochastic reaction-diffusion-advection problems
-Copyright (C) 2021 SpatialPy developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU GENERAL PUBLIC LICENSE Version 3 as
-published by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU GENERAL PUBLIC LICENSE Version 3 for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-'''
-
-import json
-
-import numpy
-from scipy.spatial import KDTree
-
-from spatialpy.Result import _plotly_iterate
-
-
-
[docs]class Domain(): - """ Domain class for SpatialPy. A domain defines points and attributes of a regional space for simulation. - - :param numpoints: Total number of spatial domain points - :type numpoints: int - - :param xlim: Range of domain along x-axis - :type xmax: tuple(float) - - :param ylim: Range of domain along y-axis - :type ymin: tuple(float) - - :param zlim: Range of domain along z-axis - :type ymax: tuple(float) - - :param rho: Background density for the system - :type zmin: float - - :param c0: Speed of sound for the system - :type zmax: float - - :param P0: Background pressure for the system - :type typeid: float - - :param gravity: Acceleration of gravity for the system. - :type species: float - """ - - - def __init__(self, numpoints, xlim, ylim, zlim, rho0=1.0, c0=10, P0=10, gravity=None): - self.vertices = numpy.zeros((numpoints, 3), dtype=float) - self.triangles = None - self.tetrahedrons = None - - self.on_boundary = None - self.domain_size = None - self.tetrahedron_vol = None - - self.vol = numpy.zeros((numpoints), dtype=float) - self.mass = numpy.zeros((numpoints), dtype=float) - self.type = numpy.zeros((numpoints), dtype=int) - self.nu = numpy.zeros((numpoints), dtype=float) - self.fixed = numpy.zeros((numpoints), dtype=bool) - - self.rho0 = rho0 - self.c0 = c0 - self.P0 = P0 - self.gravity = gravity - - self.xlim = xlim - self.ylim = ylim - self.zlim = zlim - - def __str__(self): - pad = " " - domain_strs = ["Domain Attributes", "", f"{pad}On Boundary: {self.on_boundary}", - f"{pad}Domain Size: {self.domain_size}", f"{pad}RHO_0: {self.rho0}", f"{pad}C_0: {self.c0}", - f"{pad}P_0: {self.P0}", f"{pad}Gravity: {self.gravity}", f"{pad}X Limit: {self.xlim}", - f"{pad}Y Limit: {self.ylim}", f"{pad}Z Limit: {self.zlim}"] - domain_strs.extend(["", "Paritcles", ""]) - for i, vertex in enumerate(self.vertices): - v_str = f"{pad}{i+1}: {vertex}\n{pad} Volume:{self.vol[i]}, Mass: {self.mass[i]}, " - v_str += f"Type: {self.type[i]}, nu: {self.nu[i]}, Fixed: {self.fixed[i]}" - domain_strs.append(v_str) - if self.triangles is not None: - domain_strs.extend(["", "Triangles", ""]) - for i, triangle in enumerate(self.triangles): - domain_strs.append(f"{pad}{i+1}: {triangle}") - if self.tetrahedrons is not None: - domain_strs.extend(["", "Tetrahedrons", ""]) - for i, tetrahedron in enumerate(self.tetrahedrons): - domain_strs.append(f"{pad}{i+1}: {tetrahedron}, Volume: {self.tetrahedron_vol[i]}") - - return "\n".join(domain_strs) - -
[docs] def add_point(self, x, vol, mass, type, nu, fixed): - """ Add a single point particle to the domain space. - - :param x: Spatial coordinate vertices of point to be added - :type x: tuple(float, float, float) or tuple(float, float) - - :param vol: Default volume of particle to be added - :type vol: float - - :param mass: Default mass of particle to be added - :type mass: float - - :param type: Particle typeid of particle to be created - :type type: int - - :param nu: Default viscosity of particle to be created - :type nu: float - - :param fixed: True if particle is spatially fixed, else False - :type fixed: bool - """ - - self.vol = numpy.append(self.vol, vol) - self.mass = numpy.append(self.mass, mass) - self.type = numpy.append(self.type, type) - self.nu = numpy.append(self.nu, nu) - self.fixed = numpy.append(self.fixed, fixed) - - self.vertices = numpy.append(self.vertices, [x], axis=0)
- -
[docs] def find_boundary_points(self): - if self.on_boundary is None: - self.on_boundary = numpy.zeros((self.get_num_voxels()), dtype=bool) - # exterior triangles are part of one-and-only-one tetrahedron - if self.triangles is None or len(self.triangles) == 0 or len(self.tetrahedrons) == 0: - return self.on_boundary - from itertools import combinations - triangle_in_tetrahedrons_count = {} - for i in range(self.get_num_voxels()): - tets = self.tetrahedrons[i,:] - tets.sort() - for p in combinations(tets,3): - key = ".".join([str(s) for s in p ]) - #print(key) - if key in triangle_in_tetrahedrons_count: - triangle_in_tetrahedrons_count[key]+=1 - else: - triangle_in_tetrahedrons_count[key]=1 - boundary_points = set({}) - for key in triangle_in_tetrahedrons_count: - #print(key+" "+str(triangle_in_tetrahedrons_count[key])) - if triangle_in_tetrahedrons_count[key]==1: - (a,b,c) = key.split('.') - boundary_points.add(int(a)) - boundary_points.add(int(b)) - boundary_points.add(int(c)) - for v in boundary_points: - self.on_boundary[v] = True - return self.on_boundary
- - -
[docs] def get_domain_size(self): - """ Estimate of domain size at each vertex as the average of the - diameters of the circumradius of the tetrahedrons that vertex - is a part of. - - :rtype: numpy.array - """ - if self.domain_size is None: - #coordinates = self.coordinates() - _ = self.get_vol() - - # Compute the circumradius of the cells - cr = numpy.zeros((self.tetrahedrons.shape[0]),dtype=float) - for i in range(len(cr)): - t_vtx = self.tetrahedrons[i,:] - # https://en.wikipedia.org/wiki/Tetrahedron#Circumradius - a = self.distance_between_2_vertices( t_vtx[0], t_vtx[1]) - A = self.distance_between_2_vertices( t_vtx[2], t_vtx[3]) - b = self.distance_between_2_vertices( t_vtx[0], t_vtx[2]) - B = self.distance_between_2_vertices( t_vtx[1], t_vtx[3]) - c = self.distance_between_2_vertices( t_vtx[0], t_vtx[3]) - C = self.distance_between_2_vertices( t_vtx[1], t_vtx[2]) - R = numpy.sqrt( (a*A+b*B+c*C)*(a*A+b*B-c*C)*(a*A-b*B+c*C)*(-a*A+b*B+c*C) ) / (24*self.tetrahedron_vol[i]) - cr[i] = R - - # Compute the mean for each vertex based on all incident cells - self.domain_size = numpy.zeros((self.vertices.shape[0]),dtype=float) - count = numpy.zeros((self.vertices.shape[0]),dtype=float) - for tndx in range(self.tetrahedrons.shape[0]): - for vndx in self.tetrahedrons[tndx,:]: - self.domain_size[vndx] += cr[tndx] - count[vndx] += 1 - for vndx in range(len(self.domain_size)): - self.domain_size[vndx] = self.domain_size[vndx]/count[vndx] - - return self.domain_size
- -
[docs] def distance_between_2_vertices(self, a, b): - """ Get distance between 2 domain vertices. - - :param a: Starting point - :type a: tuple(float, float, float) or tuple(float, float) - - :param b: Ending point - :type b: tuple(float, float, float) or tuple(float, float) - - :rtype: float - """ - return numpy.linalg.norm( self.vertices[a,:]-self.vertices[b,:] )
- -
[docs] def closest_vertex(self, x): - """ Find the nearest vertex of a given point in the domain. - - :param x: Target source point - :type x: tuple(float, float, float) or tuple(float, float) - - :rtype: tuple(float, float, float) or tuple(float, float) - """ - - min_dist = None - min_vtx = None - for i in range(self.vertices.shape[0]): - d = numpy.linalg.norm( self.vertices[i,:]-x ) - if d > 0 and (min_dist is None or d < min_dist): - min_dist = d - min_vtx = i - return min_vtx
- -
[docs] def coordinates(self): - """ Get coordinates within domain. - - :rtype: numpy.array - """ - return self.vertices
- -
[docs] def get_num_voxels(self): - """ Get number of voxels in domain. - - :rtype: int - """ - - return self.vertices.shape[0]
- -
[docs] def find_h(self): - """ Find h value of system. This value value is based off of \ - the particle which has the greatest distance to \ - its nearest neighbor. - - :rtype: float - - """ - kdtree = KDTree(self.vertices) - # Detect nearest neighbor distances for all points - # since each searched point is already included in - # the tree, we search 2 nearest neighbors, since - # the first is just the point itself - distances, indexes = kdtree.query(self.vertices, 2) - # We only need the distances to the second (non-self) neighbor. - max_dist = max(distances[:,1]) - h = 2.2*max_dist - return h
- -
[docs] def get_bounding_box(self): - """ Get the bounding box of the entire domain. - - :rtype: float | float | float | float | float | float - """ - - xhi=None - xlo=None - yhi=None - ylo=None - zhi=None - zlo=None - for i in range(self.vertices.shape[0]): - if xhi is None or xhi < self.vertices[i,0]: xhi = self.vertices[i,0] - if xlo is None or xlo > self.vertices[i,0]: xlo = self.vertices[i,0] - if yhi is None or yhi < self.vertices[i,1]: yhi = self.vertices[i,1] - if ylo is None or ylo > self.vertices[i,1]: ylo = self.vertices[i,1] - if zhi is None or zhi < self.vertices[i,2]: zhi = self.vertices[i,2] - if zlo is None or zlo > self.vertices[i,2]: zlo = self.vertices[i,2] - return xhi,xlo,yhi,ylo,zhi,zlo
- -
[docs] def get_vol(self): - """ Get the total volume of the domain. - - :rtype: float - """ - - if self.vol is None: - self.calculate_vol() - return self.vol
- -
[docs] def calculate_vol(self): - """ Calculate the total volume of the domain. - """ - - self.vol = numpy.zeros((self.vertices.shape[0]),dtype=float) - self.tetrahedron_vol = numpy.zeros((self.tetrahedrons.shape[0]),dtype=float) - for t_ndx in range(self.tetrahedrons.shape[0]): - v1,v2,v3,v4 = self.tetrahedrons[t_ndx] - a = self.vertices[v1,:] - b = self.vertices[v2,:] - c = self.vertices[v3,:] - d = self.vertices[v4,:] - #https://en.wikipedia.org/wiki/Tetrahedron#Volume - t_vol = numpy.abs(numpy.dot( (a-d), numpy.cross( (b-d),(c-d) ) )/6 ) - self.tetrahedron_vol[t_ndx] = t_vol - self.vol[v1] += t_vol/4 - self.vol[v2] += t_vol/4 - self.vol[v3] += t_vol/4 - self.vol[v4] += t_vol/4
- - -
[docs] def plot_types(self, width=None, height=None, colormap=None, size=5, title=None, - use_matplotlib=False, return_plotly_figure=False): - ''' - Plots the domain using plotly. Can only be viewed in a Jupyter Notebook. - - :param width: Width in pixels of output plot box or for matplotlib inches of output plot box. \ - Default=500 - :type width: int - - :param height: Height in pixels of output plot box or for matplotlib inches of output plot box. \ - Default=500 - :type height: int - - :param colormap: colormap to use. Plotly specification. **valid values:** \ - "Plotly3","Jet","Blues","YlOrRd", "PuRd","BuGn","YlOrBr","PuBuGn","BuPu",\ - "YlGnBu", "PuBu","GnBu","YlGn","Greens","Reds", "Greys","RdPu","OrRd","Purples","Oranges". - :type colormap: str - - :param size: Size in pixels of the particle - :type size: int - - :param title: The title of the graph - :type title: str - - :param return_plotly_figure: Whether or not to return a figure dictionary of data(graph object traces) and layout options - which may be edited by the user. - :type return_plotly_figure: bool - - :param use_matplotlib: Whether or not to plot the proprties results using matplotlib. - :type use_matplotlib: bool - ''' - - if width is None: - width = 6.4 if use_matplotlib else 500 - if height is None: - height = 4.8 if use_matplotlib else 500 - - if use_matplotlib: - import matplotlib.pyplot as plt - - plt.figure(figsize=(width, height)) - plt.scatter(self.vertices[:,0], self.vertices[:,1], c=self.type, cmap=colormap) - plt.axis('scaled') - plt.colorbar() - if title is not None: - plt.title(title) - plt.grid(linestyle='--', linewidth=1) - plt.plot() - return - - from plotly.offline import init_notebook_mode, iplot - - types = {} - for i, val in enumerate(self.type): - name = "type {}".format(val) - - if name in types.keys(): - types[name]['points'].append(self.vertices[i]) - types[name]['data'].append(self.type[i]) - else: - types[name] = {"points":[self.vertices[i]], "data":[self.type[i]]} - - is_2d = self.zlim[0] == self.zlim[1] - - trace_list = _plotly_iterate(types, size=size, property_name="type", - colormap=colormap, is_2d=is_2d) - - scene = { - "aspectmode": 'data', - } - layout = {"width": width, "height": width, "scene":scene, - "xaxis":{"range":self.xlim}, "yaxis":{"range":self.ylim} - } - - if title is not None: - layout["title"] = title - - fig = {"data":trace_list, "layout":layout} - - if return_plotly_figure: - return fig - else: - init_notebook_mode(connected=True) - iplot(fig)
- - -
[docs] @classmethod - def read_xml_mesh(cls, filename): - """ Read a FEniCS/dolfin style XML mesh file - - :param filename: name of file to read - :type filename: str - - :rtype: spatialpy.Domain.Domain - """ - import xml.etree.ElementTree as ET - root = ET.parse(filename).getroot() - if not root.tag == 'dolfin': raise DomainError("Not a FEniCS/dolfin xml mesh.") - mesh = root[0] - if mesh.tag != 'mesh' or \ - mesh.attrib['celltype'] != 'tetrahedron' or \ - mesh.attrib['dim'] != '3': - raise DomainError("XML mesh format error") - # - vertices = mesh[0] - cells = mesh[1] - #vertices - mesh_vertices = numpy.zeros(( len(vertices), 3), dtype=float) - for v in vertices: - mesh_vertices[ int(v.attrib['index']),0] = float(v.attrib['x']) - mesh_vertices[ int(v.attrib['index']),1] = float(v.attrib['y']) - mesh_vertices[ int(v.attrib['index']),2] = float(v.attrib['z']) - - # create domain object - xlim = ( min(mesh_vertices[:,0]) , max(mesh_vertices[:,0]) ) - ylim = ( min(mesh_vertices[:,1]) , max(mesh_vertices[:,1]) ) - zlim = ( min(mesh_vertices[:,2]) , max(mesh_vertices[:,2]) ) - obj = Domain(len(vertices), xlim, ylim, zlim) - obj.vertices = mesh_vertices - - #tetrahedrons - obj.tetrahedrons = numpy.zeros(( len(cells), 4), dtype=int) - for c in cells: - obj.tetrahedrons[ int(c.attrib['index']),0] = int(c.attrib['v0']) - obj.tetrahedrons[ int(c.attrib['index']),1] = int(c.attrib['v1']) - obj.tetrahedrons[ int(c.attrib['index']),2] = int(c.attrib['v2']) - obj.tetrahedrons[ int(c.attrib['index']),3] = int(c.attrib['v3']) - # volume - obj.calculate_vol() - # set Mass equal to the volume - obj.mass = obj.vol - # return model ref - return obj
- - -
[docs] @classmethod - def import_meshio_object(cls, mesh_obj): - """ Import a python meshio mesh object. - - :param mesh_obj: MeshIO object to import - :type mesh_obj: meshio.Mesh - - :rtype: spatialpy.Domain.Domain - """ - # create domain object - obj = Domain() - #vertices - obj.vertices = mesh_obj.points - # triangles - if 'triangle' in mesh_obj.cells: - obj.triangles = mesh_obj.cells['triangle'] - #tetrahedrons - if 'tetra' in mesh_obj.cells: - obj.tetrahedrons = mesh_obj.cells['tetra'] - # volume - obj.calculate_vol() - # return model ref - return obj
- -
[docs] @classmethod - def read_msh_file(cls, filename): - """ Read a Gmsh style .msh file - - :param filename: Filename of gmsh file - :type filename: str - - :rtype: spatialpy.Domain.Domain - """ - - try: - import pygmsh - except ImportError as e: - raise DomainError("The python package 'pygmsh' is not installed.") - # try: - # _ = pygmsh.get_gmsh_major_version() - # except FileNotFoundError as e: - # raise DomainError("The command line program 'gmsh' is not installed or is not found in the current PATH") - - try: - import meshio - except ImportError as e: - raise DomainError("The python package 'meshio' is not installed.") - - return cls.import_meshio_object(meshio.msh_io.read(filename))
- - - -
[docs] @classmethod - def read_stochss_domain(cls, filename): - """ Read a StochSS Domain (.domn) file or pull a StochSS Domain from a StochSS Spatial Model (.smdl) file. - - :param filename: name of file to read. - :type filename: str - - :rtype: spatialpy.Domain.Domain - """ - - try: - with open(filename, "r") as domain_file: - domain = json.load(domain_file) - if "domain" in domain.keys(): - domain = domain['domain'] - - obj = Domain(0, tuple(domain['x_lim']), tuple(domain['y_lim']), tuple(domain['z_lim']), - rho0=domain['rho_0'], c0=domain['c_0'], P0=domain['p_0'], gravity=domain['gravity']) - - for particle in domain['particles']: - obj.add_point(particle['point'], particle['volume'], particle['mass'], - particle['type'], particle['nu'], particle['fixed']) - - return obj - except KeyError as e: - raise DomainError("The file is not a StochSS Domain (.domn) or a StochSS Spatial Model (.smdl).")
- - -
[docs] @classmethod - def create_3D_domain(cls, xlim, ylim, zlim, nx, ny, nz, type_id=1, mass=1.0, nu=1.0, fixed=False, **kwargs): - """ Create a filled 3D domain - - :param xlim: highest and lowest coordinate in the x dimension - :type xlim: tuple(float, float) - - :param ylim: highest and lowest coordinate in the y dimension - :type ylim: tuple(float, float) - - :param zlim: highest and lowest coordinate in the z dimension - :type zlim: tuple(float, float) - - :param nx: number of particle spacing in the x dimension - :type nx: int - - :param ny: number of particle spacing in the y dimension - :type ny: int - - :param nz: number of particle spacing in the z dimension - :type nz: int - - :param type_id: default type ID of particles created to be created. Defaults to 1 - :type type_id: int - - :param mass: default mass of particles created to be created. Defaults to 1.0 - :type mass: float - - :param nu: default viscosity of particles created to be created. Defaults to 1.0 - :type nu: float - - :param fixed: spatially fixed flag of particles created to be created. Defaults to false. - :type fixed: bool - - :param rho0: background density for the system. Defaults to 1.0 - :type rho0: float - - :param c0: speed of sound for the system. Defaults to 10 - :type c0: float - - :param P0: background pressure for the system. Defaults to 10 - :type p0: float - - :rtype: spatialpy.Domain.Domain - """ - # Create domain object - numberparticles = nx*ny*nz - obj = Domain(numberparticles, xlim, ylim, zlim, **kwargs) - # Vertices - obj.vertices = numpy.zeros(( numberparticles, 3), dtype=float) - x_list = numpy.linspace(xlim[0],xlim[1],nx) - y_list = numpy.linspace(ylim[0],ylim[1],ny) - z_list = numpy.linspace(zlim[0],zlim[1],nz) - ndx = 0 - totalvolume = (xlim[1] - xlim[0]) * (ylim[1] - ylim[0]) * (zlim[1] - zlim[0]) - for x in x_list: - for y in y_list: - for z in z_list: - obj.vol[ndx] = totalvolume / numberparticles - obj.vertices[ndx,0] = x - obj.vertices[ndx,1] = y - obj.vertices[ndx,2] = z - obj.type[ndx] = type_id - obj.mass[ndx] = mass - obj.nu[ndx] = nu - obj.fixed[ndx] = fixed - ndx+=1 - - # return model ref - return obj
- -
[docs] @classmethod - def create_2D_domain(cls, xlim, ylim, nx, ny, type_id=1, mass=1.0, nu=1.0, fixed=False, **kwargs): - """ Create a filled 2D domain - - :param xlim: highest and lowest coordinate in the x dimension - :type xlim: tuple(float, float) - - :param ylim: highest and lowest coordinate in the y dimension - :type ylim: tuple(float, float) - - :param nx: number of particle spacing in the x dimension - :type nx: int - - :param ny: number of particle spacing in the y dimension - :type ny: int - - :param type_id: default type ID of particles created to be created. Defaults to 1 - :type type_id: int - - :param mass: default mass of particles created to be created. Defaults to 1.0 - :type mass: float - - :param nu: default viscosity of particles created to be created. Defaults to 1.0 - :type nu: float - - :param fixed: spatially fixed flag of particles created to be created. Defaults to false. - :type fixed: bool - - :param rho0: background density for the system. Defaults to 1.0 - :type rho0: float - - :param c0: speed of sound for the system. Defaults to 10 - :type c0: float - - :param P0: background pressure for the system. Defaults to 10 - :type p0: float - - :rtype: spatialpy.Domain.Domain - """ - # Create domain object - numberparticles = nx*ny - obj = Domain(numberparticles, xlim, ylim, (0,0), **kwargs) - # Vertices - obj.vertices = numpy.zeros(( int(nx)*int(ny), 3), dtype=float) - x_list = numpy.linspace(xlim[0],xlim[1],nx) - y_list = numpy.linspace(ylim[0],ylim[1],ny) - ndx = 0 - totalvolume = (xlim[1] - xlim[0]) * (ylim[1] - ylim[0]) - #print("totalvolume",totalvolume) - for x in x_list: - for y in y_list: - obj.vol[ndx] = totalvolume / numberparticles - obj.vertices[ndx,0] = x - obj.vertices[ndx,1] = y - obj.vertices[ndx,2] = 0.0 - obj.type[ndx] = type_id - obj.mass[ndx] = mass - obj.nu[ndx] = nu - obj.fixed[ndx] = fixed - ndx+=1 - - # return model ref - return obj
- - - -
[docs]class DomainError(Exception): - pass
-
- -
- - -
-
-
-
- - - - - Fork me on GitHub - - - - - - \ No newline at end of file diff --git a/docs/build/html/_modules/spatialpy/Geometry.html b/docs/build/html/_modules/spatialpy/Geometry.html deleted file mode 100644 index 160e0eda..00000000 --- a/docs/build/html/_modules/spatialpy/Geometry.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - spatialpy.Geometry — SpatialPy 0.5.1 documentation - - - - - - - - - - - - - - - - - - -
- - -
-
- - -
- -

Source code for spatialpy.Geometry

-'''
-SpatialPy is a Python 3 package for simulation of
-spatial deterministic/stochastic reaction-diffusion-advection problems
-Copyright (C) 2021 SpatialPy developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU GENERAL PUBLIC LICENSE Version 3 as
-published by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU GENERAL PUBLIC LICENSE Version 3 for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-'''
-
-
[docs]class Geometry: - """ Geometry class provides a method for tagging parts of the spatial domain as separate parts""" - - def __init__(self): - pass - -
[docs] def inside(self, x, on_boundary): - raise Exception("Subclasses of spatialpy.Geometry must implement the inside() method")
-
- -
- - -
-
-
-
- - - - - Fork me on GitHub - - - - - - \ No newline at end of file diff --git a/docs/build/html/_modules/spatialpy/Model.html b/docs/build/html/_modules/spatialpy/Model.html deleted file mode 100644 index 8c94ef29..00000000 --- a/docs/build/html/_modules/spatialpy/Model.html +++ /dev/null @@ -1,1072 +0,0 @@ - - - - - - - - spatialpy.Model — SpatialPy 0.5.1 documentation - - - - - - - - - - - - - - - - - - -
- - -
-
- - -
- -

Source code for spatialpy.Model

-'''
-SpatialPy is a Python 3 package for simulation of
-spatial deterministic/stochastic reaction-diffusion-advection problems
-Copyright (C) 2021 SpatialPy developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU GENERAL PUBLIC LICENSE Version 3 as
-published by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU GENERAL PUBLIC LICENSE Version 3 for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-'''
-
-#This module defines a model that simulates a discrete, stoachastic, mixed biochemical reaction network in python.
-
-import uuid
-from collections import OrderedDict
-from spatialpy.Solver import Solver
-import numpy
-import scipy
-import warnings
-import math
-
-
-
[docs]def export_StochSS(spatialpy_model, filename=None, return_stochss_model=False): - """ - SpatialPy model to StochSS converter - - :param spatailpy_model: SpatialPy model to be converted to StochSS - :type spatialpy_model: spatialpy.Model - :param filename: Path to the exported stochss model - :type filename: str - :param return_stochss_model: Whether or not to return the model - :type return_stochss_model: bool - """ - try: - from spatialpy.stochss.StochSSexport import export - except ImportError: - raise ImportError('StochSS export conversion not imported successfully') - - return export(spatialpy_model, path=filename, return_stochss_model=return_stochss_model)
- - -
[docs]class Model(): - """ Representation of a spatial biochemical model. - - :param name: Name of the model - :type name: str - - """ - reserved_names = ['vol'] - special_characters = ['[', ']', '+', '-', '*', '/', '.', '^'] - - - def __init__(self, name=""): - - - # The name that the model is referenced by (should be a String) - self.name = name - - ###################### - # Dictionaries with Species, Reactions and Parameter objects. - # Species, Reaction, and Parameter names are used as keys. - self.listOfParameters = OrderedDict() - self.listOfSpecies = OrderedDict() - self.listOfReactions = OrderedDict() - - ###################### - # Dict that holds flattended parameters and species for - # evaluation of expressions in the scope of the model. - self.namespace = OrderedDict([]) - self.species_map = {} - - ###################### - self.domain = None - self.listOfTypeIDs = [1] # starts with type '1' - self.listOfDiffusionRestrictions = {} - self.listOfDataFunctions = [] - self.listOfInitialConditions = [] - self.listOfBoundaryConditions = [] - - ###################### - self.staticDomain = True - self.enable_rdme = True - self.enable_pde = True #TODO - - ###################### - self.tspan = None - self.timestep_size = 1e-5 - self.num_timesteps = None - self.output_freq = None - - - def __str__(self): - divider = f"\n{'*'*10}\n" - - def decorate(header): - return f"\n{divider}{header}{divider}" - - print_string = f"{self.name}" - if len(self.listOfSpecies): - print_string += decorate("Species") - for _, species in self.listOfSpecies.items(): - print_string += f"\n{str(species)}" - if len(self.listOfInitialConditions): - print_string += decorate("Initial Conditions") - for initial_condition in self.listOfInitialConditions: - print_string += f"\n{str(initial_condition)}" - if len(self.listOfDiffusionRestrictions): - print_string += decorate("Diffusion Restrictions") - for species, types in self.listOfDiffusionRestrictions.items(): - print_string += f"\n{species.name} is restricted to: {str(types)}" - if len(self.listOfParameters): - print_string += decorate("Parameters") - for _, parameter in self.listOfParameters.items(): - print_string += f"\n{str(parameter)}" - if len(self.listOfReactions): - print_string += decorate("Reactions") - for _, reaction in self.listOfReactions.items(): - print_string += f"\n{str(reaction)}" - if self.domain is not None: - print_string += decorate("Domain") - print_string += f"\n{str(self.domain)}" - - return print_string - - -
[docs] def run(self, number_of_trajectories=1, seed=None, timeout=None, number_of_threads=None, debug_level=0, debug=False, profile=False): - """ Simulate the model. Returns a result object containing simulation results. - - :param number_of_trajectories: How many trajectories should be run. - :type number_of_trajectories: int - :param seed: The random seed given to the solver. - :type seed: int - :param number_of_threads: The number threads the solver will use. - :type number_of_threads: int - :param debug_level: Level of output from the solver: 0, 1, or 2. Default: 0. - :type debug_level: int - - :rtype: spatialpy.Result.Result - """ - - sol = Solver(self, debug_level=debug_level) - - return sol.run(number_of_trajectories=number_of_trajectories, seed=seed, timeout=timeout, - number_of_threads=number_of_threads, debug=debug, profile=profile)
- - -
[docs] def set_timesteps(self, step_size, num_steps): - """" Set the simlation time span parameters. Note: the number of output times will be num_steps+1 as the first - output will be at time zero. - - :param step_size: Size of each timestep in seconds - :type step_size: float - :param num_steps: Total number of steps to take - :type num_steps: int - - """ - if self.timestep_size is None: - raise ModelError("timestep_size is not set") - - self.output_freq = math.ceil(step_size/self.timestep_size) - - self.num_timesteps = math.ceil(num_steps * self.output_freq) - # array of step numbers corresponding to the simulation times in the timespan - self.timespan_steps = numpy.linspace(0,self.num_timesteps, - num=math.ceil(self.num_timesteps/self.output_freq)+1, dtype=int)
- -
[docs] def timespan(self, time_span, timestep_size=None): - """ - Set the time span of simulation. The SSA-SDPD engine does not support - non-uniform timespans. - - :param tspan: Evenly-spaced list of times at which to sample the species populations during the simulation. - :type tspan: numpy.ndarray - """ - - self.tspan = time_span - if timestep_size is not None: - self.timestep_size = timestep_size - - items_diff = numpy.diff(time_span) - items = map(lambda x: round(x, 10), items_diff) - isuniform = (len(set(items)) == 1) - - if isuniform: - self.set_timesteps( items_diff[0], len(items_diff) ) - else: - raise ModelError("Only uniform timespans are supported")
- - - -
[docs] def set_type(self, geometry_ivar, type_id, mass=None, nu=None, fixed=False): - """ Add a type definition to the model. By default, all regions are set to - type 0. Returns the number of domain points that were tagged with this type_id - - :param geometry_ivar: an instance of a 'spatialpy.Geometry' subclass. The 'inside()' method - of this object will be used to assign type_id to points. - :type geometry_ivar: spatialpy.Geometry.Geometry - :param type_id: (usually an int) the identifier for this type - :type type_id: int - :param mass: The mass of each particle in the type - :type mass: float - :param nu: The viscosity of each particle in the type - :type nu: float - :param fixed: Are the particles in this type immobile - :type fixed: bool - - :rtype: int - """ - - if self.domain is None: - raise Exception("SpatialPy models must have a domain before types can be attached"); - if type_id not in self.listOfTypeIDs: - # index is the "particle type", value is the "type ID" - self.listOfTypeIDs.append(type_id) - # apply the type to all points, set type for any points that match - count = 0 - on_boundary = self.domain.find_boundary_points() - for v_ndx in range(self.domain.get_num_voxels()): - if geometry_ivar.inside( self.domain.coordinates()[v_ndx,:], on_boundary[v_ndx]): - self.domain.type[v_ndx] = type_id - if (mass is not None): - self.domain.mass[v_ndx] = mass - if (nu is not None): - self.domain.nu[v_ndx] = nu - self.domain.fixed[v_ndx] = fixed - count +=1 - if count == 0: - warnings.warn("Type with type_id={0} has zero particles in it".format(type_id)) - return count
- -
[docs] def restrict(self, species, listOfTypes): - """ Set the diffusion coefficient to zero for 'species' in all types not in - 'listOfTypes'. This effectively restricts the movement of 'species' to - the types specified in 'listOfTypes'. - - :param species: Target species to restrict - :type species: spatialpy.Model.Species - :param listOfTypes: a list, each object in the list should be a 'type_id' - :type listOfTypes: list(int) - """ - #x = Species() - #if not isinstance(species, Species): - #if str(type(species)) != 'Species': - # raise ModelError("First argument to restrict() must be a Species object, not {0}".format(str(type(species)))) - if not isinstance(listOfTypes,list): - self.listOfDiffusionRestrictions[species] = [listOfTypes] - else: - self.listOfDiffusionRestrictions[species] = listOfTypes
- -
[docs] def add_domain(self, domain): - ''' - Add a spatial domain to the model - - :param domain: The Domain object to be added to the model - :type domain: spatialpy.Domain.Domain - ''' - if type(domain).__name__ != 'Domain': - raise ModelError("Unexpected parameter for add_domain. Parameter must be a Domain.") - - self.domain = domain - self.listOfTypeIDs = list(set(domain.type))
- -
[docs] def add_data_function(self, data_function): - """ Add a scalar spatial function to the simulation. This is useful if you have a - spatially varying in put to your model. Argument is a instances of subclass of the - spatialpy.DataFunction class. It must implement a function 'map(x)' which takes a - the spatial positon 'x' as an array, and it returns a float value. - - :param data_function: Data function to be added. - :type data_function: spatialpy.DataFunction.DataFunction - """ - self.listOfDataFunctions.append(data_function)
- -
[docs] def add_initial_condition(self, ic): - """ Add an initial condition object to the initialization of the model. - - :param ic: Initial condition to be added - :type ic: spatialpy.InitialCondition.InitialCondition - - """ - self.listOfInitialConditions.append(ic)
- -
[docs] def add_boundary_condition(self, bc): - """ Add an BoundaryCondition object to the model. - - :param bc: Boundary condition to be added - :type bc: spatialpy.BoundaryCondition.BoundaryCondition - - """ - bc.model = self - self.listOfBoundaryConditions.append(bc)
- -
[docs] def update_namespace(self): - """ Create a dict with flattened parameter and species objects. """ - - for param in self.listOfParameters: - self.namespace[param]=self.listOfParameters[param].value - # Dictionary of expressions that can be evaluated in the scope of this model. - self.expressions = {}
- -
[docs] def get_species(self, sname): - """ Returns target species from model as object. - - :param sname: name of species to be returned - :type sname: str - - :rtype: spatialpy.Model.Species - - """ - return self.listOfSpecies[sname]
- -
[docs] def get_num_species(self): - """ Returns total number of species contained in the model. - - :rtype: int - - """ - return len(self.listOfSpecies)
- -
[docs] def get_all_species(self): - """ Returns a dictionary of all species in the model using names as keys. - - :rtype: dict - - """ - return self.listOfSpecies
- -
[docs] def add_species(self, obj): - """ - Adds a species, or list of species to the model. Will return the added object upon success. - - :param obj: The species or list of species to be added to the model object. - :type obj: spatialpy.Model.Species | list(spatialpy.Model.Species - - :rtype: spatialpy.Model.Species | list(spatialpy.Model.Species - """ - - - if isinstance(obj, list): - for S in obj: - self.add_species(S) - elif type(obj).__name__ == 'Species': - problem = self.__problem_with_name(obj.name) - if problem is not None: - raise problem - self.species_map[obj] = len(self.listOfSpecies) - self.listOfSpecies[obj.name] = obj - else: - raise ModelError("Unexpected parameter for add_species. Parameter must be Species or list of Species.") - return obj
- - -
[docs] def delete_species(self, obj): - """ Remove a Species from model.listOfSpecies. - - :param obj: Species object to be removed - :type obj: spatialpy.Model.Species - - """ - self.listOfSpecies.pop(obj)
- -
[docs] def delete_all_species(self): - """ Remove all species from model.listOfSpecies. - """ - - self.listOfSpecies.clear()
- -
[docs] def get_parameter(self,pname): - """ Remove a Parameter from model.listOfParameters. - - :param pname: Name of parameter to be removed - :type pname: spatialpy.Model.Parameter - - """ - try: - return self.listOfParameters[pname] - except: - raise ModelError("No parameter named "+pname)
- -
[docs] def get_all_parameters(self): - """ Return a dictionary of all model parameters, indexed by name. - - :rtype: dict - - """ - - return self.listOfParameters
- - def __problem_with_name(self, name): - if name in Model.reserved_names: - return ModelError('Name "{}" is unavailable. It is reserved for internal SpatialPy use. Reserved Names: ({}).'.format(name, Model.reserved_names)) - if name in self.listOfSpecies: - return ModelError('Name "{}" is unavailable. A species with that name exists.'.format(name)) - if name in self.listOfParameters: - return ModelError('Name "{}" is unavailable. A parameter with that name exists.'.format(name)) - if name.isdigit(): - return ModelError('Name "{}" is unavailable. Names must not be numeric strings.'.format(name)) - for special_character in Model.special_characters: - if special_character in name: - return ModelError('Name "{}" is unavailable. Names must not contain special characters: {}.'.format(name, Model.special_characters)) - - - -
[docs] def add_parameter(self,params): - """ Add Parameter(s) to model.listOfParameters. Input can be either a single - Parameter object or a list of Parameter objects. - - :param params: Parameter object or list of Parameters to be added. - :type params: spatialpy.Model.Parameter | list(spatialpy.Model.Parameter) - """ - if isinstance(params,list): - for p in params: - self.add_parameter(p) - else: - #if isinstance(params, type(Parameter())): - x = Parameter() - if str(type(params)) == str(type(x)): - problem = self.__problem_with_name(params.name) - if problem is not None: - raise problem - # make sure that you don't overwrite an existing parameter?? - if params.name in self.listOfParameters.keys(): - raise ParameterError("Parameter '{0}' has already been added to the model.".format(params.name)) - self.listOfParameters[params.name] = params - else: - #raise ParameterError("Could not resolve Parameter expression {} to a scalar value.".format(params)) - raise ParameterError("Parameter '{0}' needs to be of type '{2}', it is of type '{1}'".format(params.name,str(type(params)),str(type(x)))) - return params
- -
[docs] def delete_parameter(self, obj): - self.listOfParameters.pop(obj)
- -
[docs] def set_parameter(self,pname,expression): - """ Set the expression of an existing paramter. - - :param pname: Name of target parameter for expression - :type pname: str - :param expression: math expression to be assigned to target parameter - :type expression: str - - """ - p = self.listOfParameters[pname] - p.expression = expression - p.__evaluate()
- - def _resolve_parameters(self): - """ Attempt to resolve all parameter expressions to scalar floating point values. - Must be called prior to exporting the model. """ - self.update_namespace() - for param in self.listOfParameters: - try: - self.listOfParameters[param].__evaluate(self.namespace) - except: - raise ParameterError("Could not resolve Parameter expression "+param + "to a scalar value.") - -
[docs] def delete_all_parameters(self): - """ Remove all parameters from model.listOfParameters - """ - - self.listOfParameters.clear()
- -
[docs] def add_reaction(self,reacs): - """ Add Reaction(s) to the model. Input can be single instance, a list of instances - or a dict with name, instance pairs. - - :param reacs: Reaction or list of Reactions to be added. - :type reacs: spatialpy.Model.Reaction | list(spatialpy.Model.Reaction) - - """ - if isinstance(reacs, list): - for r in reacs: - r.initialize(self) - if r.name is None or r.name == "": - r.name = 'rxn' + str(uuid.uuid4()).replace('-', '_') - self.listOfReactions[r.name] = r - elif type(reacs).__name__ == "Reaction": - reacs.initialize(self) - if reacs.name is None or reacs.name == "": - reacs.name = 'rxn' + str(uuid.uuid4()).replace('-', '_') - self.listOfReactions[reacs.name] = reacs - else: - raise ModelError("add_reaction() takes a spatialpy.Reaction object or list of objects")
- -
[docs] def get_reaction(self, rname): - """ Retrieve a reaction object from the model by name - - :param rname: name of Reaction to retrieve - :type rname: str - - :rtype: spatialpy.Model.Reaction - - """ - return self.listOfReactions[rname]
- -
[docs] def get_num_reactions(self): - """ Returns the number of reactions in this model. - - :rtype: int - """ - return len(self.listOfReactions)
- -
[docs] def get_all_reactions(self): - """ Returns a dictionary of all model reactions using names as keys. - - :rtype: dict - """ - - return self.listOfReactions
- -
[docs] def delete_reaction(self, obj): - """ Remove reaction from model.listOfReactions - - :param obj: Reaction to be removed. - :type obj: spatialpy.Model.Reaction - - """ - self.listOfReactions.pop(obj)
- -
[docs] def delete_all_reactions(self): - """ Remove all reactions from model.listOfReactions - """ - self.listOfReactions.clear()
- - def __ne__(self, other): - return not self.__eq__(other) - - def __eq__(self, other): - return (self.listOfParameters == other.listOfParameters and \ - self.listOfSpecies == other.listOfSpecies and \ - self.listOfReactions == other.listOfReactions and \ - self.name == other.name) - - def _create_stoichiometric_matrix(self): - """ Generate a stoichiometric matrix in sparse CSC format. """ - - if self.get_num_reactions() > 0: - ND = numpy.zeros((self.get_num_species(), self.get_num_reactions())) - for i, r in enumerate(self.listOfReactions): - R = self.listOfReactions[r] - reactants = R.reactants - products = R.products - - for s in reactants: - ND[self.species_map[s], i] -= reactants[s] - for s in products: - ND[self.species_map[s], i] += products[s] - - N = scipy.sparse.csc_matrix(ND) - else: - N = numpy.zeros((self.get_num_species(), self.get_num_reactions())) - - return N - - - def _create_dependency_graph(self): - """ Construct the sparse dependency graph. """ - # We cannot safely generate a dependency graph (without attempting to analyze the - # propensity string itself) if the model contains custom propensities. - mass_action_model = True - for name, reaction in self.listOfReactions.items(): - if not reaction.massaction: - GF = numpy.ones((self.get_num_reactions(), - self.get_num_reactions() + self.get_num_species())) - mass_action_model = False - - if mass_action_model: - GF = numpy.zeros((self.get_num_reactions(), - self.get_num_reactions() + self.get_num_species())) - species_map = self.species_map - - involved_species = [] - reactants = [] - for name, reaction in self.listOfReactions.items(): - temp = [] - temp2 = [] - for s in reaction.reactants: - temp.append(species_map[s]) - temp2.append(species_map[s]) - for s in reaction.products: - temp.append(species_map[s]) - involved_species.append(temp) - reactants.append(temp2) - - species_to_reactions = [] - for sname,species in self.listOfSpecies.items(): - temp = [] - for j, x in enumerate(reactants): - if species_map[species] in x: - temp.append(j) - species_to_reactions.append(temp) - - reaction_to_reaction = [] - for name, reaction in self.listOfReactions.items(): - temp = [] - for s in reaction.reactants: - if species_to_reactions[species_map[s]] not in temp: - temp = temp+species_to_reactions[species_map[s]] - - for s in reaction.products: - if species_to_reactions[species_map[s]] not in temp: - temp = temp+ species_to_reactions[species_map[s]] - - temp = list(set(temp)) - reaction_to_reaction.append(temp) - - # Populate G - for j, spec in enumerate(species_to_reactions): - for s in spec: - GF[s, j] = 1 - - for i,reac in enumerate(reaction_to_reaction): - for r in reac: - GF[r, self.get_num_species()+i] = 1 - - - try: - G = scipy.sparse.csc_matrix(GF) - except Exception as e: - G = GF - - return G - - def _apply_initial_conditions(self): - """ Initalize the u0 matrix (zeros) and then apply each initial condition""" - # initalize - ns = self.get_num_species() - nv = self.domain.get_num_voxels() - self.u0 = numpy.zeros((ns, nv)) - # apply initial condition functions - for ic in self.listOfInitialConditions: - ic.apply(self)
- - - - - -
[docs]class Species(): - """ Model of a biochemical species. Must be assigned a diffusion coefficent. - - :param name: Name of the Species - :type name: str - :param diffusion_coefficient: non-constant coefficient of diffusion for Species - :type diffusion_coefficient: float - """ - - def __init__(self,name=None, diffusion_coefficient=None): - # A species has a name (string) and an initial value (positive integer) - if name is None: - raise ModelError("Species must have a name") - else: - self.name = name - if diffusion_coefficient is not None: - self.diffusion_coefficient=diffusion_coefficient - else: - raise ModelError("Species must have a diffusion_coefficient") - - - def __str__(self): - print_string = f"{self.name}: {str(self.diffusion_coefficient)}" - return print_string
- -
[docs]class Parameter(): - """ - Model of a rate paramter. - A parameter can be given as a String expression (function) or directly as a scalar value. - If given a String expression, it should be evaluable in the namespace of a parent Model. - - :param name: Name of the Parameter - :type name: str - :param expression: Mathematical expression of Parameter - :type expression: str - :param value: Parameter as value rather than expression. - :type value: float - - """ - - def __init__(self,name="",expression=None,value=None): - - self.name = name - # We allow expression to be passed in as a non-string type. Invalid strings - # will be caught below. It is perfectly fine to give a scalar value as the expression. - # This can then be evaluated in an empty namespace to the scalar value. - self.expression = expression - if expression != None: - self.expression = str(expression) - - self.value = value - - # self.value is allowed to be None, but not self.expression. self.value - # might not be evaluable in the namespace of this parameter, but defined - # in the context of a model or reaction. - if self.expression == None: - #raise TypeError - self.value = 0 - - if self.value is None: - self.__evaluate() - - def __evaluate(self,namespace={}): - """ Evaluate the expression and return the (scalar) value """ - try: - self.value = (float(eval(self.expression, namespace))) - except: - self.value = None - -
[docs] def set_expression(self,expression): - """ Sets the Parameters expression - - :param expression: Expression to be set for Parameter - :type expression: str - """ - self.expression = expression - # We allow expression to be passed in as a non-string type. Invalid strings - # will be caught below. It is perfectly fine to give a scalar value as the expression. - # This can then be evaluated in an empty namespace to the scalar value. - if expression is not None: - self.expression = str(expression) - - if self.expression is None: - raise TypeError - - self.__evaluate()
- - def __str__(self): - print_string = f"{self.name}: {str(self.expression)}" - return print_string
- - -
[docs]class Reaction(): - """ - Models a biochemical reaction. A reaction conatains dictionaries of species (reactants and products) \ - and parameters. The reaction's propensity function needs to be evaluable and result in a \ - non-negative scalar value in the namespace defined by the union of its Reactant, Product and \ - Parameter dictionaries. If massaction is set to true, propensity_function is not a valid argument. \ - Instead, the propensity function is constructed automatically. For mass-action, zeroth, first \ - and second order reactions are supported, attempting to used higher orders will result in an error. - - :param name: String that the model is referenced by - :type name: str - :param parameters: A list of parameter instances - :type parameters: list(spatialpy.Model.Parameter) - :param propensity_function: String with the expression for the reaction's propensity - :type propensity_function: str - :param reactants: Dictionary of {species:stoichiometry} of reaction reactants - :type reactants: dict - :param products: Dictionary of {species:stoichiometry} of reaction products - :type products: dict - :param annotation: Description of the reaction (meta) - :type annotation: str - :param massaction: Is the reaction of mass action type or not? - :type massaction: bool - :param rate: if mass action, rate is a reference to a parameter instance. - :type rate: spatialpy.model.Parameter - - - """ - - def __init__(self, name = "", reactants = {}, products = {}, propensity_function=None, massaction=None, rate=None, annotation=None,restrict_to=None): - - # Metadata - self.name = name - self.reactants = reactants - self.products = products - self.propensity_function = propensity_function - self.massaction = massaction - self.rate = rate - self.annotation = annotation - self.restrict_to = restrict_to - -
[docs] def initialize(self, model): - """ Defered object initialization, called by model.add_reaction(). """ - - self.ode_propensity_function = self.propensity_function - - if self.propensity_function is None: - if self.rate is None: - errmsg = f"Reaction {name}: You must either set the reaction to be mass-action or specifiy a propensity function." - raise ReactionError(errmsg) - self.massaction = True - else: - if self.rate is not None: - errmsg = f"Reaction {name}: You cannot set the propensity type to mass-action and simultaneously set a propensity function." - raise ReactionError(errmsg) - # If they don't give us a propensity function and do give a rate, assume mass-action. - self.massaction = False - self.marate = None - - - reactants = self.reactants - self.reactants = {} - if reactants is not None: - for r in reactants: - rtype = type(r).__name__ - if rtype=='Species': - self.reactants[r]=reactants[r] - elif rtype=='str': - if r not in model.listOfSpecies: - raise ReactionError(f"Could not find species '{r}' in model.") - self.reactants[model.listOfSpecies[r]] = reactants[r] - - products = self.products - self.products = {} - if products is not None: - for p in products: - rtype = type(p).__name__ - if rtype=='Species': - self.products[p]=products[p] - else: - if p not in model.listOfSpecies: - raise ReactionError(f"Could not find species '{p}' in model.") - self.products[model.listOfSpecies[p]] = products[p] - - if self.massaction: - self.type = "mass-action" - rtype = type(self.rate).__name__ - if rtype == 'Parameter': - self.marate = self.rate.name - elif rtype == 'int' or rtype == 'float': - self.marate = str(self.rate) - else: - self.marate = self.rate - self.create_mass_action() - else: - self.type = "customized"
- - - def __str__(self): - print_string = f"{self.name}, Active in: {str(self.restrict_to)}" - if len(self.reactants): - print_string += f"\n\tReactants" - for species, stoichiometry in self.reactants.items(): - name = species if isinstance(species, str) else species.name - print_string += f"\n\t\t{name}: {stoichiometry}" - if len(self.products): - print_string += f"\n\tProducts" - for species, stoichiometry in self.products.items(): - name = species if isinstance(species, str) else species.name - print_string += f"\n\t\t{name}: {stoichiometry}" - print_string += f"\n\tPropensity Function: {self.propensity_function}" - return print_string - - - def __create_mass_reaction(self): - """ Create a mass action propensity function given self.reactants and a single parameter value. - - We support zeroth, first and second order propensities only. - There is no theoretical justification for higher order propensities. - Users can still create such propensities if they really want to, - but should then use a custom propensity. - """ - total_stoch = 0 - for r in self.reactants: - total_stoch += self.reactants[r] - if total_stoch > 2: - raise ReactionError("Reaction: A mass-action reaction cannot involve more than two of one species or one " - "of two species.") - # Case EmptySet -> Y - - if isinstance(self.marate, str): - propensity_function = self.marate - ode_propensity_function = self.marate - else: - propensity_function = self.marate.name - ode_propensity_function = self.marate.name - - # There are only three ways to get 'total_stoch==2': - for r in self.reactants: - # Case 1: 2X -> Y - if self.reactants[r] == 2: - propensity_function = ("0.5*" + propensity_function + - "*" + r.name + "*(" + r.name + "-1)/vol") - else: - # Case 3: X1, X2 -> Y; - propensity_function += "*" + r.name - ode_propensity_function += "*" + r.name - - # Set the volume dependency based on order. - order = len(self.reactants) - if order == 2: - propensity_function += "/vol" - elif order == 0: - propensity_function += "*vol" - - self.propensity_function = propensity_function - self.ode_propensity_function = ode_propensity_function - -
[docs] def set_type(self,type): - """ Set type restriction for this Reaction. - - :param type: Type for this reaction to be restricted to. - :type type: int - - """ - if type not in {'mass-action','customized'}: - raise ReactionError("Invalid reaction type.") - self.type = type
- -
[docs] def add_reactant(self,S,stoichiometry): - """ Add a reactant to this reaction - - :param s: reactant Species object - :type s: spatialpy.Model.Species - :param stoichiometry: Stoichiometry of this participant reactant - :type stoichiometry: int - - """ - if stoichiometry <= 0: - raise ReactionError("Reaction "+self.name+"Stoichiometry must be a positive integer.") - self.reactants[S.name]=stoichiometry
- -
[docs] def add_product(self,S,stoichiometry): - """ Add a product to this reaction - - :param s: Species object to be produced by the reaction - :type s: spatialpy.Model.Species - :param stoichiometry: Stoichiometry of this product. - :type stoichiometry: int - - """ - self.products[S.name]=stoichiometry
- -
[docs] def annotate(self,annotation): - """ Add an annotation to this reaction. - - :param annotation: Annotation note to be added to reaction - :type annotation: str - - """ - self.annotation = annotation
- - -# Module exceptions -
[docs]class ModelError(Exception): - pass
- -
[docs]class SpeciesError(ModelError): - pass
- -
[docs]class ReactionError(ModelError): - pass
- -
[docs]class ParameterError(ModelError): - pass
-
- -
- - -
-
-
-
- - - - - Fork me on GitHub - - - - - - \ No newline at end of file diff --git a/docs/build/html/_modules/spatialpy/Solver.html b/docs/build/html/_modules/spatialpy/Solver.html deleted file mode 100644 index 9b55cc83..00000000 --- a/docs/build/html/_modules/spatialpy/Solver.html +++ /dev/null @@ -1,742 +0,0 @@ - - - - - - - - spatialpy.Solver — SpatialPy 0.5.1 documentation - - - - - - - - - - - - - - - - - - -
- - -
-
- - -
- -

Source code for spatialpy.Solver

-'''
-SpatialPy is a Python 3 package for simulation of
-spatial deterministic/stochastic reaction-diffusion-advection problems
-Copyright (C) 2021 SpatialPy developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU GENERAL PUBLIC LICENSE Version 3 as
-published by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU GENERAL PUBLIC LICENSE Version 3 for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-'''
-
-import os
-import shutil
-import signal
-import subprocess
-import sys
-import tempfile
-import threading
-import time
-import tempfile
-import getpass
-import re
-
-
-from spatialpy.Model import *
-from spatialpy.Result import *
-
-def _read_from_stdout(stdout,verbose=True):
-    ''' Used with subprocess.Popen and threading to capture all output and print
-        to the screen notebook without waiting or storing the output ina buffer.'''
-    try:
-        while True:
-            line = stdout.readline()
-            if line != b'':
-                if verbose:
-                    print(line.decode(),end='')
-            else:
-                #got empty line, ending
-                return
-    except Exception as e:
-        print("_read_from_stdout(): {0}".format(e))
-
-
-
[docs]class Solver: - """ SpatialPy solver object. - - :param model: Target model of solver simulation - :type model: spatialpy.Model.Model - :param debug_level: Target level of debugging - :type debug_level: int - """ - - def __init__(self, model, debug_level=0): - """ Constructor - """ - - # TODO: fix class checking - # if not isinstance(model, Model): - # raise SimulationError("Solver constructors must take a Model as an argument.") - # if not issubclass(self.__class__, Solver): - # raise SimulationError("Solver classes must be a subclass of SpatialPy.Solver.") - - self.model = model - self.is_compiled = False - self.debug_level = debug_level - self.model_name = self.model.name - self.build_dir = None - self.executable_name = 'ssa_sdpd' - self.h = None # basis function width - - self.SpatialPy_ROOT = os.path.dirname( - os.path.abspath(__file__))+"/ssa_sdpd-c-simulation-engine" - self.SpatialPy_ROOTDIR = self.SpatialPy_ROOT.replace(" ","\\ "); - self.SpatialPy_ROOTINC = self.SpatialPy_ROOT.replace(" ","\\\\ "); - self.SpatialPy_ROOTPARAM = self.SpatialPy_ROOT.replace(" ","?"); - #print("SpatialPy_ROOTDIR = "+self.SpatialPy_ROOTDIR) - #print("SpatialPy_ROOTPARAM = "+self.SpatialPy_ROOTPARAM) - - tmpdir = tempfile.gettempdir() - self.core_dir = os.path.join(os.path.join(tmpdir, 'spatialpy_core'), getpass.getuser()) - - if not os.path.isdir(os.path.join(tmpdir, 'spatialpy_core')): - os.mkdir(os.path.join(tmpdir, 'spatialpy_core')) - if not os.path.isdir(self.core_dir): - os.mkdir(self.core_dir) - - def __del__(self): - """ Deconstructor. Removes the compiled solver.""" - try: - if self.build_dir is not None: - try: - shutil.rmtree(self.build_dir) - except OSError as e: - print("Could not delete '{0}'".format( - self.solver_base_dir)) - except Exception as e: - pass - - def __run_debugger(self): - """ Start a gdbgui debugger at port 5000. """ - self.debugger_url = 'http://127.0.0.1:5000' - if not hasattr(self, 'debugger_process'): - self.debugger_process = subprocess.Popen('gdbgui -r', shell=True) - print(f'Your debugger is running at {self.debugger_url}') - -
[docs] def compile(self, debug=False, profile=False): - """ Compile the model. - - :param debug: If True, will print additional build debugging - :type debug: bool - :param profile: If True, will print additional profiling information - :type profile: bool - """ - - # Create a unique directory each time call to compile. - self.build_dir = tempfile.mkdtemp( - prefix='spatialpy_build_', dir=os.environ.get('SPATIALPY_TMPDIR')) - - if self.debug_level >= 1: - print("Compiling Solver. Build dir: {0}".format(self.build_dir)) - - # Write the propensity file - self.propfilename = re.sub('[^\w\_]', '', self.model_name) # Match except word characters \w = ([a-zA-Z0-9_]) and \_ = _ replace with '' - self.propfilename = self.propfilename + '_generated_model' - self.prop_file_name = self.build_dir + '/' + self.propfilename + '.c' - if self.debug_level > 1: - print("Creating propensity file {0}".format(self.prop_file_name)) - self.__create_propensity_file(file_name=self.prop_file_name) - - # Build the solver - makefile = self.SpatialPy_ROOTDIR+'/build/Makefile' - cmd_list = ['cd', self.core_dir, '&&', 'make', 'CORE', '-f', makefile, - 'ROOT="' + self.SpatialPy_ROOTPARAM+'"', - 'ROOTINC="' + self.SpatialPy_ROOTINC+'"', - 'BUILD='+self.core_dir, '&&' - 'cd', self.build_dir, '&&', 'make', '-I', self.core_dir, '-f', makefile, - 'ROOT="' + self.SpatialPy_ROOTPARAM+'"', - 'ROOTINC="' + self.SpatialPy_ROOTINC+'"', - 'COREDIR="' + self.core_dir + '"', - 'MODEL=' + self.prop_file_name, 'BUILD='+self.build_dir] - if profile: - cmd_list.append('GPROFFLAG=-pg') - if profile or debug: - cmd_list.append('GDB_FLAG=-g') - cmd = " ".join(cmd_list) - if self.debug_level > 1: - print("cmd: {0}\n".format(cmd)) - try: - handle = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, shell=True) - stdout, _ = handle.communicate() - return_code = handle.wait() - if return_code != 0: - try: - print(stdout.decode("utf-8")) - except Exception as e: - pass - raise SimulationError( - "Compilation of solver failed, return_code={0}".format( - return_code)) - - if self.debug_level > 1: - print(stdout.decode("utf-8")) - - except OSError as e: - print("Error, execution of compilation raised an exception: {0}".format( - e)) - print("cmd = {0}".format(cmd)) - raise SimulationError("Compilation of solver failed") - - self.is_compiled = True
- - -
[docs] def run(self, number_of_trajectories=1, seed=None, timeout=None, - number_of_threads=None, debug=False, profile=False, verbose=True): - """ Run one simulation of the model. - - :param number_of_trajectories: How many trajectories should be simulated. - :type number_of_trajectories: int - :param seed: the random number seed (incremented by one for multiple runs). - :type seed: int - :param timeout: maximum number of seconds the solver can run. - :type timeout: int - :param number_of_threads: the number threads the solver will use. - :type number_of_threads: int - :param debug: start a gdbgui debugger (also compiles with debug symbols - if compilation hasn't happened) - :type debug: bool - :param profile: Output gprof profiling data if available - :type profile: bool - - :rtype: spatialpy.Result.Result | list(spatialpy.Result.Result) - - """ - if number_of_trajectories > 1: - result_list = [] - # Check if compiled, call compile() if not. - if not self.is_compiled: - self.compile(debug=debug, profile=profile) - - # Execute the solver - for run_ndx in range(number_of_trajectories): - outfile = tempfile.mkdtemp( - prefix='spatialpy_result_', dir=os.environ.get('SPATIALPY_TMPDIR')) - result = Result(self.model, outfile) - solver_cmd = 'cd {0}'.format( - outfile) + ";" + os.path.join(self.build_dir, self.executable_name) - - if number_of_threads is not None: - solver_cmd += " -t " + str(number_of_threads) - - if seed is not None: - solver_cmd += " -s "+str(seed+run_ndx) - - if self.debug_level > 1: - print('cmd: {0}\n'.format(solver_cmd)) - - start = time.monotonic() - return_code = None - try: - with subprocess.Popen(solver_cmd, shell=True, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - start_new_session=True) as process: - try: - # start thread to read process stdout to stdout - t = threading.Thread(target=_read_from_stdout, - args=(process.stdout,verbose,)) - t.start() - if timeout is not None: - return_code = process.wait(timeout=timeout) - else: - return_code = process.wait() - t.join() - except KeyboardInterrupt: - # send signal to the process group - os.killpg(process.pid, signal.SIGINT) - print('Terminated by user after seconds: {:.2f}'.format( - time.monotonic() - start)) - except subprocess.TimeoutExpired: - result.timeout = True - # send signal to the process group - os.killpg(process.pid, signal.SIGINT) - raise SimulationTimeout("SpatialPy solver timeout exceded.") - except OSError as e: - print("Error, execution of solver raised an exception: {0}".format( - e)) - print("cmd = {0}".format(solver_cmd)) - - if self.debug_level >= 1: # output time - print('Elapsed seconds: {:.2f}'.format( - time.monotonic() - start)) - - if return_code is not None and return_code != 0: - print("solver_cmd = {0}".format(solver_cmd)) - raise SimulationError( - "Solver execution failed, return code = {0}".format(return_code)) - - result.success = True - if profile: - self.__read_profile_info(result) - if number_of_trajectories > 1: - result_list.append(result) - else: - return result - - return result_list
- - def __read_profile_info(self, result): - profile_data_path = os.path.join(result.result_dir, 'gmon.out') - exe_path = os.path.join(self.build_dir, self.executable_name) - cmd = f'gprof {exe_path} {profile_data_path}' - print(cmd) - proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) - stdout, stderr = proc.communicate() - proc.wait() - print(f'Gprof report for {result.result_dir}') - print(stdout.decode('utf-8')) - - def __create_propensity_file(self, file_name=None): - """ Generate the C propensity file that is used to compile the solvers. - """ - - num_types = len(self.model.listOfTypeIDs) - if self.model.enable_pde: - num_chem_species = len(self.model.listOfSpecies) - num_chem_rxns = len(self.model.listOfReactions) - else: - num_chem_species = 0 - num_chem_rxns = 0 - if self.model.enable_rdme: - num_stoch_species = len(self.model.listOfSpecies) - num_stoch_rxns = len(self.model.listOfReactions) - else: - num_stoch_species = 0 - num_stoch_rxns = 0 - num_data_fn = len(self.model.listOfDataFunctions) - - - template = open(os.path.abspath(os.path.dirname( - __file__)) + '/ssa_sdpd-c-simulation-engine/propensity_file_template.cpp', 'r') - propfile = open(file_name, "w") - propfilestr = template.read() - - speciesdef = "" - i = 0 - for S in self.model.listOfSpecies: - speciesdef += "#define " + S + " " + "x[" + str(i) + "]" + "\n" - i += 1 - - propfilestr = propfilestr.replace("__DEFINE_SPECIES__", speciesdef) - - propfilestr = propfilestr.replace( - "__NUMBER_OF_REACTIONS__", str(self.model.get_num_reactions())) - propfilestr = propfilestr.replace( - "__NUMBER_OF_SPECIES__", str(self.model.get_num_species())) - propfilestr = propfilestr.replace( - "__NUMBER_OF_VOXELS__", str(self.model.domain.get_num_voxels())) - - # Make sure all paramters are evaluated to scalars before we write them to the file. - self.model._resolve_parameters() - parameters = "" - for p in self.model.listOfParameters: - parameters += "const double " + p + " = " + \ - str(self.model.listOfParameters[p].value) + ";\n" - propfilestr = propfilestr.replace( - "__DEFINE_PARAMETERS__", str(parameters)) - - # Reactions - funheader = "double __NAME__(const int *x, double t, const double vol, const double *data_fn, int sd)" - - funcs = "" - funcinits = "" - i = 0 - for R in self.model.listOfReactions: - func = "" - rname = self.model.listOfReactions[R].name - func += funheader.replace("__NAME__", rname) + "\n{\n" - if self.model.listOfReactions[R].restrict_to == None or (isinstance(self.model.listOfReactions[R].restrict_to, list) and len(self.model.listOfReactions[R].restrict_to) == 0): - func += "return " - func += self.model.listOfReactions[R].propensity_function - func += ";" - else: - func += "if(" - if isinstance(self.model.listOfReactions[R].restrict_to, list) and len(self.model.listOfReactions[R].restrict_to) > 0: - for sd in self.model.listOfReactions[R].restrict_to: - func += "sd == " + str(sd) + "||" - func = func[:-2] - elif isinstance(self.model.listOfReactions[R].restrict_to, int): - func += "sd == " + \ - str(self.model.listOfReactions[R].restrict_to) - else: - raise SimulationError( - "When restricting reaction to types, you must specify either a list or an int") - func += "){\n" - func += "return " - func += self.model.listOfReactions[R].propensity_function - func += ";" - func += "\n}else{" - func += "\n\treturn 0.0;}" - - func += "\n}" - funcs += func + "\n\n" - funcinits += " ptr[" + \ - str(i) + "] = (PropensityFun) " + rname + ";\n" - i += 1 - - propfilestr = propfilestr.replace("__DEFINE_REACTIONS__", funcs) - propfilestr = propfilestr.replace("__DEFINE_PROPFUNS__", funcinits) - - # TODO make deterministic chemical reaction functions work - funheader = "double det__NAME__(const double *x, double t, const double vol, const double *data_fn, int sd)" - deterministic_chem_rxn_functions = "" - deterministic_chem_rxn_function_init = "" - ############################################ - i = 0 - for R in self.model.listOfReactions: - func = "" - rname = self.model.listOfReactions[R].name - func += funheader.replace("__NAME__", rname) + "\n{\n" - if self.model.listOfReactions[R].restrict_to == None or (isinstance(self.model.listOfReactions[R].restrict_to, list) and len(self.model.listOfReactions[R].restrict_to) == 0): - func += "return " - func += self.model.listOfReactions[R].ode_propensity_function - func += ";" - else: - func += "if(" - if isinstance(self.model.listOfReactions[R].restrict_to, list) and len(self.model.listOfReactions[R].restrict_to) > 0: - for sd in self.model.listOfReactions[R].restrict_to: - func += "sd == " + str(sd) + "||" - func = func[:-2] - elif isinstance(self.model.listOfReactions[R].restrict_to, int): - func += "sd == " + \ - str(self.model.listOfReactions[R].restrict_to) - else: - raise SimulationError( - "When restricting reaction to types, you must specify either a list or an int") - func += "){\n" - func += "return " - func += self.model.listOfReactions[R].ode_propensity_function - func += ";" - func += "\n}else{" - func += "\n\treturn 0.0;}" - - func += "\n}" - deterministic_chem_rxn_functions += func + "\n\n" - deterministic_chem_rxn_function_init += " ptr[" + \ - str(i) + "] = (ChemRxnFun) det" + rname + ";\n" - i += 1 - ############################################ - propfilestr = propfilestr.replace("__DEFINE_CHEM_FUNS__", deterministic_chem_rxn_functions) - propfilestr = propfilestr.replace("__DEFINE_CHEM_FUN_INITS__", deterministic_chem_rxn_function_init) - - # End of pyurdme replacements - # SSA-SDPD values here - init_particles = "" - if self.model.domain.type is None: - self.model.domain.type = numpy.ones(self.model.domain.get_num_voxels()) - for i in range(len(self.model.domain.type)): - if self.model.domain.type[i] == 0: - raise SimulationError( - "Not all particles have been defined in a type. Mass and other properties must be defined") - init_particles += "init_create_particle(sys,id++,{0},{1},{2},{3},{4},{5},{6},{7},{8});".format( - self.model.domain.coordinates()[i,0],self.model.domain.coordinates()[i,1],self.model.domain.coordinates()[i,2], - self.model.domain.type[i],self.model.domain.nu[i],self.model.domain.mass[i], - (self.model.domain.mass[i] / self.model.domain.vol[i]),int(self.model.domain.fixed[i]),num_chem_species )+"\n" - propfilestr = propfilestr.replace("__INIT_PARTICLES__", init_particles) - - # process initial conditions here - self.model._apply_initial_conditions() - nspecies = self.model.u0.shape[0] - ncells = self.model.u0.shape[1] - - input_constants = "" - - outstr = "unsigned int input_u0[{0}] = ".format(nspecies*ncells) - outstr += "{" - if len(self.model.listOfSpecies) > 0: - for i in range(ncells): - for s in range(nspecies): - if i+s > 0: - outstr += ',' - outstr += str(int(self.model.u0[s, i])) - outstr += "};" - input_constants += outstr + "\n" - - data_fn_defs = "" - if len(self.model.listOfSpecies) > 0: - for ndf in range(len(self.model.listOfDataFunctions)): - data_fn_defs += "#define {0} data_fn[{1}]\n".format( - self.model.listOfDataFunctions[ndf].name, ndf) - propfilestr = propfilestr.replace( - "__DATA_FUNCTION_DEFINITIONS__", data_fn_defs) - - if len(self.model.listOfSpecies) > 0: - N = self.model._create_stoichiometric_matrix() - if(min(N.shape) > 0): - Nd = N.todense() # this will not work if Nrxn or Nspecies is zero - outstr = "static int input_N_dense[{0}] = ".format( - Nd.shape[0] * Nd.shape[1]) - outstr += "{" - for i in range(Nd.shape[0]): - for j in range(Nd.shape[1]): - if j+i > 0: - outstr += ',' - outstr += "{0}".format(int(Nd[i, j])) - outstr += "};\n" - outstr += "static size_t input_irN[{0}] = ".format(len(N.indices)) - outstr += "{" - for i in range(len(N.indices)): - if i > 0: - outstr += ',' - outstr += str(N.indices[i]) - outstr += "};" - input_constants += outstr + "\n" - outstr = "static size_t input_jcN[{0}] = ".format(len(N.indptr)) - outstr += "{" - for i in range(len(N.indptr)): - if i > 0: - outstr += ',' - outstr += str(N.indptr[i]) - outstr += "};" - input_constants += outstr + "\n" - outstr = "static int input_prN[{0}] = ".format(len(N.data)) - outstr += "{" - for i in range(len((N.data))): - if i > 0: - outstr += ',' - outstr += str(int(N.data[i])) - outstr += "};" - input_constants += outstr + "\n" - else: - input_constants += "static int input_N_dense[0] = {};\n" - input_constants += "static size_t input_irN[0] = {};\n" - input_constants += "static size_t input_jcN[0] = {};\n" - input_constants += "static int input_prN[0] = {};\n" - - G = self.model._create_dependency_graph() - outstr = "static size_t input_irG[{0}] = ".format(len(G.indices)) - outstr += "{" - for i in range(len(G.indices)): - if i > 0: - outstr += ',' - outstr += str(G.indices[i]) - outstr += "};" - input_constants += outstr + "\n" - - outstr = "static size_t input_jcG[{0}] = ".format(len(G.indptr)) - outstr += "{" - for i in range(len(G.indptr)): - if i > 0: - outstr += ',' - outstr += str(G.indptr[i]) - outstr += "};" - input_constants += outstr + "\n" - if(len(self.model.listOfSpecies)>0): - outstr = "const char* const input_species_names[] = {" - for i, s in enumerate(self.model.listOfSpecies.keys()): - if i > 0: - outstr += "," - outstr += '"'+s+'"' - outstr += ", 0};" - input_constants += outstr + "\n" - num_types = len(self.model.listOfTypeIDs) - outstr = "const int input_num_subdomain = {0};".format(num_types) - input_constants += outstr + "\n" - outstr = "const double input_subdomain_diffusion_matrix[{0}] = ".format( - len(self.model.listOfSpecies)*num_types) - outstr += "{" - for i, sname in enumerate(self.model.listOfSpecies.keys()): - s = self.model.listOfSpecies[sname] - for j, sd_id in enumerate(self.model.listOfTypeIDs): - if i+j > 0: - outstr += ',' - try: - if s not in self.model.listOfDiffusionRestrictions or \ - sd_id in self.model.listOfDiffusionRestrictions[s]: - outstr += "{0}".format(s.diffusion_coefficient) - else: - outstr += "0.0" - except KeyError as e: - print("error: {0}".format(e)) - print(self.model.listOfDiffusionRestrictions) - raise Exception() - - outstr += "};" - input_constants += outstr + "\n" - propfilestr = propfilestr.replace( - "__INPUT_CONSTANTS__", input_constants) - - system_config = "debug_flag = {0};\n".format(self.debug_level) - system_config += "ParticleSystem *system = new ParticleSystem({0},{1},{2},{3},{4},{5});\n".format( - num_types, num_chem_species, num_chem_rxns, - num_stoch_species, num_stoch_rxns, num_data_fn - ) - system_config += "system->static_domain = {0};\n".format(int(self.model.staticDomain)) - if(len(self.model.listOfSpecies) > 0): - system_config += "system->subdomain_diffusion_matrix = input_subdomain_diffusion_matrix;\n" - system_config += "system->stoichiometric_matrix = input_N_dense;\n" - system_config += "system->chem_rxn_rhs_functions = ALLOC_ChemRxnFun();\n" - system_config += "system->stoch_rxn_propensity_functions = ALLOC_propensities();\n" - system_config += "system->species_names = input_species_names;\n"; - - system_config += "system->dt = {0};\n".format(self.model.timestep_size) - system_config += "system->nt = {0};\n".format(self.model.num_timesteps) - system_config += "system->output_freq = {0};\n".format(self.model.output_freq) - if self.h is None: - self.h = self.model.domain.find_h() - if self.h == 0.0: - raise ModelError('h (basis function width) can not be zero.') - system_config +="system->h = {0};\n".format(self.h) - system_config +="system->rho0 = {0};\n".format(self.model.domain.rho0) - system_config +="system->c0 = {0};\n".format(self.model.domain.c0) - system_config +="system->P0 = {0};\n".format(self.model.domain.P0) - #// bounding box - system_config += "system->xlo = {0};\n".format(self.model.domain.xlim[0]) - system_config += "system->xhi = {0};\n".format(self.model.domain.xlim[1]) - system_config += "system->ylo = {0};\n".format(self.model.domain.ylim[0]) - system_config += "system->yhi = {0};\n".format(self.model.domain.ylim[1]) - system_config += "system->zlo = {0};\n".format(self.model.domain.zlim[0]) - system_config += "system->zhi = {0};\n".format(self.model.domain.zlim[1]) - # - if self.model.domain.gravity is not None: - for i in range(3): - system_config += "system->gravity[{0}] = {1};\n".format(i,self.model.domain.gravity[i]) - - - propfilestr = propfilestr.replace("__SYSTEM_CONFIG__", system_config) - - init_rdme='' - if(self.model.enable_rdme and len(self.model.listOfSpecies) > 0): - init_rdme = ''' - initialize_rdme(system, input_irN, input_jcN, input_prN, input_irG, - input_jcG, input_u0 );''' - propfilestr = propfilestr.replace("__INIT_RDME__", init_rdme) - - - init_bc = "" - for bc in self.model.listOfBoundaryConditions: - init_bc += bc.expression() - for ndf, df in enumerate(self.model.listOfDataFunctions): - init_bc += "me->data_fn[{0}] = {1};\n".format(ndf,df.expression()) - propfilestr = propfilestr.replace("__BOUNDARY_CONDITIONS__", init_bc) - - #### Write the data to the file #### - propfile.write(propfilestr) - propfile.close()
- - -
[docs]class SimulationError(Exception): - pass
- - -
[docs]class SimulationTimeout(SimulationError): - pass
-
- -
- - -
-
-
-
- - - - - Fork me on GitHub - - - - - - \ No newline at end of file diff --git a/docs/build/html/_modules/spatialpy/VTKReader.html b/docs/build/html/_modules/spatialpy/VTKReader.html deleted file mode 100644 index 06309311..00000000 --- a/docs/build/html/_modules/spatialpy/VTKReader.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - - - - - spatialpy.VTKReader — SpatialPy 0.5.1 documentation - - - - - - - - - - - - - - - - - - -
- - -
-
- - -
- -

Source code for spatialpy.VTKReader

-'''
-SpatialPy is a Python 3 package for simulation of
-spatial deterministic/stochastic reaction-diffusion-advection problems
-Copyright (C) 2021 SpatialPy developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU GENERAL PUBLIC LICENSE Version 3 as
-published by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU GENERAL PUBLIC LICENSE Version 3 for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-'''
-
-import numpy
-import math
-
-
-
[docs]class VTKReader: - """VTKReader.py: SpatialPy minimal VTK legacy file reader.""" - """Reference: https://vtk.org/wp-content/uploads/2015/04/file-formats.pdf""" - - def __init__(self, filename=None, debug=False): - - self.filename = filename - self.pointdatatype = None - self.numpoints = None - self.points = None - self.arrays = None - self.debug = debug - self.datatypes = { - "int": "int32", - "float": "float32", - "double": "float64", - } - -
[docs] def setfilename(self, filename): - """Set filename. - Args: - (str) filename - """ - - self.filename = filename
- -
[docs] def getarrayname(self, i): - """Get (str) array name. - Args: - (int) index""" - - arrayids = list(self.arrays.keys()) - - if i <= len(arrayids): - return arrayids[i] - else: - return None
- -
[docs] def getarrays(self): - """Get (dict) of arrays.""" - - return self.arrays
- -
[docs] def getnumpoints(self): - """Get (int) number of points.""" - - return self.numpoints
- -
[docs] def getpoints(self): - """Get (list) points.""" - return self.points
- -
[docs] def isvalidnum(self, numstr): - """Test if string is a valid numeric value. - Args: - (str) string to test - Return: - (boolean) True/False - """ - - try: - float(numstr) - except: - return False - - return True
- -
[docs] def readnumeric(self, fd): - """Read numeric data to be returned in a list. - Args: - file descriptor - Return: - (list) numeric data read""" - - numericlist = [] - - for line in fd: - #print('line={0}'.format(line)) - l = line.strip().split() - if(len(l)==0): - break - if self.isvalidnum(l[0]): - numericlist.extend(l) - else: - break - - return numericlist
- -
[docs] def readpoints(self, fd, row, col, datatype): - """Read numeric data to be returned in a shaped numpy array. - Args: - file descriptor - (int) number of rows - (int) number of columns - (str) VTK datatype - Return: - (numpy array) of shape row by col of (numpy) dtype - """ - - pointlist = numpy.array(self.readnumeric(fd), dtype=self.datatypes[datatype]) - if col > 1: - pointlist = pointlist.reshape(row, col) - - return pointlist
- - # This should be refactored at some point and probably eliminated - # In factor of readnumeric() -
[docs] def populatearrays(self, vtkdata, arraydata, col, row, name, datatype): - """Populate arrays with data to be added to dict of shaped arrays. - Args: - (dict) array data - (array) array data to be added to dict - (int) number of rows - (int) number of columns - (str) name of dict key - (str) VTK datatype - """ - - array = numpy.array(arraydata, dtype=datatype) - if col > 1: - array = array.reshape(row, col) - vtkdata[name] = array
- -
[docs] def readarrays(self, fd): - """Read all arrays and data in file. - Args: - file descriptor - Return: - (dict) array data - """ - vtkdata = {} - arraydata = [] - - for line in fd: - #if self.debug: print("line={0}".format(line),end='') - if line.isspace(): - continue - try: - name, col, row, datatype = line.strip().split() - # got another PointData section - except Exception as e: - print("Error: {1}".format(e)) - print("on line >>>") - print(line, end='') - print("<<< {0}".format(fd.tell())) - raise e - col = int(col) - row = int(row) - - # now read row*col number of values - arraydata = [] - while len(arraydata) < row*col: - line = fd.readline() - arraydata.extend(line.strip().split()) - - #if self.debug: print("populatearrays(name={0})".format(name)) - self.populatearrays(vtkdata, arraydata, col, row, name, datatype) - - return vtkdata
- -
[docs] def readfile(self): - """Read VTK file.""" - - with open(self.filename) as fd: - if self.debug: print("open({0})".format(self.filename)) - tmp = fd.readline() - #if self.debug: print("line={0}".format(tmp),end='') - tmp = fd.readline() - #if self.debug: print("line={0}".format(tmp),end='') - - # We only output ASCII so we can ignore BINARY - tmp = fd.readline() - #if self.debug: print("line={0}".format(tmp),end='') - if tmp.strip().upper() != "ASCII": - raise VTKReaderIOError("{0} doesn't look like a valid ASCII VTK file.".format(self.filename)) - - tmp = fd.readline() - #if self.debug: print("line={0}".format(tmp),end='') - - tmp = fd.readline() - #if self.debug: print("line={0}".format(tmp),end='') - _, self.numpoints, self.pointdatatype = tmp.strip().split() - self.numpoints = int(self.numpoints) - - #if self.debug: print("self.readpoints(numpoints={0})".format(self.numpoints),end='') - self.points = self.readpoints(fd, self.numpoints, 3, self.pointdatatype) - #if self.debug: print("self.points.shape = {0}".format(self.points.shape)) - - for line in fd: - if line[:5] != "FIELD": - #print("skipping line: {0}".format(line),end='') - continue - else: - #if self.debug: print("break skipping on line: {0}".format(line),end='') - break - - self.arrays = self.readarrays(fd)
- #if self.debug: print("self.arrays.keys = {0}".format(self.arrays.keys())) - - -
[docs]class VTKReaderError(Exception): - """Base class for exceptions in VTKReader module.""" - - pass
- - -
[docs]class VTKReaderIOError(VTKReaderError): - """Exception raised for I/O errors.""" - - def __init__(self, message): - self.message = message
-
- -
- - -
-
-
-
- - - - - Fork me on GitHub - - - - - - \ No newline at end of file diff --git a/docs/build/html/_modules/spatialpy/core/boundarycondition.html b/docs/build/html/_modules/spatialpy/core/boundarycondition.html new file mode 100644 index 00000000..e0c3f691 --- /dev/null +++ b/docs/build/html/_modules/spatialpy/core/boundarycondition.html @@ -0,0 +1,289 @@ + + + + + + + + spatialpy.core.boundarycondition — SpatialPy 0.5.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + +
+ +

Source code for spatialpy.core.boundarycondition

+'''
+SpatialPy is a Python 3 package for simulation of
+spatial deterministic/stochastic reaction-diffusion-advection problems
+Copyright (C) 2019 - 2022 SpatialPy developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU GENERAL PUBLIC LICENSE Version 3 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU GENERAL PUBLIC LICENSE Version 3 for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+from spatialpy.core.spatialpyerror import BoundaryConditionError
+
+
+
[docs]class BoundaryCondition(): + """ + Set spatial regions of the domain where a property of + particles are held constant (updated each simulation step) + + Conditions (one or more of the following must be set): + - xmin, xmax: (float) min or max value in the x dimension + - ymin, ymax: (float) min or max value in the y dimension + - zmin, zmax: (float) min or max value in the z dimension + - type_id: type (subdomain) of the partciles + Targets (one of the following must be set): + property: (str), 'nu', 'rho','v' + species: (str) name of a chemical species. + Must also set deterministic=True/False flag. + + :param xmin: x-axis coordinate lower bound of **condition** + :type xmin: float + + :param xmax: x-axis coordinate upper bound of **condition** + :type xmax: float + + :param ymin: y-axis coordinate lower bound of **condition** + :type ymin: float + + :param ymax: y-axis coordinate upper bound of **condition** + :type ymax: float + + :param zmin: z-axis coordinate lower bound of **condition** + :type zmin: float + + :param zmax: z-axis coordinate upper bound of **condition** + :type zmax: float + + :param type_id: Set **condition** to particle type id + :type type_id: int | str + + :param deterministic: **Must be set if target is Species.** Set True if boundary condition target is species \ + and applies to deterministic simulation. **BoundaryCondition not yet implemeneted for Stochastic Species**. + :type deterministic: bool + + :param target: Set **target** to properties, can be 'nu' 'rho' or 'v' or species name + If species name, determinstic must also be set to True/False. + :type target: str + + :param value: Value property will take in region defined by the conditions + :type value: float or float[3] + + :param model: Target model of boundary condition + :type model: spatialpy.Model.Model + """ + + def __init__(self, xmin=None, xmax=None, ymin=None, ymax=None, zmin=None, zmax=None, + type_id=None, deterministic=True, target=None, value=None, model=None): + from spatialpy.core.model import Model # pylint: disable=import-outside-toplevel + from spatialpy.core.species import Species # pylint: disable=import-outside-toplevel + if xmin is not None and not isinstance(xmin, (int, float)): + raise BoundaryConditionError("X-min must be of type int or float.") + if xmax is not None and not isinstance(xmax, (int, float)): + raise BoundaryConditionError("X-max must be of type int or float.") + if ymin is not None and not isinstance(ymin, (int, float)): + raise BoundaryConditionError("Y-min must be of type int or float.") + if ymax is not None and not isinstance(ymax, (int, float)): + raise BoundaryConditionError("Y-max must be of type int or float.") + if zmin is not None and not isinstance(zmin, (int, float)): + raise BoundaryConditionError("Z-min must be of type int or float.") + if zmax is not None and not isinstance(zmax, (int, float)): + raise BoundaryConditionError("Z-max must be of type int or float.") + if type_id is not None and not isinstance(type_id, (int, str)): + raise BoundaryConditionError("Type-ID must be of type int.") + elif type_id is not None: + type_id = f"type_{type_id}" + if target is None or not (isinstance(target, (str, Species)) or + type(target).__name__ == 'Species' or property in ('nu', 'rho', 'v')): + raise BoundaryConditionError("Target must be of type string or SpatialPy.Species") + if not (value is None or isinstance(value, (int, float)) or (isinstance(value, list) and len(value) == 3)): + raise BoundaryConditionError("Value must be of type float or float[3].") + try: + if isinstance(value, int): + value = float(value) + if isinstance(value, list): + value = [float(val) for val in value] + except Exception as err: + raise BoundaryConditionError("Value must be of type float or float[3].") from err + if not (model is None or isinstance(model, Model) or type(model).__name__ == 'Model'): + raise BoundaryConditionError("Model must be of type SpatialPy.Model.") + + self.xmin = xmin + self.xmax = xmax + self.ymin = ymin + self.ymax = ymax + self.zmin = zmin + self.zmax = zmax + self.type_id = type_id + self.target = target + self.deterministic = deterministic + self.value = value + self.model = model + + +
[docs] def expression(self): + """ + Creates evaluable string expression of boundary condition. + + :returns: A string representation of the boundary condition. + :rtype: str + """ + if self.value is None: + raise BoundaryConditionError("Must set value") + cond=[] + if self.xmin is not None: + cond.append(f"(me->x[0] >= {self.xmin})") + if self.xmax is not None: + cond.append(f"(me->x[0] <= {self.xmax})") + if self.ymin is not None: + cond.append(f"(me->x[1] >= {self.ymin})") + if self.ymax is not None: + cond.append(f"(me->x[1] <= {self.ymax})") + if self.zmin is not None: + cond.append(f"(me->x[2] >= {self.zmin})") + if self.zmax is not None: + cond.append(f"(me->x[2] <= {self.zmax})") + if self.type_id is not None: + cond.append(f"(me->type == {self.type_id})") + if len(cond)==0: + raise BoundaryConditionError('need at least one condition on the BoundaryCondition') + bcstr = "if(" + '&&'.join(cond) + "){" + if self.target in self.model.listOfSpecies: + if self.deterministic: + s_ndx = self.model.species_map[self.model.listOfSpecies[self.target]] + bcstr += f"me->C[{s_ndx}] = {self.value};" + else: + raise BoundaryConditionError( + "BoundaryConditions don't work for stochastic species yet." + ) + elif self.target is not None: + if self.target == 'v': + for i in range(3): + bcstr+= f"me->v[{i}]={self.value[i]};" + elif self.target == 'nu': + bcstr += f"me->nu={self.value};" + elif self.target == 'rho': + bcstr += f"me->rho={self.value};" + else: + raise BoundaryConditionError(f"Unable handle boundary condition for property '{self.target}'") + bcstr+= "}" + return bcstr
+
+ +
+ + +
+
+
+
+ + + + + Fork me on GitHub + + + + + + \ No newline at end of file diff --git a/docs/build/html/_modules/spatialpy/core/cleanup.html b/docs/build/html/_modules/spatialpy/core/cleanup.html new file mode 100644 index 00000000..ac31de37 --- /dev/null +++ b/docs/build/html/_modules/spatialpy/core/cleanup.html @@ -0,0 +1,211 @@ + + + + + + + + spatialpy.core.cleanup — SpatialPy 0.5.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + +
+ +

Source code for spatialpy.core.cleanup

+'''
+SpatialPy is a Python 3 package for simulation of
+spatial deterministic/stochastic reaction-diffusion-advection problems
+Copyright (C) 2019 - 2022 SpatialPy developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU GENERAL PUBLIC LICENSE Version 3 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU GENERAL PUBLIC LICENSE Version 3 for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import os
+import shutil
+import tempfile
+
+
[docs]def cleanup_tempfiles(): + ''' + Cleanup all tempfiles in spatialpy core, build, and results. + ''' + cleanup_core_files() + tempdir = tempfile.gettempdir() + for file_obj in os.listdir(tempdir): + if file_obj.startswith("spatialpy_build"): + cleanup_build_files(build_dir=os.path.join(tempdir, file_obj)) + elif file_obj.startswith("spatialpy_result"): + cleanup_result_files(result_dir=os.path.join(tempdir, file_obj))
+ +
[docs]def cleanup_core_files(): + ''' + Cleanup all tempfiles in spatialpy core. + ''' + from spatialpy.core import log # pylint: disable=import-outside-toplevel + + tempdir = tempfile.gettempdir() + core_dir = os.path.join(tempdir, "spatialpy_core") + if os.path.isdir(core_dir): + shutil.rmtree(core_dir) + log.info("Spatialpy core directory was removed")
+ +
[docs]def cleanup_build_files(build_dir=None): + ''' + Cleanup all spatialpy_build directories. + + :param build_dir: Path to the build directory to be removed. (optional) + :type build_dir: string + ''' + from spatialpy.core import log # pylint: disable=import-outside-toplevel + + if build_dir is not None: + shutil.rmtree(build_dir) + log.info("Build directory'{}' was removed", build_dir) + else: + count = 0 + tempdir = tempfile.gettempdir() + for file_obj in os.listdir(tempdir): + if file_obj.startswith("spatialpy_build"): + build_dir = os.path.join(tempdir, file_obj) + shutil.rmtree(build_dir) + count += 1 + log.info("{} build directories were removed", count)
+ +
[docs]def cleanup_result_files(result_dir=None): + ''' + Cleanup all spatialpy_result directories. + + :param result_dir: Path to the result directory to be removed. (optional) + :type result_dir: string + ''' + from spatialpy.core import log # pylint: disable=import-outside-toplevel + + if result_dir is not None: + shutil.rmtree(result_dir) + log.info("Result directory '{}' was removed", result_dir) + else: + count = 0 + tempdir = tempfile.gettempdir() + for file_obj in os.listdir(tempdir): + if file_obj.startswith("spatialpy_result"): + result_dir = os.path.join(tempdir, file_obj) + shutil.rmtree(result_dir) + count += 1 + log.info("{} result directories were removed", count)
+
+ +
+ + +
+
+
+
+ + + + + Fork me on GitHub + + + + + + \ No newline at end of file diff --git a/docs/build/html/_modules/spatialpy/DataFunction.html b/docs/build/html/_modules/spatialpy/core/datafunction.html similarity index 53% rename from docs/build/html/_modules/spatialpy/DataFunction.html rename to docs/build/html/_modules/spatialpy/core/datafunction.html index a6a1b007..1faca822 100644 --- a/docs/build/html/_modules/spatialpy/DataFunction.html +++ b/docs/build/html/_modules/spatialpy/core/datafunction.html @@ -5,20 +5,20 @@ - spatialpy.DataFunction — SpatialPy 0.5.1 documentation - - - - - - - - - + spatialpy.core.datafunction — SpatialPy 0.5.1 documentation + + + + + + + + + - + - + @@ -29,8 +29,8 @@