Source code for besmarts.core.arrays

"""
besmarts.core.arrays

The core data types that store SMARTS primitives.
"""

from typing import List, Tuple
import array
import itertools

INF = -1

[docs] class bitvec: """ Holds the bit vector of a single primitive as a single integer. """ __slots__ = "v", "maxbits" def __init__(self, val=0, maxbits=64): self.v: int = val self.maxbits: int = maxbits
[docs] def inv(self): return self.v < 0
def __iter__(self): for x in bitvec_on(self): b = bitvec(maxbits=self.maxbits) b[x] = True yield b def __getitem__(self, i): l = min(len(self), self.maxbits) if isinstance(i, slice): # [:] if i.stop is None and i.start is None and i.step is None: return [((self.v >> x) & 1) for x in range(l)] start = 0 if i.start is None else i.start end = i.stop if end is None: end = self.maxbits if self.maxbits == INF: raise IndexError( "Cannot supply an infinite length array " "(input slice had no bounds and maxbits was inf (-1))" ) step = 1 if i.step is None else i.step rev = step < 0 step = abs(step) # [i:] and i > maxbits if start >= l: diff = (end - start + 1) // step pad = list([int(self.v < 0)] * diff) pad = pad[::-1] return pad # [i:j:k] and 2**j > v if end > l: diff = (end - l) // step expl = [(self.v >> x) & 1 for x in range(start, l, step)] pad = list([int(self.v < 0)] * diff) expl += pad if rev: expl = expl[::-1] return expl # [i:j:k] and all within v expl = [(self.v >> x) & 1 for x in range(start, end, step)] if rev: expl = expl[::-1] return expl # [i] elif i >= l: return int(self.v < 0) else: return (self.v >> i) & 1 def __setitem__(self, i, v: int): v: bool = v > 0 if isinstance(i, slice): # [:] = v if i.stop is None and i.start is None and i.step is None: if v: self.v = -1 else: self.v = 0 return start = 0 if i.start is None else i.start end = int(max(self.maxbits if i.stop is None else i.stop, start)) if end > self.maxbits: end = self.maxbits step = 1 if i.step is None else i.step # [x:] = v if i.stop is None: mask = sum(1 << i for i in range(start, end, step)) if v: self.v |= mask else: mask = ~mask self.v &= mask else: # [x:y:z] = v mask = sum(1 << i for i in range(start, end, step)) if v: self.v |= mask else: mask = ~mask self.v &= mask # [5] = True elif isinstance(i, int): mask = 1 << i if v: self.v |= mask else: mask = ~mask self.v &= mask # [[1,0,1,0,1]] = v elif isinstance(i, bitvec): on = bitvec_on(i) mask = sum(1 << x for x in on) if v: self.v |= mask else: mask = ~mask self.v &= mask # [[1,2,3]] = v elif hasattr(i, "__iter__"): mask = sum(1 << x for x in set(i)) if v: self.v |= mask else: mask = ~mask self.v &= mask else: raise Exception( "Using datatype {} for setitem not supported".format(type(i)) ) def __len__(self) -> int: if self.v == 0: return 0 l = len(bin(self.v)) if self.v < 0: return l - 3 return l - 2 def __repr__(self) -> str: v = self.v neg = " " if v < 0: v = -self.v - 1 neg = "~" return neg + "{:>s}".format(bin(v)) def __hash__(self) -> int: return self.v def __and__(self, o) -> "bitvec": return bitvec_and(self, o) def __or__(self, o) -> "bitvec": return bitvec_or(self, o) def __xor__(self, o) -> "bitvec": return bitvec_xor(self, o) def __invert__(self) -> "bitvec": return bitvec_not(self) def __add__(self, o) -> "bitvec": return bitvec_or(self, o) def __sub__(self, o) -> "bitvec": return bitvec_subtract(self, o) def __eq__(self, o) -> bool: return bitvec_equal(self, o) def __ne__(self, o) -> bool: return not self == o def __contains__(self, o) -> "bool": return bitvec_is_null(bitvec_subtract(o, self))
[docs] def any(self) -> bool: return bitvec_any(self)
[docs] def all(self) -> bool: return bitvec_all(self)
[docs] def is_null(self) -> bool: return bitvec_is_null(self)
[docs] def on(self) -> List[int]: return bitvec_on(self)
[docs] def on_first(self) -> int: return bitvec_on_first(self)
[docs] def off(self) -> List[int]: return bitvec_off(self)
[docs] def off_first(self) -> int: return bitvec_off_first(self)
[docs] def bits(self, maxbits=False) -> int: return bitvec_bits(self, maxbits=maxbits)
[docs] def reduce(self) -> int: return bitvec_sum(self)
[docs] def clear(self): self.v = 0
[docs] def copy(self): return bitvec_copy(self)
[docs] class intvec: __slots__ = "v" def __init__(self): self.v = array.array("q")
[docs] def bitvec_sum(bv: bitvec) -> int: return bv.v
[docs] def bitvec_is_inverted(bv: bitvec) -> bool: return bv.v < 0
[docs] def bitvec_bits(bv: bitvec, maxbits=False) -> int: inv = bitvec_is_inverted(bv) if inv: if maxbits: return bv.maxbits else: return INF return len(bitvec_on(bv))
[docs] def bitvec_on(bv: bitvec) -> List[int]: return [i for i in range(bv.maxbits) if (bv.v >> i) & 1]
[docs] def bitvec_on_first(bv: bitvec) -> List[int]: for i in range(bv.maxbits): if (bv.v >> i) & 1: return i return None
[docs] def bitvec_off(bv: bitvec) -> List[int]: return [i for i in range(bv.maxbits) if not (bv.v >> i) & 1]
[docs] def bitvec_off_first(bv: bitvec) -> List[int]: for i in range(bv.maxbits): if not (bv.v >> i) & 1: return i return None
[docs] def bitvec_explicit_flip(bv: bitvec) -> None: bv.v = ~bv.v
[docs] def bitvec_is_null(bv: bitvec) -> bool: return bv.v == 0
[docs] def bitvec_clear(bv: bitvec) -> None: bv.v = 0
[docs] def bitvec_copy(bv: bitvec) -> bitvec: return bitvec(bv.v, bv.maxbits)
[docs] def bitvec_all(bv: bitvec) -> bool: return bv.v == -1
[docs] def bitvec_any(bv: bitvec) -> bool: return bv.v != 0
[docs] def bitvec_reduce(bv: bitvec) -> int: return bv.v
[docs] def bitvec_not(a: bitvec) -> bitvec: return bitvec(~a.v, a.maxbits)
[docs] def bitvec_or(a: bitvec, b: bitvec) -> bitvec: return bitvec(a.v | b.v, max(a.maxbits, b.maxbits))
[docs] def bitvec_and(a: bitvec, b: bitvec) -> bitvec: return bitvec(a.v & b.v, min(a.maxbits, b.maxbits))
[docs] def bitvec_xor(a: bitvec, b: bitvec) -> bitvec: return bitvec(a.v ^ b.v, max(a.maxbits, b.maxbits))
[docs] def bitvec_subtract(a: bitvec, b: bitvec) -> bitvec: return bitvec(a.v & (a.v ^ b.v), a.maxbits)
[docs] def bitvec_equal(a: bitvec, b: bitvec) -> bool: return a.v == b.v
[docs] def bitvec_subset(a: bitvec, b: bitvec) -> bool: return a.v == (a.v & b.v)
[docs] def bitvec_superset(a: bitvec, b: bitvec) -> bool: return b.v == (a.v & b.v)
array_dtype = type(bitvec)
[docs] def batched(iterable, n: int): """ https://docs.python.org/3/library/itertools.html#itertools-recipes Batch data into tuples of length n. The last batch may be shorter. Parameters ---------- iterable n : int Returns ------- Generator """ # batched('ABCDEFG', 3) --> ABC DEF G it = iter(iterable) batch = tuple(itertools.islice(it, n)) while batch: yield batch batch = tuple(itertools.islice(it, n))
[docs] def argmax(iterable): return max(enumerate(iterable), key=lambda x: x[1])[0]
[docs] def argmin(iterable): return min(enumerate(iterable), key=lambda x: x[1])[0]
[docs] def argsort(iterable): return [x[0] for x in sorted(enumerate(iterable), key=lambda x: x[1])]
[docs] def find_unsigned_typecode_min(N: int): code = None for c in "QLIHB": a = array.array(c) if N < 2 ** (8 * a.itemsize): # print(f"Array max value is {2**(8*a.itemsize) - 1} and itemsize is {a.itemsize}; len(A) is {len(A)}") code = c assert code, "Number is too large for this machine" return code
[docs] def flatten_list(l, times=1): if times == 0: return l if len(l) == 0: return l if times == -1: if isinstance(l[0], list): ll = [a for b in l if hasattr(b, "__iter__") for a in b] return flatten_list(ll, times) else: return l else: return flatten_list( [a for b in l if hasattr(b, "__iter__") for a in b], times - 1 )
[docs] def array_scale(a, s): return [i*s for i in a]
[docs] def array_translate(a, s): return [i+s for i in a]
[docs] def array_sum(a): return sum(a)
[docs] def array_mean(a): return sum(a)/len(N)
[docs] def array_add(a, b): return [i+j for i,j in zip(a,b)]
[docs] def array_abs(a): return [abs(i) for i in a]
[docs] def array_difference(a, b): return [i-j for i,j in zip(a,b)]
[docs] def array_multiply(a, b): return [i*j for i,j in zip(a,b)]
[docs] def array_divide(a, b): return [i/j for i,j in zip(a,b)]
[docs] def array_inner_product(a, b): return sum((i*j for i,j in zip(a,b)))
[docs] def array_cross(a, b): return ( (a[1]*b[2] - a[2]*b[1]), (a[2]*b[0] - a[0]*b[2]), (a[0]*b[1] - a[1]*b[0]) )
[docs] def array_unit(a, b): """ unit vector from a to b """ return array_scale(array_difference(b, a), 1/array_distance(b, a))
[docs] def array_basis(a, b): """ unit vector from a to b and its projection (magnitude) """ r = array_distance(a, b) return array_scale(array_difference(b, a), 1/r), r
[docs] def array_magnitude(a) -> float: return sum([x*x for x in a])**.5
[docs] def array_distance(a,b) -> float: return sum([x*x for x in array_difference(b,a)])**.5
[docs] def array_round(a, b) -> List[float]: return [round(x, b) for x in a]
[docs] def measure_distance(xyz1, xyz2): result = [[array_distance(a,b)] for a, b in zip(xyz1, xyz2)] return result