"""
besmarts.core.primitives
Standard definitions of SMARTS primitives.
"""
from typing import List
import re
from besmarts.core.arrays import bitvec
from besmarts.core import arrays
primitive_key = str
primitive_key_set = set(
[
"element",
"hydrogen",
"connectivity_total",
"connectivity_ring",
"ring_smallest",
"aromatic",
"chirality",
"valence",
"formal_charge",
"bond_order",
"bond_ring",
"unit",
"unit_index",
"link_src",
"link_dst",
"variable",
]
)
[docs]
class primitive_key:
ELEMENT = "element"
HYDROGEN = "hydrogen"
CONNECTIVITY_TOTAL = "connectivity_total"
CONNECTIVITY_RING = "connectivity_ring"
RING_SMALLEST = "ring_smallest"
AROMATIC = "aromatic"
CHIRALITY = "chirality"
VALENCE = "valence"
FORMAL_CHARGE = "formal_charge"
BOND_ORDER = "bond_order"
BOND_RING = "bond_ring"
UNIT = "unit"
UNIT_INDEX = "unit_index"
LINK_SRC = "link_src"
LINK_DST = "link_dst"
VARIABLE = "variable"
def __init__(self, *args, **kwds):
pass
def __repr__(self):
return f"{self.name}"
[docs]
class primitive_codec:
def __init__(self, implements, smarts_prefix):
self.implements: str = implements
self.smarts_prefix = smarts_prefix
[docs]
def decode_int(self, x: int) -> int:
"""
Transform a decoded integer into its value for SMARTS/SMILES. The
default behavior is a passthrough and works for most primitives, such
as element and hydrogen. Special behavior for certain primitives e.g.
formal charge, must be handled by subclassing.
Parameters
----------
x : int
The integer to decode
Returns
-------
int
The decoded integer
"""
return x
[docs]
def encode_int(self, x: int) -> int:
"""
Transform an integer parsed from a SMILES or SMARTS representation to a
valid value for storing in a bit array. The default behavior is a
passthrough and works for most primitives, such as element and
hydrogen. Special behavior for certain primitives e.g. formal charge,
must be handled by subclassing.
Parameters
----------
x : int
The integer to encode
Returns
-------
int
The encoded integer
"""
return x
[docs]
def tokenize_smarts(self, obj) -> List[str]:
descriptor = f"(!?){self.smarts_prefix}([0-9][0-9]*)"
matches = list(re.finditer(descriptor, obj))
return matches
[docs]
def decode_supersmarts(self, dtype, obj) -> bitvec:
return self.decode_smarts(dtype, obj)
[docs]
def decode_smarts(self, dtype, obj) -> bitvec:
"""
Parse and decode a SMARTS atom or bond for a primitive.
Parameters
----------
dtype : array
The class used to store the decoded values
obj : str
The atom or bond string to parse and decode
Returns
-------
bitvec
The decoded SMARTS as an array of bits
"""
matches = self.tokenize_smarts(obj)
# descriptor = f"(!?){self.smarts_prefix}([0-9][0-9]*)"
# matches = list(re.finditer(descriptor, obj))
on = dtype()
off = dtype()
if "*" in obj:
on[:] = True
return on
result = on
if matches:
for ret in matches:
result = on
if ret.group(1) == "!":
result = off
encbit = self.decode_int(int(ret.group(2)))
result[encbit] = True
else:
on[:]
if on.any():
result = on - off
else:
result = ~off
return result
[docs]
def encode_supersmarts(self, dtype, obj) -> bitvec:
return self.encode_smarts(dtype, obj)
[docs]
def encode_smarts(self, arr: bitvec) -> str:
"""
Transform a primitive as a bit array to a SMARTS string.
Parameters
----------
arr : array
The primitive in the form of a bit array
Returns
-------
str
The SMARTS string represented the array. Note that if the primitive
has no values set, it is not a valid SMARTS string and "_" will be
returned
"""
if arrays.bitvec_all(arr):
return ""
if not arrays.bitvec_any(arr):
return "_"
on = list(arrays.bitvec_on(arr))
off = list(arrays.bitvec_off(arr))
if len(on) <= len(off):
strs = [self.smarts_prefix + str(self.decode_int(i)) for i in on]
return ",".join(strs)
else:
strs = [
"!" + self.smarts_prefix + str(self.decode_int(i)) for i in off
]
return "".join(strs)
[docs]
def encode_smiles(self, arr: bitvec) -> str:
return ""
[docs]
def decode_smiles(self, dtype: bitvec, obj) -> bitvec:
raise NotImplementedError()
element_tr = {
"1": "H",
"2": "He",
"3": "Li",
"4": "Be",
"5": "B",
"6": "C",
"7": "N",
"8": "O",
"9": "F",
"10": "Ne",
"11": "Na",
"12": "Mg",
"13": "Al",
"14": "Si",
"15": "P",
"16": "S",
"17": "Cl",
"18": "Ar",
"19": "K",
"20": "Ca",
"21": "Sc",
"22": "Ti",
"23": "V",
"24": "Cr",
"25": "Mn",
"26": "Fe",
"27": "Co",
"28": "Ni",
"29": "Cu",
"30": "Zn",
"31": "Ga",
"32": "Ge",
"33": "As",
"34": "Se",
"35": "Br",
"36": "Kr",
"37": "Rb",
"38": "Sr",
"39": "Y",
"40": "Zr",
"41": "Nb",
"42": "Mo",
"43": "Tc",
"44": "Ru",
"45": "Rh",
"46": "Pd",
"47": "Ag",
"48": "Cd",
"49": "In",
"50": "Sn",
"51": "Sb",
"52": "Te",
"53": "I",
"54": "Xe",
"55": "Cs",
"56": "Ba",
"57": "La",
"58": "Ce",
"59": "Pr",
"60": "Nd",
"61": "Pm",
"62": "Sm",
"63": "Eu",
"64": "Gd",
"65": "Tb",
"66": "Dy",
"67": "Ho",
"68": "Er",
"69": "Tm",
"70": "Yb",
"71": "Lu",
"72": "Hf",
"73": "Ta",
"74": "W",
"75": "Re",
"76": "Os",
"77": "Ir",
"78": "Pt",
"79": "Au",
"80": "Hg",
"81": "Tl",
"82": "Pb",
"83": "Bi",
"84": "Po",
"85": "At",
"86": "Rn",
"87": "Fr",
"88": "Ra",
"89": "Ac",
"90": "Th",
"91": "Pa",
"92": "U",
"93": "Np",
"94": "Pu",
"95": "Am",
"96": "Cm",
"97": "Bk",
"98": "Cf",
"99": "Es",
"100": "Fm",
"101": "Md",
"102": "No",
"103": "Lr",
"104": "Rf",
"105": "Db",
"106": "Sg",
"107": "Bh",
"108": "Hs",
"109": "Mt",
"110": "Ds",
"111": "Rg",
"112": "Cn",
"113": "Nh",
"114": "Fl",
"115": "Mc",
"116": "Lv",
"117": "Ts",
"118": "Og",
}