Source code for sisl.utils.misc

"""
Miscellaneous routines
"""
from __future__ import division

from numbers import Integral
from math import pi

from sisl._help import _range as range

__all__ = ['merge_instances', 'name_spec', 'direction', 'angle']
__all__ += ['iter_shape']


[docs]def merge_instances(*args, **kwargs): """ Merges an arbitrary number of instances together. Parameters ---------- name: str or MergedClass name of class to merge """ name = kwargs.get('name', 'MergedClass') # We must make a new-type class cls = type(name, (object,), {}) # Create holder of class # We could have m = cls() for arg in args: m.__dict__.update(arg.__dict__) return m
[docs]def iter_shape(shape): """ Generator for iterating a shape by returning consecutive slices Parameters ---------- shape : array_like the shape of the iterator Yields ------ tuple of int a tuple of the same length as the input shape. The iterator is using the C-indexing. Examples -------- >>> for slc in iter_shape([2, 1, 3]): >>> print(slc) [0, 0, 0] [0, 0, 1] [0, 0, 2] [1, 0, 0] [1, 0, 1] [1, 0, 2] """ shape1 = [i-1 for i in shape] ns = len(shape) ns1 = ns - 1 # Create list for iterating # we require a list because tuple's are immutable slc = [0] * ns while slc[0] < shape[0]: for i in range(shape[ns1]): slc[ns1] = i yield slc # Increment the previous shape indices for i in range(ns1, 0, -1): if slc[i] >= shape1[i]: slc[i] = 0 if i > 0: slc[i-1] += 1
[docs]def name_spec(name): """ Split into a tuple of name and specifier, delimited by ``{...}``. Parameters ---------- name: str string to split Returns ------- tuple of str returns the name and the specifier (without delimiter) in a tuple Examples -------- >>> name_spec('hello') 'hello', None >>> name_spec('hello{TEST}') 'hello', 'TEST' """ if not name.endswith('}'): return name, None lname = name[:-1].split('{') return '{'.join(lname[:-1]), lname[-1]
# Transform a string to a Cartesian direction
[docs]def direction(d): """ Return the index coordinate index corresponding to the Cartesian coordinate system. Parameters ---------- d: {0, 'X', 'x', 1, 'Y', 'y', 2, 'Z', 'z'} returns the integer that corresponds to the coordinate index. If it is an integer, it is returned *as is*. Returns ------- int The index of the Cartesian coordinate system. Examples -------- >>> direction(0) 0 >>> direction('Y') 1 >>> direction('z') 2 """ if isinstance(d, Integral): return d # We take it as a string d = d.lower() # We must use an arry to not allow 'xy' input if d in 'x y z a b c'.split(): return 'xaybzc'.index(d) // 2 raise ValueError('Input direction is not an integer, nor a string in "xyzabc".')
# Transform an input to an angle
[docs]def angle(s, radians=True, in_radians=True): """ Convert the input string to an angle, either radians or degrees. Parameters ---------- s : str If `s` starts with ``'r'`` it is interpreted as radians ``[0:2pi]``. If `s` starts with ``'a'`` it is interpreted as a regular angle ``[0:360]``. If `s` ends with ``'r'`` it returns in radians. If `s` ends with ``'a'`` it returns in regular angle. `s` may be any mathematical equation which can be intercepted through `eval`. radians : bool Whether the returned angle is in radians. Note than an ``'r'`` at the end of `s` has precedence. in_radians : bool Whether the calculated angle is in radians. Note than an `'r'` at the beginning of `s` has precedence. Returns ------- float the angle in the requested unit """ s = s.lower() if s.startswith('r'): in_radians = True elif s.startswith('a'): in_radians = False if s.endswith('r'): radians = True elif s.endswith('a'): radians = False # Remove all r/a's and remove white-space s = s.replace('r', '').replace('a', '').replace(' ', '') # Figure out if Pi is circumfered by */+- spi = s.split('pi') nspi = len(spi) if nspi > 1: # We have pi at least in one place. for i, si in enumerate(spi): # In case the last element is a pi if len(si) == 0: continue if i < nspi - 1: if not si.endswith(('*', '/', '+', '-')): # it *MUST* be '*' spi[i] = spi[i] + '*' if 0 < i: if not si.startswith(('*', '/', '+', '-')): # it *MUST* be '*' spi[i] = '*' + spi[i] # Now insert Pi dependent on the input type if in_radians: Pi = pi else: Pi = 180. s = ('{}'.format(Pi)).join(spi) # We have now transformed all values # to the correct numerical values and we calculate # the expression ra = eval(s) if radians and not in_radians: return ra / 180. * pi if not radians and in_radians: return ra / pi * 180. # Both radians and in_radians are equivalent # so return as-is return ra