Module bioiain.base

Sub-modules

bioiain.base.atom
bioiain.base.chain
bioiain.base.entity
bioiain.base.ligand
bioiain.base.mmcif
bioiain.base.residue
bioiain.base.structure

Functions

def downloadPDBlist(data_dir: str,
list_name: str,
pdb_list: list = None,
file_path: str = None,
file_format='cif',
overwrite: bool = False) ‑> str
Expand source code
def downloadPDBlist(data_dir:str, list_name:str, pdb_list:list=None, file_path:str = None, file_format="cif",
                    overwrite:bool=False) -> str:
    """
    Downloads a list of PDB files into a folder of given name within the data_dir. Creates a file containing all
    the file in the data_dir
    :param data_dir: Directory to create the download folder
    :param list_name: Name of the folder to download files to
    :param pdb_list: List of PDB codes to download
    :param file_path: (optional) file with PDB codes, separated by comma or new lines, extends pdb_list
    :param file_format: PDB / CIF, extension of downloaded files
    :param overwrite: True to download existing pdb files and overwrite
    :return: Path to folder containing downloaded files
    """
    log("header", "Downloading PDB files...")
    file_format = file_format.lower()
    assert file_format in ["pdb", "cif"]
    if pdb_list is None:
        pdb_list = []
    if file_path is not None:
        with open(file_path) as f:
            for line in f:
                new = string_to_list(line, delimiter=",")
                for n in new:
                    n = clean_string(n)
                    pdb_list.append(n)
    pdb_list = sorted(list(set([p.upper() for p in pdb_list])))

    if len(pdb_list) <= 10:
        log(1, "N codes:", pdb_list)
    else:
        log(1, "N codes:", len(pdb_list))

    os.makedirs(data_dir, exist_ok=True)
    list_folder = os.path.join(data_dir, list_name)
    os.makedirs(list_folder, exist_ok=True)
    link_file = "{}_{}.link.list".format(list_name, file_format)
    with open(os.path.join(data_dir, link_file) , "w") as f:
        for pdb in pdb_list:
            if file_format == "pdb":
                f.write("https://files.rcsb.org/download/{}.pdb\n".format(pdb))
            elif file_format == "cif":
                f.write("https://files.rcsb.org/download/{}.cif\n".format(pdb))
    # log("debug", "Generated links at: {}".format(os.path.join(data_dir, link_file) ))

    with open(os.path.join(data_dir, link_file)) as f:
        counter = 0
        failed_counter = 0
        skipped_counter = 0
        for line in f:
            line = line.replace("\n", "")
            f_name = line.split("/")[-1]
            if os.path.exists(os.path.join(list_folder, f_name)) and not overwrite:
                skipped_counter += 1
                continue
            url = line
            log("debug", "...Downloading {}".format(url), end="\r")
            response = requests.get(url)
            if response.status_code != 200:
                log("Error", "Failed to download from:", line)
                failed_counter += 1
            else:
                with open(os.path.join(list_folder, f_name), "w") as f:
                    f.write(response.text)
                counter += 1
    log(1, "{} files downloaded, {} failed, {} skipped".format(counter, failed_counter, skipped_counter))
    return list_folder

Downloads a list of PDB files into a folder of given name within the data_dir. Creates a file containing all the file in the data_dir :param data_dir: Directory to create the download folder :param list_name: Name of the folder to download files to :param pdb_list: List of PDB codes to download :param file_path: (optional) file with PDB codes, separated by comma or new lines, extends pdb_list :param file_format: PDB / CIF, extension of downloaded files :param overwrite: True to download existing pdb files and overwrite :return: Path to folder containing downloaded files

def read_mmcif(file_path,
output_folder=None,
subset: list | str = None,
exclude: list | str = None,
as_dict=False) ‑> MMCIF
Expand source code
def read_mmcif(file_path, output_folder=None, subset:list|str=None, exclude:list|str=None ,as_dict=False) -> MMCIF:
    from ..utilities.strings import str_to_list_with_literals
    data = {}
    name = os.path.basename(file_path).split(".")[0]
    if type(subset) is str:
        subset = [subset]
    if type(exclude) is str:
        subset = [exclude]
    if output_folder is not None:
        os.makedirs(output_folder, exist_ok=True)
    with open(file_path, "r") as f:
        n = -1
        in_loop = False
        group_key = None
        loop_keys = None
        loop_values = None
        next_line = None
        eof = False
        multi_line = False
        multi_cached = None
        multi_delimiters = [";"]
        looping = False
        while not eof:
            n+=1
            line = next_line
            next_line = next(f, None)
            if line is None:
                continue
            if next_line is None:
                eof = True
                next_line = "#"
            if line.startswith("#"):
                in_loop = False
                looping = False
                group_key = None
                multi_cached = None
                continue

            if line.startswith("loop_"):
                in_loop = True
                loop_keys = []
                loop_values = []
                group_key = []
                #print("LOOP START")
                continue
            #print("\nLINE:")
            #print(repr(line))
            #print("group_key:", repr(group_key))
            #print("multi:", multi_line)
            #print("loop:", in_loop)
            if multi_line:
                if multi_cached is None:
                    multi_cached = ""
                if line.replace("\n", "").strip() == "":
                    multi_cached += line
                    continue
                if type(multi_cached) is list:
                    line_list, open_lit = str_to_list_with_literals(line, check_open_literal=True )

                if line.replace("\n", "").strip()[-1] in multi_delimiters:
                    line = line.replace("\n", "").strip()[:-2]
                    multi_line = False
                    open_lit = False
                    multi_delimiter = None
                    #print("MULTI LINE END")
                    if not in_loop:
                        v.append(multi_cached)
                        multi_cached = None
                        continue
                    else:
                        loop_values[-1].append(multi_cached)


                else:
                    if type(multi_cached) is str:
                        if line[0] in multi_delimiters:
                            multi_cached += line[1:]
                        else:
                            multi_cached += line
                    elif type(multi_cached) is list:
                        if open_lit:
                            multi_cached[-1] += line_list[0]
                            multi_cached.extend(line_list[1:])

                        else:
                            multi_cached.extend(line_list)
                    else:
                        raise MMCIFTypeError("Bioiain mmcif Parser error")



            if not in_loop:

                if not multi_line:
                    line_list, open_lit = str_to_list_with_literals(line, check_open_literal=True)
                    if len(line_list) == 0:
                        continue
                    group_key = line_list[0].split(".")[0].replace("\n", "").strip()
                    try:
                        k = line_list[0].split(".")[1]
                    except IndexError:

                        k = group_key
                        group_key = None

                    if len(line_list) == 1:
                        v = []
                    else:
                        v = line_list[1:]
                if open_lit:
                    #print(multi_cached)
                    #print(line_list)
                    #print("MULTI_LINE START (OPEN-LIT)")
                    multi_line = True
                    multi_cache = line_list[-1]
                    multi_delimiter = line_list[-1][0]

                if next_line[0] in multi_delimiters and not multi_line:
                    #print("MULTI_LINE START")
                    multi_line = True
                    multi_delimiter = next_line[0]
                    continue

                if group_key is None:
                    #print("multi line", multi_line)
                    #print("line",repr(line))
                    #print("line_list",line_list)
                    #print(n)
                    if n != 1:
                        #log("warning", f"No key-value structure found in line {n}:", repr(line), f"\n  (In file: {file_path})")
                        pass

                    else:
                        #log("debug", "Parsing:", line.replace("\n", "").strip())
                        pass

                if not multi_line and group_key is not None:
                    # print(group_key)
                    # print(subset)
                    # print([group_key == s if not s.endswith("*") else group_key.startswith(s[:-1]) for s in
                    #                 subset])

                    if exclude is not None:
                        # print(group_key)
                        # print(exclude)
                        # print([group_key == s if not s.endswith("*") else group_key.startswith(s[:-1]) for s in
                        #        exclude])
                        if any([group_key == s if not s.endswith("*") else group_key.startswith(s[:-1]) for s in
                                    exclude]):
                            continue
                    if subset is not None:
                        if not any([group_key == s if not s.endswith("*") else group_key.startswith(s[:-1]) for s in
                                    subset]):
                            continue
                        # if group_key not in subset:
                        #     continue
                    if group_key not in data.keys():
                        data[group_key] = [{}]
                    #print(f"{group_key}.{k} ->", " ".join(v))
                    data[group_key][0][k] = interpret(" ".join(v))


            else: # IN LOOP
                #print("MULTI:", multi_line)
                if not multi_line:
                    if line.startswith("_") and not looping:
                        group_key.append(line.split(".")[0].replace("\n", "").strip())
                        loop_keys.append(line.split(" ")[0].split(".")[1].replace("\n", "").strip())
                        continue
                    else:
                        looping = True
                    if multi_cached is None:
                        if len(loop_values) > 0:
                            if len(loop_values[-1]) < len(loop_keys):
                                loop_values[-1].extend(str_to_list_with_literals(line))
                            else:
                                loop_values.append(str_to_list_with_literals(line))
                        else:
                            loop_values.append(str_to_list_with_literals(line))
                    else:
                        multi_cached = None
                if (next_line[0] in multi_delimiters) and (not multi_line):
                    multi_line = True
                    multi_delimiter = next_line[0]
                    multi_cache = []
                    continue

                if next_line[0] == "#":
                    try:
                        assert len(set(group_key)) == 1
                    except AssertionError:
                        print(group_key)
                        log("error", f"Multiple keys structure found in line {n}:", repr(line))
                        raise MMCIFError(f"Multiple keys structure found")
                        exit()
                        continue
                    group_key = group_key[0]
                    if exclude is not None:
                        if group_key in exclude:
                            continue
                    if subset is not None:
                        if group_key not in subset:
                            continue
                    if group_key not in data.keys():
                        data[group_key] = []
                    for i, l in enumerate(loop_values):
                        try:
                            assert len(l) == len(loop_keys)
                        except AssertionError:
                            log("warning", f"Missmatch on key-value numbers in group {group_key}, element {i}")
                            continue
                        d = {k:interpret(v) for k, v in zip(loop_keys, l)}
                        data[group_key].append(d)
                    #print(f"{group_key} ->", f"list of length: {len(loop_values)}")
    if not as_dict:
        mmcif = MMCIF(data, cif_path=file_path)
        if output_folder is not None:
            save_path = os.path.join(output_folder, f"{name}.header.json")
            log("debug","Headers saved to:", os.path.abspath(save_path))
            mmcif.save(save_path)
        return mmcif
    else:
        return data
def write_atoms(atoms,
file_path,
name=None,
include_misc=True,
preserve_ids=False,
mode='w',
key='_atom_site') ‑> str
Expand source code
def write_atoms(atoms, file_path, name=None, include_misc=True, preserve_ids=False,
                mode="w", key="_atom_site") -> str:
    if len(atoms) == 0:
        return None
    labels = atoms[0]._mmcif_dict( include_misc=include_misc).keys()

    if not file_path.endswith(".cif"):
        file_path += ".cif"
    os.makedirs(os.path.dirname(file_path), exist_ok=True)
    if name is None:
        name = os.path.basename(file_path).split(".")[0]
    try:
        with open(file_path, mode) as f:
            if mode == "w":
                f.write(f"data_{name}\n")
            f.write("#\n")
            f.write("loop_\n")

            for l in labels:
                f.write(f"{key}.{l}\n")
            n = 1
            for a in atoms:
                d = a._mmcif_dict(include_misc=include_misc)
                if not preserve_ids:
                    d["id"] = f"{n:4d}"
                if len(d.values()) != len(labels):
                    log("Warning", f"Atom with inconsistent ({d.values()}/{len(labels)}) labels")
                    continue
                f.write("  ".join(d.values()) + "\n")
                n+=1

        return file_path
    except Exception as e:
        log("error", f"Atom write interrupted, deleting corrupted file: {file_path}")
        os.remove(file_path)
        log("error", "File deleted successfully!")
        raise e

Classes

class BIAtom (data)
Expand source code
class BIAtom(PseudoAtom):
    def __init__(self, data):
        if len(data) == 1:
            data = data[0]
        for k, v in data.items():
            if v == "." or v == "?" or v=="None":
                #print(k, "is empty for:", data["id"])
                #print(data)
                data[k] = None

        essential_labs = [
            "group_PDB",
            "id",
            "type_symbol",
            "label_alt_id",
            "label_atom_id",
            "label_comp_id",
            "label_seq_id",
            "label_asym_id",
            "label_entity_id",
            "auth_atom_id",
            "auth_comp_id",
            "auth_seq_id",
            "auth_asym_id",
            "Cartn_x",
            "Cartn_y",
            "Cartn_z",
            "occupancy",
            "B_iso_or_equiv",
            "pdbx_PDB_model_num",
            "pdbx_PDB_ins_code",

        ]


        self.misc = {}
        for k, v in data.items():
            if not (k  in essential_labs):
                self.misc[k] = v


        #ATOM
        self.atomnum = int(data["id"])
        self.type = data["group_PDB"] # ATOM / HETATM
        self.element = data["type_symbol"].strip() #C
        self.name = data["label_atom_id"].strip() # CA (Auto)
        self.name2 = data["auth_atom_id"].strip() # CA (Given)
        self.prime = False
        if "'" in self.name:
            self.prime = True
            self.name = self.name.replace("'", "").strip()
            self.name2 = self.name2.replace("'", "").strip()

        #RES
        self.resname = data["label_comp_id"] # Auto
        self.resname2 = data["auth_comp_id"] # Given
        self.resseq = data["label_seq_id"] # Auto
        if self.resseq is not None: self.resseq = int(self.resseq)
        self.resnum = data["auth_seq_id"] # Given
        if self.resnum is not None: self.resnum = int(self.resnum)

        #CHAIN
        self.chain = data["label_asym_id"] # Auto
        self.complex = data["auth_asym_id"] # Given
        self.entity = int(data["label_entity_id"])
        self.model = int(data["pdbx_PDB_model_num"])

        #PROPERTIES
        self.id = (self.name,  self.resnum, self.complex) # Ambiguous (author)
        self.id2 = (self.name,  self.resseq, self.chain, self.entity) # Not ambiguous (label)
        self.id3 = (self.atomnum, self.type, self.element, self.name, self.resseq, self.chain, self.model) # Unique
        x = float(data["Cartn_x"])
        y = float(data["Cartn_y"])
        z = float(data["Cartn_z"])
        coord = (x, y, z)
        super().__init__(coord)
        self.occupancy = float(data["occupancy"])
        self.b = float(data["B_iso_or_equiv"])
        self.ins_code = data.get("pdbx_PDB_ins_code", None)
        

        



        #DISORDER
        if "label_alt_id" not in data:
            data["label_alt_id"] = "."
        self.alt_id = data["label_alt_id"]
        if self.alt_id is None or self.alt_id == "None":
            self.alt_id = "."

        if self.alt_id == ".":
            self.disordered = False
            self.doppelgangers = None
            self.favourite = None
        else:
            self.disordered = True
            self.doppelgangers = []
            self.favourite = True



    def print_full(self):
        if self.disordered:
            if self.favourite:
                r = "\n<bi.{} id={}.{} b={} occupancy={} (disordred)>".format(self.__class__.__name__, self.id, self.alt_id, self.b, self.occupancy)
                for datm in self.doppelgangers:
                    r += "\n - <bi.{} id={}.{} b={} occupancy={} (disordered)>".format(self.__class__.__name__, datm.id, datm.alt_id, datm.b, datm.occupancy)
                return r
            else:
                return "<bi.{} id={}.{} b={} occupancy={} (disordred/not-favourite)>".format(self.__class__.__name__, self.id, self.alt_id, self.b, self.occupancy)
        else:
            return "<bi.{} id={} b={}>".format(self.__class__.__name__, self.id, self.b)

    def __repr__(self):
        if self.disordered:
            if self.favourite:
                return "<bi.{} id={}.{} b={} occupancy={} (disordred)>".format(self.__class__.__name__, self.id, self.alt_id, self.b, self.occupancy)
            else:
                return "<bi.{} id={}.{} b={} occupancy={} (disordred/not-favourite)>".format(self.__class__.__name__, self.id, self.alt_id, self.b, self.occupancy)
        else:
            return "<bi.{} id={} b={}>".format(self.__class__.__name__, self.id, self.b)



    def __iter__(self):
        if not self.disordered or self.doppelgangers is None:
            self.i = 0
            #raise AtomDisorderException("Trying to iterate over a non-disordered atom")
            return self
        else:
            self.i = 0
            return self

    def __next__(self):
        if not self.disordered or self.doppelgangers is None:
            if self.i == 0:
                self.i += 1
                return self
            else:
                self.i = None
                raise StopIteration

        if self.i > len(self.doppelgangers):
            self.i = None
            raise StopIteration
        else:
            if self.i == 0:
                self.i += 1
                return self
            else:
                self.i += 1
                #print(len(self.doppelgangers), self.i, self.i-2)
                return self.doppelgangers[self.i - 2]

    def set_bfactor(self, bfactor):
        for t in self:
            t.b = float(bfactor)
        return self.b

    def set_coord(self, coord):
        for t in self:
            t.coord = float(coord)
        return self.b


    def set_misc(self, label, value):

        for t in self:
            t.misc[label] = value
        return self.misc[label]

    def get_misc(self, label, keyerror="undefined"):
        if label in self.misc:
            return self.misc[label]
        else:
            if keyerror == "undefined":
                raise KeyError
            else:
                return keyerror

    def pdb_string(self, new_id=None):
        record_name = f"{self.type:<6s}"[-6:]
        if new_id is None:
            atom_serial_number = f"{self.atomnum:5d}"[-5:]
        else:
            atom_serial_number = f"{new_id:5d}"[-5:]
        if len(self.element) >= 2 or len(self.name) >= 4:
            atom_name = f"{self.name:<4s}"[-4:]
        else:
            atom_name = f" {self.name:<3s}"[-4:]
        if self.alt_id is None or self.alt_id==".":
            alternate_location_indicator = " "
        else:
            alternate_location_indicator = f"{self.alt_id:1s}"[0]
        residue_name = f"{self.resname:>3s}"[:3]
        chain_identifier = f"{self.chain:1s}"[0]
        residue_sequence_number = f"{self.resnum:>4d}"[-4:]
        code_for_insertions_of_residues = " " # ??
        xcoord = f"{self.x:>8.3f}"[:8]
        ycoord = f"{self.y:>8.3f}"[:8]
        zcoord = f"{self.z:>8.3f}"[:8]
        occupancy = f"{self.occupancy:>6.2f}"[-6:]
        temperature_factor = f"{self.b:>6.2f}"[-6:]
        segment_identifier = f"    " # ??
        element_symbol = f"{self.element:>2s}"[:2]
        charge = "  " # ??



        string = ""
        string+=record_name # 1-6
        string+=atom_serial_number # 7-11
        string+=" " # 12
        string+=atom_name # 13-16
        string+=alternate_location_indicator # 17
        string+=residue_name # 18-20
        string+=" " # 21
        string+=chain_identifier # 22
        string+=residue_sequence_number # 23-26
        string+=code_for_insertions_of_residues # 26
        string+="   " # 28-30
        string+=xcoord # 31-38
        string+=ycoord # 39-46
        string+=zcoord # 47-54
        string+=occupancy # 55-60
        string+=temperature_factor # 61-66
        string+= "      " # 67-72
        string+=segment_identifier # 73-76
        string+=element_symbol # 77-78
        string+=charge # 79-80

        return string





    @staticmethod
    def _none_point(val):
        if val is None:
            return "."
        if type(val) == float:
            return f"{val:7.3f}"
        elif type(val) == int:
            return f"{val:>3d}"
        elif type(val) in [list, tuple, dict]:
            log("warning", "Data type not insertable in mmcif:", type(val))
            return "."
        else:
            return f"{str(val):<3s}"


    def _mmcif_dict(self, include_misc=True):

        try:
            data = {}
            data["group_PDB"] = f"{self.type:6s}"
            data["id"] = f"{self.atomnum:4d}"


            data["label_alt_id"] = f"{self._none_point(self.alt_id):>1s}"
            data["type_symbol"] = f"{self._none_point(self.element):<2s}"




            if self.prime and not self.name.endswith("'"): data["label_atom_id"] = f"\"{self.name:>2s}'\""
            else: data["label_atom_id"] = f"{self._none_point(self.name):<5s}"

            data["label_comp_id"] =f"{self._none_point(self.resname):<3s}"
            data["label_seq_id"] = f"{self._none_point(self.resseq):>3s}"
            data["label_asym_id"] = f"{self._none_point(self.chain):1s}"
            data["label_entity_id"] = f"{self._none_point(self.entity):1s}"

            if self.prime and not self.name2.endswith("'"): data["auth_atom_id"] = f"\"{self._none_point(self.name2):>2s}'\""
            else: data["auth_atom_id"] = f"{self._none_point(self.name2):<5s}"

            data["auth_comp_id"] =f"{self._none_point(self.resname2):<3s}"
            data["auth_seq_id"] = f"{self._none_point(self.resnum):>3s}"
            data["auth_asym_id"] = f"{self._none_point(self.complex):1s}"

            data["pdbx_PDB_model_num"] = f"{self._none_point(self.model):>2s}"
            data["Cartn_x"] = f"{self.x:7.3f}"
            data["Cartn_y"] = f"{self.y:7.3f}"
            data["Cartn_z"] = f"{self.z:7.3f}"
            data["occupancy"] = f"{self.occupancy:6.3f}"
            data["B_iso_or_equiv"] = f"{self.b:6.2f}"
            data["pdbx_PDB_ins_code"] = f"{self._none_point(self.ins_code):>1s}"


        except Exception as e:
            log("ERROR", "Generating atom mmcif dict")
            print(self)
            print(self.__dict__)
            raise e


        if include_misc:
            for k, v in self.misc.items():
                data[k] = self._none_point(v)
        return data

Ancestors

Methods

def get_misc(self, label, keyerror='undefined')
Expand source code
def get_misc(self, label, keyerror="undefined"):
    if label in self.misc:
        return self.misc[label]
    else:
        if keyerror == "undefined":
            raise KeyError
        else:
            return keyerror
def pdb_string(self, new_id=None)
Expand source code
def pdb_string(self, new_id=None):
    record_name = f"{self.type:<6s}"[-6:]
    if new_id is None:
        atom_serial_number = f"{self.atomnum:5d}"[-5:]
    else:
        atom_serial_number = f"{new_id:5d}"[-5:]
    if len(self.element) >= 2 or len(self.name) >= 4:
        atom_name = f"{self.name:<4s}"[-4:]
    else:
        atom_name = f" {self.name:<3s}"[-4:]
    if self.alt_id is None or self.alt_id==".":
        alternate_location_indicator = " "
    else:
        alternate_location_indicator = f"{self.alt_id:1s}"[0]
    residue_name = f"{self.resname:>3s}"[:3]
    chain_identifier = f"{self.chain:1s}"[0]
    residue_sequence_number = f"{self.resnum:>4d}"[-4:]
    code_for_insertions_of_residues = " " # ??
    xcoord = f"{self.x:>8.3f}"[:8]
    ycoord = f"{self.y:>8.3f}"[:8]
    zcoord = f"{self.z:>8.3f}"[:8]
    occupancy = f"{self.occupancy:>6.2f}"[-6:]
    temperature_factor = f"{self.b:>6.2f}"[-6:]
    segment_identifier = f"    " # ??
    element_symbol = f"{self.element:>2s}"[:2]
    charge = "  " # ??



    string = ""
    string+=record_name # 1-6
    string+=atom_serial_number # 7-11
    string+=" " # 12
    string+=atom_name # 13-16
    string+=alternate_location_indicator # 17
    string+=residue_name # 18-20
    string+=" " # 21
    string+=chain_identifier # 22
    string+=residue_sequence_number # 23-26
    string+=code_for_insertions_of_residues # 26
    string+="   " # 28-30
    string+=xcoord # 31-38
    string+=ycoord # 39-46
    string+=zcoord # 47-54
    string+=occupancy # 55-60
    string+=temperature_factor # 61-66
    string+= "      " # 67-72
    string+=segment_identifier # 73-76
    string+=element_symbol # 77-78
    string+=charge # 79-80

    return string
def print_full(self)
Expand source code
def print_full(self):
    if self.disordered:
        if self.favourite:
            r = "\n<bi.{} id={}.{} b={} occupancy={} (disordred)>".format(self.__class__.__name__, self.id, self.alt_id, self.b, self.occupancy)
            for datm in self.doppelgangers:
                r += "\n - <bi.{} id={}.{} b={} occupancy={} (disordered)>".format(self.__class__.__name__, datm.id, datm.alt_id, datm.b, datm.occupancy)
            return r
        else:
            return "<bi.{} id={}.{} b={} occupancy={} (disordred/not-favourite)>".format(self.__class__.__name__, self.id, self.alt_id, self.b, self.occupancy)
    else:
        return "<bi.{} id={} b={}>".format(self.__class__.__name__, self.id, self.b)
def set_bfactor(self, bfactor)
Expand source code
def set_bfactor(self, bfactor):
    for t in self:
        t.b = float(bfactor)
    return self.b
def set_coord(self, coord)
Expand source code
def set_coord(self, coord):
    for t in self:
        t.coord = float(coord)
    return self.b
def set_misc(self, label, value)
Expand source code
def set_misc(self, label, value):

    for t in self:
        t.misc[label] = value
    return self.misc[label]

Inherited members

class BIChain (*args, **kwargs)
Expand source code
class BIChain(BIEntity):
    child_class = BIResidue
    extension = "chain"
    level = "chain"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.paths["sub_folder"] = "chains"

    def find_id(self, method="first"):

        if method == "first":
            chain_id = self.atoms()[0].chain
        else:
            chain_id = method
        return chain_id


    def id(self):
        return self.data["info"]["chain_id"]



    def set_chain_id(self, chain_id=None, complex=False):
        if chain_id is None:
            chain_id = self.find_id()
        chain_id = str(chain_id)
        if len(chain_id) != 1:
            chain_id = chain_id.replace("_", "-")

            log(f"warning", "CHAIN ID is longer than 1: {chain_id}")
            self.set_flag("unconventional_chain_id", True)
        self.data["info"]["chain_id"] = chain_id
        if self.has_flag("has_chain_id", True):
            old_name = self.name().split("_")
            for n, o in enumerate(old_name):
                if o == self.id():
                    old_name[n] = self.id()
            new_name = "_".join(old_name)
            self.set_name(new_name, append=False)
        else:
            self.set_name(chain_id, append=True)
            self.set_flag("has_chain_id", True)

        for a in self.all_atoms():
            a.chain = chain_id
            if complex:
                a.complex = chain_id
        return self.id()



    @classmethod
    def from_atoms(cls, atoms, code=None, chain_id=None, overwrite_complex=False, **kwargs):
        self = super().from_atoms(atoms, code, **kwargs)
        self._atoms = atoms
        self.set_chain_id(chain_id, complex=overwrite_complex)
        self.sequence()
        return self

    @classmethod
    def from_file(cls,*args, chain_id=None, overwrite_complex=False, **kwargs):
        self = super().from_file(*args, **kwargs)
        self._atoms = self.atoms(chain=chain_id, hetatm=True, disordered=True)
        self.set_chain_id(chain_id, complex=overwrite_complex)
        self.sequence()
        return self

Ancestors

Subclasses

Static methods

def from_atoms(atoms, code=None, chain_id=None, overwrite_complex=False, **kwargs)
def from_file(*args, chain_id=None, overwrite_complex=False, **kwargs)

Methods

def find_id(self, method='first')
Expand source code
def find_id(self, method="first"):

    if method == "first":
        chain_id = self.atoms()[0].chain
    else:
        chain_id = method
    return chain_id
def id(self)
Expand source code
def id(self):
    return self.data["info"]["chain_id"]
def set_chain_id(self, chain_id=None, complex=False)
Expand source code
def set_chain_id(self, chain_id=None, complex=False):
    if chain_id is None:
        chain_id = self.find_id()
    chain_id = str(chain_id)
    if len(chain_id) != 1:
        chain_id = chain_id.replace("_", "-")

        log(f"warning", "CHAIN ID is longer than 1: {chain_id}")
        self.set_flag("unconventional_chain_id", True)
    self.data["info"]["chain_id"] = chain_id
    if self.has_flag("has_chain_id", True):
        old_name = self.name().split("_")
        for n, o in enumerate(old_name):
            if o == self.id():
                old_name[n] = self.id()
        new_name = "_".join(old_name)
        self.set_name(new_name, append=False)
    else:
        self.set_name(chain_id, append=True)
        self.set_flag("has_chain_id", True)

    for a in self.all_atoms():
        a.chain = chain_id
        if complex:
            a.complex = chain_id
    return self.id()

Inherited members

class BIEntity (export_folder=None, parent=None, use_tmp=False, **kwargs)
Expand source code
class BIEntity(object):
    child_class = None
    extension = "structure"
    level = "structure"
    tmp_folder = "/tmp"
    excluded_from_headers = ["_bi_*", "_atom_site", "_aleph_*","_cell", "_symmetry"]

    def __init__(self, export_folder=None, parent=None, use_tmp=False, **kwargs):
        if export_folder is None:
            export_folder = os.path.join(SUBDIR_NAME, "exports")
        self.children = []
        self.paths = {
            "self": None, # This entity cif path
            "source": None,
            "minimal": None, # This but only nice atoms (no headers or data)
            "parent": None, # Parent entity cif path
            "export_folder": export_folder, # Folder with all exports (default: "bioiain/exports")
            "top_folder": None, # Highest related folder
            "sub_folder": "", # Path of self under top_folder
        }
        self.data = {
            "info": {
                "code": None, # The code of this structure, if any
                "name": None, # The name of this structure (used mainly for file naming)
                "class": self.__class__.__name__,
            },
            "sequences": {
                "aa": None,
            },
            "symmetry": {},

            # Machine
            "embeddings": {},

            # Tools
            "SASA": {},
            "PISA":{},
            "DSSP":{},
        }
        self.headers = {
            "entry":{
                "id":None
            }
        }
        self.flags = {
            "loaded": False,
        }
        self.exporting = ["data", "paths", "flags"]

        #Properties
        self._com = None
        self._kdtree = None

        #CVectors
        self._cvectors = None

        # Children
        self._chains = None
        self._residues = None
        self._atoms = None
        self._mates = None

        # Crystal
        self._card = None
        self._parameters = None
        self._operations = None


        if parent is not None:
            self.paths["export_folder"] = parent.paths["export_folder"]
            self.paths["parent"] = parent.paths["self"]
            self.data["info"]["parent"] = repr(parent)
            self.headers = parent.headers

        if use_tmp:
            self.paths["export_folder"] = os.path.join(self.tmp_folder, self.paths["export_folder"] )


    def clear_cahces(self):
        self._com = None
        self._kdtree = None
        self._chains = None
        self._residues = None
        self._atoms = None
        self._mates = None
        self._cvectors = None

    def __repr__(self):
        if self.is_symmetry():
            return "<{}:{} id={} op={}>".format(self.__class__.__name__, self.code(), self.id(), self.op())
        return "<{}:{} id={} (len:{})>".format(self.__class__.__name__, self.code(), self.id(), len(self))

    def __str__(self):
        return repr(self)

    def __len__(self):
        return len(self.residues())

    def name(self):
        return str(self.data["info"]["name"])

    def set_name(self, name, append=False):
        if append and self.name() is not None:
            self.data["info"]["name"] = self.name() + f"_{name}"
        else:
            self.data["info"]["name"] = str(name)

    def path(self, minimal=False):
        if not minimal:
            if self.paths.get("self", None) is None:
                self.export()
            return self.paths["self"]
        else:
            if self.paths.get("minimal", None) is None:
                self.export(minimal=True)
            return self.paths["minimal"]

    def folder(self):

        folders = []
        if self.paths["export_folder"] is not None:
            folders.append(self.paths["export_folder"])
        if self.paths["top_folder"] is not None:
            folders.append(self.paths["top_folder"])
        if self.paths["sub_folder"] is not None:
            folders.append(self.paths["sub_folder"])

        return os.path.join(*folders)

    def code(self):
        return str(self.data["info"]["code"])

    def id(self):
        return str(self.data["info"]["code"])

    def get_sequence(self, name="aa"):
        return self.data["sequences"][name]

    def set_sequence(self, name, seq):
        self.data["sequences"][name] = seq
        return self.data["sequences"][name]

    def has_flag(self, flag, value=None):
        if value is None:
            return flag in self.flags

        return self.flags.get(flag) == value

    def set_flag(self, flag, value=True):
        self.flags[flag] = value

    def sequence(self, force=False):
        if self.get_sequence() is None or force:
            seq = "".join([r.rn1 for r in self.residues()])
            self.data["sequences"]["aa"] = seq
        return self.get_sequence()

    def structure(self, code=None):
        from .structure import BIStructure
        if code is None:
            code = str(self.data["info"]["code"])
        return BIStructure.from_atoms(self._atoms, code, parent=self)

    def chains(self):
        return self.atoms(as_chains=True, hetatm=True)

    def residues(self):
        return self.atoms(ca_only=False, residues=True)

    def waters(self):
        return self.atoms(ca_only=False, water=True)

    def dna(self):
        return self.atoms(ca_only=False, dna=True)

    def ligands(self, relevant_only=True):
        return [l for l in self.atoms(ca_only=False, ligands=True) if l.relevant]

    def cvectors(self, vc_mode=None):
        if self._cvectors is None or vc_mode != getattr(self, "_vc_mode", None):
            self._calculate_cvectors(vc_mode=vc_mode)
        else:
            log(1, f"Using previously saved CVectors ({vc_mode})...")
        return self._cvectors

    def _calculate_cvectors(self, vc_mode=None):
        log(1, "Calculating CVectors for:", self.name(), f"({vc_mode})")
        from ..aleph.vectors import CVector
        residues = self.residues()
        n_res = len(residues)
        cvector_list = []
        for n, res in enumerate(residues):
            if n == 0 or n == n_res -1:
                continue
            print(f"{n:4d}/{len(residues)-2:4d}", end="\r")
            cvector = CVector(residues[n-1], res, residues[n+1], params=self.params(), symops=self.symops(), entity_centre=self.com(), vc_mode=vc_mode)
            if cvector.trash:
                continue
            cvector_list.append(cvector)
        log(2, f"n CVectors: {len(cvector_list)}")
        self._cvectors = cvector_list
        self._vc_mode = vc_mode
        return cvector_list


    def atoms(self, ca_only=False, hetatm=False, ligands=False, residues=False, dna=False, water=False, hydrogens=False, force=False, group_by_residue=False, disordered=False, as_residues=False, chain=None, group_by_chain=False, as_chains=False, **kwargs):
        from .atom import _fix_disordered
        from .residue import build_res

        target_entities = []
        if as_residues:
            residues = True

        if residues:
            target_entities.append("residue")

        if dna:
            target_entities.append("dna")


        if water or ligands:
            hetatm = True
            ca_only = False

        if water:
            target_entities.append("water")

        if ligands:
            target_entities.append("ligand")


        atoms = self.all_atoms()

        if not disordered:
            atoms = _fix_disordered(atoms)

        if not hetatm:
            atoms = [a for a in atoms if a.type == "ATOM"]
        if not hydrogens:
            atoms = [a for a in atoms if a.element != "H"]
        if ca_only:
            atoms = [a for a in atoms if a.name == "CA"]
        if not water:
            atoms = [a for a in atoms if a.name != "HOH"]
        if chain is not None:
            atoms = [a for a in atoms if a.chain == chain]

        if as_chains or group_by_chain:
            from .chain import BIChain
            chain_list = {}
            for atom in atoms:
                if atom.complex in chain_list.keys():
                    chain_list[atom.complex].append(atom)
                else:
                    chain_list[atom.complex] = [atom]
            if as_chains:
                for ch, atms in chain_list.items():
                    chain_list[ch] = BIChain().from_atoms(atms, self.code(), ch, parent=self)
                chain_list = [ch for ch in chain_list.values()]
            return chain_list

        if group_by_residue or len(target_entities) > 0:
            atoms_by_res = {}
            for atom in atoms:
                if atom.id2[1:] in atoms_by_res:
                    atoms_by_res[atom.id2[1:]].append(atom)
                else:
                    atoms_by_res[atom.id2[1:]] = [atom]

            if len(target_entities) > 0:
                entities = []
                for resatms in atoms_by_res.values():
                    e = build_res(resatms, parent=self)
                    if getattr(e, "type", None) in target_entities:
                        entities.append(e)
                return entities

            return atoms_by_res

        return atoms



    def remove_atom(self, atom):
        try:
            print(len(self._atoms))
            self._atoms.remove(atom)
            print(len(self._atoms))
            log(f"warning", f"Atom {atom} removed")
        except:
            log("warning", f"Atom {atom} not found, not removed")
            return False
        return True

    def set_symmetry(self):
        pass



    @classmethod
    def from_atoms(cls, atoms, code=None, share=True, **kwargs):
        self = cls(**kwargs)
        if share:
            self._atoms = atoms
        else:
            self._atoms = [a.copy() for a in atoms]
        if code is None and kwargs.get("parent", None) is not None:
            code = kwargs["parent"].code()
        if code is not None:
            self.data["info"]["code"] = clean_string(code).upper()
            #self.data["info"]["code"] = str(code)
        self.set_name(self.code())
        self.paths["top_folder"] = self.code()

        return self



    @classmethod
    def from_file(cls, filepath, code="auto", file_format="auto", force=False, check_existing=True, source=None, **kwargs):
        log(1, "Loading from file:", filepath)
        if not os.path.exists(filepath):
            raise FileNotFoundError(filepath)
        self = cls(**kwargs)

        if file_format == "auto":
            file_format = filepath.split(".")[-1]

        if file_format not in ["cif", "pdb"]:
            raise UnknownFormat(file_format)
        try:
            self._all_atoms(filepath=filepath, force=True, is_pdb=file_format == "pdb", **kwargs)
        except (StructureLoadException, CrystalError) as e:
            log("Error", f"Structure not loaded: {filepath}", e)
            raise e
            return None

        if code == "auto":
            code = str(read_mmcif(filepath, subset="_entry")["_entry.id"])

        if code == "file" or code is None:
            code = filepath.split(".")[0]

        self.data["info"]["code"] = clean_string(code).upper()
        self.headers["entry"]["id"] = self.code()
        self.paths["top_folder"] = self.code()
        self.set_name(self.code())
        self.set_flag("fractional", False)

        log(2, "Reading CIF data...")
        data = read_mmcif(filepath, subset="_bi_*")
        if len(data) > 0:
            #print(data)
            for k in data.keys():
                kk = k.split("_")[2]
                ss = "_".join(k.split("_")[3:])
                old_data = getattr(self, kk, {})
                new_data = old_data
                if ss is None or ss == "":
                    new_data = new_data | data[k]
                else:
                    new_data[ss] = new_data.get(ss, {}) | data[k]
                setattr(self, kk, new_data)
            self.set_flag("data_recovered", True)
        else:
            self.set_flag("data_recovered", False)

        if self.paths["self"] != filepath:
            self.paths["source"] = filepath
        elif source is not None:
            self.paths["source"] = source


        if check_existing and not force:
            prev_path = self.export(dry=True)
            if os.path.exists(prev_path):
                log("warning", "Recovering previously exported file:", prev_path)
                try:
                    recovered_self = cls.from_file(prev_path, check_existing=False, source=filepath, **kwargs)
                    if recovered_self is None:
                        raise StructureRecoverException()
                    return recovered_self
                except Exception as e:
                    log("warning", e.__class__.__name__, e)
                    log("Error", "Recovery failed (returning new)")
                    raise

        self.recover_cvectors()

        self.set_flag("loaded", True)
        self.export()
        return self


    def  recover_cvectors(self):
        pass


    def from_biopython(self, entity):
        pass


    def com(self, force=False):
        from ..utilities.maths import find_com
        if self._com is None or force:
            self._com = find_com(self)
        return self._com


    def all_atoms(self):
        if self._atoms is None:
            self._all_atoms()
        return self._atoms

    def _all_atoms(self, filepath=None, force=False, require_crystal=True, **kwargs):
        from .atom import BIAtom


        if filepath is None:
            filepath = self.paths.get("self", None)

        if not hasattr(self, "_atoms"):
            force = True
        elif self._atoms is None:
            force = True

        if force:
            if filepath is None:
                self.export()
            if not os.path.exists(filepath):
                self.export()

            log(1, "Reading atoms from CIF:", filepath)
            mmcif= read_mmcif(filepath, subset=["_atom_site", "_cell", "_symmetry"])
            atoms=mmcif("_atom_site")
            if atoms is None:
                raise StructureLoadException("No atoms")
            self.headers["cell"] = mmcif("_cell")
            self.headers["symmetry"] = mmcif("_symmetry")
            self.data["symmetry"]["in_asu"] = True
            try:
                self._calculate_crystal()
            except CrystalError as e:
                if require_crystal:
                    raise e
            atoms = [BIAtom(a) for a in atoms]

            self._atoms = atoms
        return self._atoms

    def fix_headers(self):
        if self.headers["symmetry"].get("space_group_name_H-M", None) is not None:
            self.headers["symmetry"]["space_group_name_H-M"] = f"\'{self.headers["symmetry"]["space_group_name_H-M"]}\'"


    def export(self, minimal=False, cleanup=False, as_pdb=False, target_folder=None, sufix=None, dry=False, all_headers=True):

        custom_folder = False
        if target_folder is None:
            target_folder = self.paths["export_folder"]

        else:
            custom_folder = True

        fname = str(self.name())
        if sufix is not None:
            custom_folder = True
            if sufix[0] in [".", "-", "_", "(",]:
                fname += sufix
            else:
                fname += "_"+sufix

        fname += f".{self.extension}"
        if minimal:
            fname += ".minimal"
        try:
            base_folder = os.path.join(target_folder, self.paths.get("top_folder", self.code()), self.paths["sub_folder"].strip() )
        except TypeError:
            print(self.paths)
            raise
        os.makedirs(base_folder, exist_ok=True)
        base_path = os.path.join(base_folder, fname)
        if dry: return base_path+".cif"

        log(2, f"Exporting: {self} to {base_path}")
        if self.has_flag("is_fractional", True):
            log("Warning", "A fractional entity was about to be exported!")
            log("Warning", "An orthogonal copy was made for you and exported instead! (only for atoms)")
            orth = self.copy()._to_orthogonal()
        else:
            orth = self

        if minimal:
            minimal_path= orth._export_structure(base_path, headers=False, all_headers=False, misc_fields=True, cleanup=True, as_pdb=as_pdb)
            if not custom_folder:
                self.paths["minimal"] = minimal_path
            return minimal_path
        else:
            path = orth._export_structure(base_path, headers=True, all_headers=all_headers, misc_fields=True, cleanup=cleanup, as_pdb=as_pdb)
            if not custom_folder:
                self.paths["self"] = path
                if not as_pdb:
                    self.set_flag("exported", True)
                    self.paths["data"] = self._export_data(base_path)
            return path

    def _export_data(self, filepath, mode="w") -> str:
        if filepath.endswith(".cif"):
            filepath = filepath.replace(".cif", ".json")
        elif not filepath.endswith(".json"):
            filepath += ".json"
        exp = {e: self.__getattribute__(e) for e in self.exporting if hasattr(self, e)}
        with open(filepath, mode) as f:
            f.write(json.dumps(exp, indent=4))
        return filepath


    def _export_structure(self, filepath:str, atoms:list=None, headers:bool=None, misc_fields:bool=True, cleanup=True, as_pdb=False, all_headers=True, cvectors=True, cvmatrix=True) -> str:

        log(2,"Exporting structure...")
        mode = "w"
        if atoms is None:
            if cleanup:
                atoms = self.atoms()
            else:
                #log("Warning", "Exporting all atoms and misc fields might corrupt the file (cleanup=True recommended)")
                atoms = self._all_atoms()
        if as_pdb:
            return write_pdb_atoms(atoms, filepath, mode=mode, end=True)
        else:

            full_headers = {}
            if all_headers and headers and self.paths.get("source", None) is not None:
                full_headers = read_mmcif(self.paths.get("source", None), exclude=self.excluded_from_headers).dict()
                #print(full_headers.keys())

            if headers:
                for e in self.exporting:
                    d = getattr(self, e)
                    if e == "data":
                        for k, v in d.items():
                            write_dict(v, file_path=filepath, label=f"bi_{e}_{k}", mode=mode, name=self.name())
                            mode = "a"
                    else:
                        write_dict(d, file_path=filepath, label=f"bi_{e}", mode=mode, name=self.name())
                        mode = "a"
                # print(type(self.headers))
                # print(self.headers)


                full_headers = full_headers | self.headers
                for k, d in full_headers.items():
                    if type(d) is list and len(d) > 1:
                        write_dict_list(d, file_path=filepath, label=k, mode=mode, name=self.name())
                        mode = "a"
                    else:
                        if type(d) is list:
                            d = d[0]
                        write_dict(d, file_path=filepath, label=k, mode=mode, name=self.name())
                        mode = "a"

            #print(self)
            #log(3, "CVECTORS", cvectors, self._cvectors is not None)
            #print(self._cvectors)
            #log(3,"CVMATRIX", cvmatrix, getattr(self, "_cvmatrix", None) is not None)
            #print(getattr(self, "_cvmatrix", None))


            if cvectors and (self._cvectors is not None):
                log(3, "Exporting cvectors...")
                write_dict_list(self._cvectors, file_path=filepath, label="aleph_cvectors", mode=mode, name=self.name())
                mode = "a"
            if cvmatrix and (getattr(self, "_cvmatrix", None) is not None):
                log(3, "Exporting cvmatrix...")
                write_dict_list([v.closest_vp for v in self._cvmatrix.vectors], file_path=filepath, label="aleph_cvmatrix", mode=mode, name=self.name())
                mode = "a"
            log(3, "Exporting atoms...")
            return write_atoms(atoms, filepath, name=self.name(), include_misc=misc_fields, mode=mode)

    @classmethod
    def recover_from_id(cls, code, endswith=None, full_name=None, **kwargs):
        placeholder = cls(**kwargs)
        path = os.path.join(placeholder.paths["export_folder"], code, placeholder.paths["sub_folder"])
        if full_name is not None:
            path = os.path.join(path, full_name)
        else:
            if not os.path.exists(path):
                raise StructureNotFound(path)
            for file in os.listdir(path):
                ext = file.split(".")[-1] 
                if ext != "json":
                    continue
                extension= file.split(".")[-2]
                if extension != placeholder.extension:
                    continue
                if endswith is not None:
                    if not file.split(".")[0].endswith(endswith):
                        continue
                path = os.path.join(path, file)
                return cls.recover_from_path(path, **kwargs)

        raise StructureNotFound(path)



    # @classmethod
    # def recover_from_path(cls, cif_path, **kwargs):
    #
    #     # if path.endswith(".json"):
    #     #     data_path = path
    #     #     cif_path = data_path.replace(".json", ".cif")
    #     # elif path.endswith(".cif"):
    #     #     cif_path = path
    #     #     data_path = cif_path.replace(".cif", ".json")
    #     # else:
    #     #     cif_path = path+".cif"
    #     #     data_path = path+".json"
    #     #
    #     # if os.path.exists(cif_path):
    #     #     self = cls.from_file(cif_path, check_existing=False, **kwargs)
    #     #     if self.has_flag("data_recovered", True):
    #     #         log(3, "Recovering data from CIF...")
    #     # else:
    #     #     raise FileNotFoundError(path, cif_path)
    #     # elif os.path.exists(data_path):
    #     #     raw = json.load(open(path, "r"))
    #     #     self = cls.from_file(raw["paths"]["self"], check_existing=False, **kwargs)
    #     # else:
    #     #     raise FileNotFoundError(path, cif_path, data_path)
    #     #
    #     # if os.path.exists(data_path) and raw is None:
    #     #     log(3, "Recovering data from json...")
    #     #     raw = json.load(open(path, "r"))
    #     #     for k, v in raw.items():
    #     #         setattr(self, k, v)
    #     # elif raw is None:
    #     #     log("Warning", "Data file not found for:", path)
    #
    #     return self

    def _calculate_crystal(self):
        try:
            self._get_crystal_card()
            self._get_operations()
        except Exception as e:
            self.set_flag("crystal_error", True)
            raise CrystalError()

    def _get_crystal_card(self):
        cell = self.headers["cell"]

        a = float(cell["length_a"])
        b = float(cell["length_b"])
        c = float(cell["length_c"])
        alpha = float(cell["angle_alpha"])
        beta = float(cell["angle_beta"])
        gamma = float(cell["angle_gamma"])
        Z = float(cell["Z_PDB"])

        card = dict(a=a, b=b, c=c, alpha=alpha, beta=beta, gamma=gamma, Z=Z)
        self._card = card
        return self._card

    def _get_operations(self):
        from ..utilities.space_groups import dictio_space_groups

        space_group_key = self.headers["symmetry"].get("Int_Tables_number")

        space_group_key = int(space_group_key)
        self._operations = dictio_space_groups[space_group_key]
        return self._operations

    def operations(self):
        if self._operations is None:
            self._get_operations()
        return self._operations

    def symops(self, n=None):
        if self._operations is None:
            self._get_operations()
        if n is None:
                return self._operations["symops"]
        else:
            return self._operations["symops"][n]

    def params(self):
        if self._parameters is None:
            self._calculate_parameters()
        return self._parameters


    def _calculate_parameters(self) -> dict:
        if self._card is None:
            self._get_crystal_card()

        card = self._card

        parameters = {}
        parameters["A"] = A = float(card["a"])
        parameters["B"] = B = float(card["b"])
        parameters["C"] = C = float(card["c"])
        parameters["alphaDeg"] = alphaDeg = float(card["alpha"])
        parameters["betaDeg"] = betaDeg = float(card["beta"])
        parameters["gammaDeg"] = gammaDeg = float(card["gamma"])
        parameters["alpha"] = alpha = (alphaDeg * 2 * np.pi) / 360
        parameters["beta"] = beta = (betaDeg * 2 * np.pi) / 360
        parameters["gamma"] = gamma = (gammaDeg * 2 * np.pi) / 360
        parameters["c_a"] = c_a = np.cos(alpha)
        parameters["c_b"] = c_b = np.cos(beta)
        parameters["c_g"] = c_g = np.cos(gamma)
        parameters["s_g"] = s_g = np.sin(gamma)
        parameters["q"] = q = np.sqrt(1 + 2 * c_a * c_b * c_g - c_a ** 2 - c_b ** 2 - c_g ** 2)
        parameters["uu"] = uu = s_g / (q * C)
        parameters["vv"] = vv = (c_b * c_g - c_a) / (q * B * s_g)
        parameters["uuy"] = uuy = 1 / (B * s_g)
        parameters["vvz"] = vvz = -1 * (c_g / (A * s_g))
        parameters["uuz"] = uuz = (c_a * c_g - c_b) / (q * A * s_g)
        parameters["vvy"] = vvy = 1 / A

        self._parameters = parameters
        return self._parameters

    def fragment(self, in_place=False, force=False):
        from ..aleph.fragments import FragmentedStructure
        if isinstance(self, FragmentedStructure):
            if not in_place:
                frag = self.copy()
            else:
                frag = self
        else:
            frag = FragmentedStructure.from_atoms(self.all_atoms(), parent=self, share=in_place, export_folder=self.paths["export_folder"])


        frag.fragments(force=force)
        return frag


    def copy(self):
        from copy import deepcopy
        new = self.__class__.from_atoms(self.all_atoms(), parent=self, share=False)
        new.data = deepcopy(self.data)
        new.flags = deepcopy(self.flags)
        new.set_flag("is_copy", True)
        new.set_flag("exported", False)
        return new

    def displace(self, distance:float|int|list[float|int]|tuple[float|int], inplace=True):
        if not inplace:
            self = self.copy()
        for a in self.all_atoms():
            a + distance
        return self

    def _to_fractional(self):
        if self.has_flag("is_fractional", True):
            raise AlreadyFractional(self)
        for a in self.all_atoms():
            a.to_frac(self.params())
        self.set_flag("is_fractional", True)

    def _to_orthogonal(self):
        if self.has_flag("is_fractional", False):
            raise AlreadyOrthogonal(self)
        for a in self.all_atoms():
            a.to_orth(self.params())
        self.set_flag("is_fractional", False)



    def _symmetry_operation(self, symop):
        was_orth = False
        if self.has_flag("is_fractional", False):
            was_orth = True
            self._to_fractional()

        for a in self.all_atoms():
            a.symop(self.symops(symop), self.params())

        if was_orth:
            self._to_orthogonal()
        return self

    def is_symmetry(self):
        return self.has_flag("is_symmetry", True)

    def op(self):
        return self.data["symmetry"].get("symop", None)


    def symmetry(self, symop, in_place=False):
        if not in_place:
            self = self.copy()

        self._symmetry_operation(symop)
        self.set_flag("is_symmetry", True)
        self.data["symmetry"]["in_asu"] = False
        self.data["symmetry"]["symop"] = symop
        return self


    def mates(self):
        mates = []
        for symop in self.symops().keys():
            mates.append(self.symmetry(symop))
        self._mates = mates
        return self._mates

    def show(self, execute=True, script=None):
        if script is None:
            from ..visualisation.pymol import PymolScript
            script = PymolScript(self.name())
        script.load(self.export(), self.name())
        script.spectrum(self.name())
        script.orient()
        script.write_script()
        if execute:
            script.execute()
        return script

    def av_sasa(self, force=False, **kwargs):
        if force or self.data["SASA"].get("average", None) is not None:
            av = 0
            total = 0
            sasas = self.sasa_list(**kwargs)
            for s in sasas:
                if s is not None:
                    av += s
                    total += 1
            if total == 0:
                av = None
            else:
                av /= total
            self.data["SASA"]["average"] = av
        return self.data["SASA"]["average"]

    def sasa_list(self, **kwargs):
        self.calculate_sasa(**kwargs)
        sasas = []
        for a in self.atoms(**kwargs):
            sasas.append(a.get_misc("SASA", None))
        return sasas

    def calculate_sasa(self, force=False, **kwargs):
        if not self.has_flag("sasa_calculated") or force:
            from ..tools.SASA import SASA
            sasa = SASA(**kwargs)
            sasa.compute(self, **kwargs)
        return self

    def assembly_level(self):
        pass

    def _calculate_pisa(self, **kwargs):
        from ..tools.PISA import PISA
        pisa = PISA(pisa_id=self.name(), **kwargs)

Subclasses

Class variables

var child_class

The type of the None singleton.

var excluded_from_headers

The type of the None singleton.

var extension

The type of the None singleton.

var level

The type of the None singleton.

var tmp_folder

The type of the None singleton.

Static methods

def from_atoms(atoms, code=None, share=True, **kwargs)
def from_file(filepath,
code='auto',
file_format='auto',
force=False,
check_existing=True,
source=None,
**kwargs)
def recover_from_id(code, endswith=None, full_name=None, **kwargs)

Methods

def all_atoms(self)
Expand source code
def all_atoms(self):
    if self._atoms is None:
        self._all_atoms()
    return self._atoms
def assembly_level(self)
Expand source code
def assembly_level(self):
    pass
def atoms(self,
ca_only=False,
hetatm=False,
ligands=False,
residues=False,
dna=False,
water=False,
hydrogens=False,
force=False,
group_by_residue=False,
disordered=False,
as_residues=False,
chain=None,
group_by_chain=False,
as_chains=False,
**kwargs)
Expand source code
def atoms(self, ca_only=False, hetatm=False, ligands=False, residues=False, dna=False, water=False, hydrogens=False, force=False, group_by_residue=False, disordered=False, as_residues=False, chain=None, group_by_chain=False, as_chains=False, **kwargs):
    from .atom import _fix_disordered
    from .residue import build_res

    target_entities = []
    if as_residues:
        residues = True

    if residues:
        target_entities.append("residue")

    if dna:
        target_entities.append("dna")


    if water or ligands:
        hetatm = True
        ca_only = False

    if water:
        target_entities.append("water")

    if ligands:
        target_entities.append("ligand")


    atoms = self.all_atoms()

    if not disordered:
        atoms = _fix_disordered(atoms)

    if not hetatm:
        atoms = [a for a in atoms if a.type == "ATOM"]
    if not hydrogens:
        atoms = [a for a in atoms if a.element != "H"]
    if ca_only:
        atoms = [a for a in atoms if a.name == "CA"]
    if not water:
        atoms = [a for a in atoms if a.name != "HOH"]
    if chain is not None:
        atoms = [a for a in atoms if a.chain == chain]

    if as_chains or group_by_chain:
        from .chain import BIChain
        chain_list = {}
        for atom in atoms:
            if atom.complex in chain_list.keys():
                chain_list[atom.complex].append(atom)
            else:
                chain_list[atom.complex] = [atom]
        if as_chains:
            for ch, atms in chain_list.items():
                chain_list[ch] = BIChain().from_atoms(atms, self.code(), ch, parent=self)
            chain_list = [ch for ch in chain_list.values()]
        return chain_list

    if group_by_residue or len(target_entities) > 0:
        atoms_by_res = {}
        for atom in atoms:
            if atom.id2[1:] in atoms_by_res:
                atoms_by_res[atom.id2[1:]].append(atom)
            else:
                atoms_by_res[atom.id2[1:]] = [atom]

        if len(target_entities) > 0:
            entities = []
            for resatms in atoms_by_res.values():
                e = build_res(resatms, parent=self)
                if getattr(e, "type", None) in target_entities:
                    entities.append(e)
            return entities

        return atoms_by_res

    return atoms
def av_sasa(self, force=False, **kwargs)
Expand source code
def av_sasa(self, force=False, **kwargs):
    if force or self.data["SASA"].get("average", None) is not None:
        av = 0
        total = 0
        sasas = self.sasa_list(**kwargs)
        for s in sasas:
            if s is not None:
                av += s
                total += 1
        if total == 0:
            av = None
        else:
            av /= total
        self.data["SASA"]["average"] = av
    return self.data["SASA"]["average"]
def calculate_sasa(self, force=False, **kwargs)
Expand source code
def calculate_sasa(self, force=False, **kwargs):
    if not self.has_flag("sasa_calculated") or force:
        from ..tools.SASA import SASA
        sasa = SASA(**kwargs)
        sasa.compute(self, **kwargs)
    return self
def chains(self)
Expand source code
def chains(self):
    return self.atoms(as_chains=True, hetatm=True)
def clear_cahces(self)
Expand source code
def clear_cahces(self):
    self._com = None
    self._kdtree = None
    self._chains = None
    self._residues = None
    self._atoms = None
    self._mates = None
    self._cvectors = None
def code(self)
Expand source code
def code(self):
    return str(self.data["info"]["code"])
def com(self, force=False)
Expand source code
def com(self, force=False):
    from ..utilities.maths import find_com
    if self._com is None or force:
        self._com = find_com(self)
    return self._com
def copy(self)
Expand source code
def copy(self):
    from copy import deepcopy
    new = self.__class__.from_atoms(self.all_atoms(), parent=self, share=False)
    new.data = deepcopy(self.data)
    new.flags = deepcopy(self.flags)
    new.set_flag("is_copy", True)
    new.set_flag("exported", False)
    return new
def cvectors(self, vc_mode=None)
Expand source code
def cvectors(self, vc_mode=None):
    if self._cvectors is None or vc_mode != getattr(self, "_vc_mode", None):
        self._calculate_cvectors(vc_mode=vc_mode)
    else:
        log(1, f"Using previously saved CVectors ({vc_mode})...")
    return self._cvectors
def displace(self,
distance: float | int | list[float | int] | tuple[float | int],
inplace=True)
Expand source code
def displace(self, distance:float|int|list[float|int]|tuple[float|int], inplace=True):
    if not inplace:
        self = self.copy()
    for a in self.all_atoms():
        a + distance
    return self
def dna(self)
Expand source code
def dna(self):
    return self.atoms(ca_only=False, dna=True)
def export(self,
minimal=False,
cleanup=False,
as_pdb=False,
target_folder=None,
sufix=None,
dry=False,
all_headers=True)
Expand source code
def export(self, minimal=False, cleanup=False, as_pdb=False, target_folder=None, sufix=None, dry=False, all_headers=True):

    custom_folder = False
    if target_folder is None:
        target_folder = self.paths["export_folder"]

    else:
        custom_folder = True

    fname = str(self.name())
    if sufix is not None:
        custom_folder = True
        if sufix[0] in [".", "-", "_", "(",]:
            fname += sufix
        else:
            fname += "_"+sufix

    fname += f".{self.extension}"
    if minimal:
        fname += ".minimal"
    try:
        base_folder = os.path.join(target_folder, self.paths.get("top_folder", self.code()), self.paths["sub_folder"].strip() )
    except TypeError:
        print(self.paths)
        raise
    os.makedirs(base_folder, exist_ok=True)
    base_path = os.path.join(base_folder, fname)
    if dry: return base_path+".cif"

    log(2, f"Exporting: {self} to {base_path}")
    if self.has_flag("is_fractional", True):
        log("Warning", "A fractional entity was about to be exported!")
        log("Warning", "An orthogonal copy was made for you and exported instead! (only for atoms)")
        orth = self.copy()._to_orthogonal()
    else:
        orth = self

    if minimal:
        minimal_path= orth._export_structure(base_path, headers=False, all_headers=False, misc_fields=True, cleanup=True, as_pdb=as_pdb)
        if not custom_folder:
            self.paths["minimal"] = minimal_path
        return minimal_path
    else:
        path = orth._export_structure(base_path, headers=True, all_headers=all_headers, misc_fields=True, cleanup=cleanup, as_pdb=as_pdb)
        if not custom_folder:
            self.paths["self"] = path
            if not as_pdb:
                self.set_flag("exported", True)
                self.paths["data"] = self._export_data(base_path)
        return path
def fix_headers(self)
Expand source code
def fix_headers(self):
    if self.headers["symmetry"].get("space_group_name_H-M", None) is not None:
        self.headers["symmetry"]["space_group_name_H-M"] = f"\'{self.headers["symmetry"]["space_group_name_H-M"]}\'"
def folder(self)
Expand source code
def folder(self):

    folders = []
    if self.paths["export_folder"] is not None:
        folders.append(self.paths["export_folder"])
    if self.paths["top_folder"] is not None:
        folders.append(self.paths["top_folder"])
    if self.paths["sub_folder"] is not None:
        folders.append(self.paths["sub_folder"])

    return os.path.join(*folders)
def fragment(self, in_place=False, force=False)
Expand source code
def fragment(self, in_place=False, force=False):
    from ..aleph.fragments import FragmentedStructure
    if isinstance(self, FragmentedStructure):
        if not in_place:
            frag = self.copy()
        else:
            frag = self
    else:
        frag = FragmentedStructure.from_atoms(self.all_atoms(), parent=self, share=in_place, export_folder=self.paths["export_folder"])


    frag.fragments(force=force)
    return frag
def from_biopython(self, entity)
Expand source code
def from_biopython(self, entity):
    pass
def get_sequence(self, name='aa')
Expand source code
def get_sequence(self, name="aa"):
    return self.data["sequences"][name]
def has_flag(self, flag, value=None)
Expand source code
def has_flag(self, flag, value=None):
    if value is None:
        return flag in self.flags

    return self.flags.get(flag) == value
def id(self)
Expand source code
def id(self):
    return str(self.data["info"]["code"])
def is_symmetry(self)
Expand source code
def is_symmetry(self):
    return self.has_flag("is_symmetry", True)
def ligands(self, relevant_only=True)
Expand source code
def ligands(self, relevant_only=True):
    return [l for l in self.atoms(ca_only=False, ligands=True) if l.relevant]
def mates(self)
Expand source code
def mates(self):
    mates = []
    for symop in self.symops().keys():
        mates.append(self.symmetry(symop))
    self._mates = mates
    return self._mates
def name(self)
Expand source code
def name(self):
    return str(self.data["info"]["name"])
def op(self)
Expand source code
def op(self):
    return self.data["symmetry"].get("symop", None)
def operations(self)
Expand source code
def operations(self):
    if self._operations is None:
        self._get_operations()
    return self._operations
def params(self)
Expand source code
def params(self):
    if self._parameters is None:
        self._calculate_parameters()
    return self._parameters
def path(self, minimal=False)
Expand source code
def path(self, minimal=False):
    if not minimal:
        if self.paths.get("self", None) is None:
            self.export()
        return self.paths["self"]
    else:
        if self.paths.get("minimal", None) is None:
            self.export(minimal=True)
        return self.paths["minimal"]
def recover_cvectors(self)
Expand source code
def  recover_cvectors(self):
    pass
def remove_atom(self, atom)
Expand source code
def remove_atom(self, atom):
    try:
        print(len(self._atoms))
        self._atoms.remove(atom)
        print(len(self._atoms))
        log(f"warning", f"Atom {atom} removed")
    except:
        log("warning", f"Atom {atom} not found, not removed")
        return False
    return True
def residues(self)
Expand source code
def residues(self):
    return self.atoms(ca_only=False, residues=True)
def sasa_list(self, **kwargs)
Expand source code
def sasa_list(self, **kwargs):
    self.calculate_sasa(**kwargs)
    sasas = []
    for a in self.atoms(**kwargs):
        sasas.append(a.get_misc("SASA", None))
    return sasas
def sequence(self, force=False)
Expand source code
def sequence(self, force=False):
    if self.get_sequence() is None or force:
        seq = "".join([r.rn1 for r in self.residues()])
        self.data["sequences"]["aa"] = seq
    return self.get_sequence()
def set_flag(self, flag, value=True)
Expand source code
def set_flag(self, flag, value=True):
    self.flags[flag] = value
def set_name(self, name, append=False)
Expand source code
def set_name(self, name, append=False):
    if append and self.name() is not None:
        self.data["info"]["name"] = self.name() + f"_{name}"
    else:
        self.data["info"]["name"] = str(name)
def set_sequence(self, name, seq)
Expand source code
def set_sequence(self, name, seq):
    self.data["sequences"][name] = seq
    return self.data["sequences"][name]
def set_symmetry(self)
Expand source code
def set_symmetry(self):
    pass
def show(self, execute=True, script=None)
Expand source code
def show(self, execute=True, script=None):
    if script is None:
        from ..visualisation.pymol import PymolScript
        script = PymolScript(self.name())
    script.load(self.export(), self.name())
    script.spectrum(self.name())
    script.orient()
    script.write_script()
    if execute:
        script.execute()
    return script
def structure(self, code=None)
Expand source code
def structure(self, code=None):
    from .structure import BIStructure
    if code is None:
        code = str(self.data["info"]["code"])
    return BIStructure.from_atoms(self._atoms, code, parent=self)
def symmetry(self, symop, in_place=False)
Expand source code
def symmetry(self, symop, in_place=False):
    if not in_place:
        self = self.copy()

    self._symmetry_operation(symop)
    self.set_flag("is_symmetry", True)
    self.data["symmetry"]["in_asu"] = False
    self.data["symmetry"]["symop"] = symop
    return self
def symops(self, n=None)
Expand source code
def symops(self, n=None):
    if self._operations is None:
        self._get_operations()
    if n is None:
            return self._operations["symops"]
    else:
        return self._operations["symops"][n]
def waters(self)
Expand source code
def waters(self):
    return self.atoms(ca_only=False, water=True)
class BIResidue (atoms, require_ca=True, **kwargs)
Expand source code
class BIResidue(object):
    child_class = BIAtom
    type="residue"
    def __init__(self, atoms, require_ca=True, **kwargs):
        if type(atoms) == dict:
            atoms = atoms.values()
        self.atoms = [a for a in atoms if a.element != "H"]
        self.ca = None
        self.cb = None
        self.c = None
        self.o = None
        self.n = None
        self.resnum = None
        self.resname = None
        self.rn1 = None
        self.resseq = None
        self.chain = None
        self.complex = None
        self.fragment = None
        self.is_residue = True
        self.is_disordered = False

        self._sasa = None
        self._av_sasa = None
        self._norm_sasa = None


            

        for a in self.atoms:
            if len(a.resname) == 2:
                log("Warning", "(DEPRECATED use to initialise a nucleotide. Use build res instead")
                self.__class__ = BINucleoutide
                self.__init__(self.atoms)
                break

            if a.name == "CA":
                self.ca = a
            elif a.name == "CB":
                self.cb = a
            elif a.name == "C":
                self.c = a
            elif a.name == "O":
                self.o = a
            elif a.name == "N":
                self.n = a
        self.backbone = [self.ca, self.c, self.o, self.n]

        if self.is_residue:

            if len(self.atoms) == 1:
                log("Warning", "Only one atom given to residue, treating as CA")
                self.ca = self.atoms[0]
            if self.ca is None:
                if require_ca:
                    log("error", "Trying to initialise residue with no CA")
                    print([a.name for a in self.atoms])
                    raise NoCaFound()

            self.set_fragment()

            self.resnum = self.ca.resnum
            self.resname = self.ca.resname
            try:
                self.rn1 = d3to1[self.resname]
            except:
                self.rn1 = "X"

            self.resseq = self.ca.resseq
            self.chain = self.ca.chain
            self.entity = self.ca.entity
            self.complex = self.ca.complex

            self.is_disordered = not self.ca.ins_code is None
            if self.is_disordered:
                raise NotImplementedError()

            if self.fragment is None:
                self.id = ( self.resname, self.resnum, self.resseq, self.chain, self.complex , self.entity)
            else:
                self.id = ( self.resname, self.resnum, self.resseq, self.chain, self.entity, self.complex, self.fragment)

            if any([a is None for a in self.backbone]):
                log("error", "Trying to initialise residue with no backbone")
                print(self.backbone)
                print(self)
                raise NoBackbone()

    def __repr__(self):
        return f"<bi.{self.__class__.__name__} id={self.id}>"

    def name(self):
        return "_".join([str(v) for v in self.id])

    def to_atoms(self, key, value):
        for atom in self.atoms:
            atom.set_misc(key, value)

    def bfactor(self):
        return self.ca.b
    def set_bfactor(self, bfactor):
        for a in self.atoms:
            a.set_bfactor(bfactor)
        return self

    def set_fragment(self, fragment=None, unset=False):
        if fragment is not None or unset:
            for a in self.atoms:
                a.set_misc(fragment)
        self.fragment = self.ca.get_misc("fragment", None)

    def sasa(self, normalised=False, average=False, force=False):
        if normalised:
            if self._norm_sasa is None or force:
                self._read_sasa()
            return self._norm_sasa
        elif average:
            if self._av_sasa is None or force:
                self._read_sasa()
            return self._av_sasa
        else:
            if self._sasa is None or force:
                self._read_sasa()
            return self._sasa

    def _read_sasa(self):
        from ..tools.SASA import residue_sasas
        summ = 0
        total = 0
        for a in self.atoms:
            s = a.get_misc("SASA")
            summ += s
            total += 1
        av = summ / total if total != 0 else None
        self._sasa = summ
        self._av_sasa = av
        self._norm_sasa = min(1, summ / residue_sasas[self.resname]) if self.resname in residue_sasas and av is not None else None
        #print( self._av_sasa, self._sasa, residue_sasas[self.resname], self._norm_sasa)
        return self._sasa

Class variables

var child_class

The type of the None singleton.

var type

The type of the None singleton.

Methods

def bfactor(self)
Expand source code
def bfactor(self):
    return self.ca.b
def name(self)
Expand source code
def name(self):
    return "_".join([str(v) for v in self.id])
def sasa(self, normalised=False, average=False, force=False)
Expand source code
def sasa(self, normalised=False, average=False, force=False):
    if normalised:
        if self._norm_sasa is None or force:
            self._read_sasa()
        return self._norm_sasa
    elif average:
        if self._av_sasa is None or force:
            self._read_sasa()
        return self._av_sasa
    else:
        if self._sasa is None or force:
            self._read_sasa()
        return self._sasa
def set_bfactor(self, bfactor)
Expand source code
def set_bfactor(self, bfactor):
    for a in self.atoms:
        a.set_bfactor(bfactor)
    return self
def set_fragment(self, fragment=None, unset=False)
Expand source code
def set_fragment(self, fragment=None, unset=False):
    if fragment is not None or unset:
        for a in self.atoms:
            a.set_misc(fragment)
    self.fragment = self.ca.get_misc("fragment", None)
def to_atoms(self, key, value)
Expand source code
def to_atoms(self, key, value):
    for atom in self.atoms:
        atom.set_misc(key, value)
class BIStructure (*args, **kwargs)
Expand source code
class BIStructure(BIEntity):
    child_class = BIChain
    extension = "structure"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def structure(self, *args, **kwargs):
        return self

Ancestors

Subclasses

Methods

def structure(self, *args, **kwargs)
Expand source code
def structure(self, *args, **kwargs):
    return self

Inherited members

class Ligand (atoms, parent=None, relevance_threshold=15, **kwargs)
Expand source code
class Ligand(object):
        child_class = BIAtom
        type = "ligand"
        def __init__(self, atoms, parent=None, relevance_threshold=15, **kwargs):
                self.atoms = atoms

                self.name = self.atoms[0].resname
                self.chain = self.atoms[0].chain
                self.complex = self.atoms[0].complex
                self.entity = self.atoms[0].entity
                self.model = self.atoms[0].model

                self.id = (self.name, self.complex)
                self.id2 = (self.name, self.chain)

                self.relevant = True

                self._com = None

                if parent is not None:
                        self._determine_relevance(entity=parent, relevance_threshold=relevance_threshold)


        def _determine_relevance(self, entity=None, relevance_threshold=15):
                self.relevant = False
                if entity is None:
                        self.relevant = True
                else:
                        sa = self._calculate_sasa(entity=entity)
                        if sa < relevance_threshold:
                                self.relevant = True
                        #print("SASA", sa, "LIGAND:", self)


                return self.relevant

        def _calculate_sasa(self, entity=None):
                from ..tools.SASA import SASA
                sasa = SASA()
                sasas = sasa.compute(entity=entity, targets=self.atoms, quiet=True)
                assert len(sasas) == len(self.atoms)

                av_sasa = sum(sasas) / len(sasas)
                self.sasa = av_sasa
                return self.sasa



        def com(self, force=False):
                if self._com is None or force:
                        from ..utilities.maths import find_com
                        self._com = find_com(self.atoms)
                return self._com

        def __repr__(self):
                return f"<bi.{self.__class__.__name__} id={self.id2}({self.complex})>"

Class variables

var child_class

The type of the None singleton.

var type

The type of the None singleton.

Methods

def com(self, force=False)
Expand source code
def com(self, force=False):
        if self._com is None or force:
                from ..utilities.maths import find_com
                self._com = find_com(self.atoms)
        return self._com
class MMCIF (data, cif_path=None)
Expand source code
class MMCIF(object):
    def __init__(self, data, cif_path=None):
        self.data = data
        self.cif_path = cif_path

    def __repr__(self):
        return f"<bi.MMCIF: {self.cif_path} keys={self.keys()}>"

    def save(self, path):
        json.dump(self.data, open(path, "w"), indent=4)

    def keys(self):
        return list(self.data.keys())

    def items(self):
        return self.data.items()

    def dict(self):
        return self.data

    def __len__(self):
        return len(self.data.keys())

    def __getitem__(self, key):
        index = None
        subkey = None
        key = key.split(".")
        #print(key)

        if type(key) is str:
            pass
        elif len(key) == 1:
            key = key[0]
        elif len(key) == 2:
            key, subkey = key

        elif len(key) == 3:
            key, index, subkey = key
        else:
            log("warning", "Invalid key {}".format(key))

        if not key.startswith("_"):
            key = "_" + key
        try:
            d = self.data[key]
            if len(d) == 1 and index is None:
                index = 0
            #print(key, index, subkey)
            if index is None and subkey is None:
                ret = [v for v in d]
            elif index is None:
                #print([v.keys() for n, v in enumerate(d)])
                ret = [v[subkey] for v in d]
            elif subkey is None:
                ret = d[index]
            else:
                ret = d[index][subkey]
        except KeyError as e:
            #print(ret)
            log("warning", f"Key not found: {e} ({key}.{index}.{subkey}) in {self.cif_path}")
            return None

        return ret

    def __call__(self, *args):
        entry = ".".join([*args])
        return self[entry]

    @classmethod
    def read_mmcif(cls, *args, **kwargs):
        self = read_mmcif(*args, as_dict=False, **kwargs)
        return self

Static methods

def read_mmcif(*args, **kwargs)

Methods

def dict(self)
Expand source code
def dict(self):
    return self.data
def items(self)
Expand source code
def items(self):
    return self.data.items()
def keys(self)
Expand source code
def keys(self):
    return list(self.data.keys())
def save(self, path)
Expand source code
def save(self, path):
    json.dump(self.data, open(path, "w"), indent=4)
class PseudoAtom (coords, fractional=False)
Expand source code
class PseudoAtom(object):
    child_class = None
    def __init__(self, coords, fractional=False):
        self.x, self.y, self.z = coords
        self.coord = (self.x, self.y, self.z)
        self.is_fractional = fractional
        self._all_is_frac = False
        self._last_centre = None
        self._sym_coords = {}
        self.element = getattr(self, "element", None)

    def __repr__(self):
        return f"<bi.{self.__class__.__name__} at: {self.coord} (frac:{self.is_fractional})>"

    def __str__(self):
        return repr(self)


    def __add__(self, other):
        if type(other) in (int, float):
            if len(other) == 1:
                a = (other, other, other)
            elif len(other) == 3:
                a = other
            else:
                raise TypeError
            self.x += a[0]
            self.y += a[1]
            self.z += a[2]
            self.coord = self.x, self.y, self.z
            return self
        else:
            raise NotImplementedError()

    def __sub__(self, other):
        if type(other) in (int, float):
            if len(other) == 1:
                a = (other, other, other)
            elif len(other) == 3:
                a = other
            else:
                raise TypeError
            self.x -= a[0]
            self.y -= a[1]
            self.z -= a[2]
            self.coord = self.x, self.y, self.z
            return self
        else:
            raise NotImplementedError()

    def copy(self):
        from copy import deepcopy
        new = deepcopy(self)
        return new

    @staticmethod
    def _to_frac(coord, params):
        x, y, z = coord
        nx = (x * params["vvy"]) + (y * params["vvz"]) + (z * params["uuz"])
        ny = (y * params["uuy"]) + (z * params["vv"])
        nz = z * params["uu"]
        return nx, ny, nz

    @staticmethod
    def _to_orth(coord, params):
        t1, t2, t3 = coord
        tz = t3 / params["uu"]
        ty = (t2 - tz * params["vv"]) / params["uuy"]
        tx = (t1 - ty * params["vvz"] - tz * params["uuz"]) / params["vvy"]
        return tx, ty, tz


    def to_frac(self, params):
        if self.is_fractional:
            log("Warning", f"Atom: {self} is already fractional")
            raise AlreadyFractional(self)

        self._orth_coords = self.coord

        nx, ny, nz = self._to_frac(self.coord, params)

        self.x = nx
        self.y = ny
        self.z = nz
        self.coord = (self.x, self.y, self.z)
        self.is_fractional = True

        return self

    def to_orth(self, params):
        if not self.is_fractional:
            log("Warning", f"Atom: {self} is already orthogonal")
            raise AlreadyOrthogonal(self)


        self._frac_coords = self.coord

        tx, ty, tz = self._to_orth(self.coord, params)

        self.x = tx
        self.y = ty
        self.z = tz
        self.coord = (self.x, self.y, self.z)
        self.is_fractional = False

        return self

    def closest(self, target, symops, params, centre=None, first_only=True, force=False):

        if not self.is_fractional:
            len_fn = length
        else:
            len_fn = flength

        distances_all = {opn:(v,len_fn(vector(v, target), params=params), pos) for opn, (v, pos) in self.all(symops, params, centre, force=force).items()}
        sorted_all = {k: v for k, v in sorted(distances_all.items(), key=lambda x: x[1][1])}
        #print([(k, d, pos ) for k, (v, d, pos) in sorted_all.items()])
        if first_only:
            for opn, (v, d, pos) in sorted_all.items():
                return v, d, opn, pos
        return sorted_all


    def all(self, symops, params, centre=None, force=False):
        all_coords = {}

        if self._last_centre == centre and  self._all_is_frac == self.is_fractional and not force:
            for opn, symop in symops.items():
                if opn in self._sym_coords.keys():
                    all_coords[opn] = self._sym_coords[opn]
                else:
                    all_coords[opn] = self.at(symop, params, centre=centre)
        else:
            for opn, symop in symops.items():
                all_coords[opn] = self.at(symop, params, centre=centre)
                self._all_is_frac = self.is_fractional
        return all_coords


    def at(self, symop, params, centre=None, centre_is_frac=False):
        if not self.is_fractional:
            p2 = np.array(self._to_frac(self.coord, params))
            was_orth = True
        else:
            was_orth = False
            p2 = np.array(self.coord)

        if centre is None:
            if self.is_fractional:
                centre = np.array(self.coord)
            else:
                centre = np.array(self._to_frac(self.coord, params))
        else:
            if centre_is_frac:
                centre = np.array(centre)
            else:
                centre = np.array(self._to_frac(centre, params))

        p2s = np.array(self._symop(p2, symop)) + 99.5
        delta = ( ( p2s - np.array(centre) ) % 1 ) - 0.5
        new = np.array(centre) + delta

        position = (new - p2s + 99.5)
        for p in position:
            assert p % 1 == 0
        position = tuple([int(p) for p in position])
        if position == (0,0,0):
            position = None

        if was_orth:
            new = self._to_orth(new, params)

        return new, position


    @staticmethod
    def _symop(coord, symop, position=None):

        rot = symop["rot"]
        tra = symop["tra"]

        x, y, z = coord

        nx = (rot[0][0] * x) + (rot[0][1] * y) + (rot[0][2] * z) + tra[0]
        ny = (rot[1][0] * x) + (rot[1][1] * y) + (rot[1][2] * z) + tra[1]
        nz = (rot[2][0] * x) + (rot[2][1] * y) + (rot[2][2] * z) + tra[2]

        if position is not None:
            nx += position[0]
            ny += position[1]
            nz += position[2]

        return nx, ny, nz



    def symop(self, symop, params, position=None):
        if not self.is_fractional:
            self.to_frac(params)
            was_orth = True
        else:
            was_orth = False

        nx, ny, nz = self._symop(self.coord, symop, position=position)

        self.x = nx
        self.y = ny
        self.z = nz
        self.coord = (self.x, self.y, self.z)
        if was_orth:
            self.to_orth(params)

        return self

Subclasses

Class variables

var child_class

The type of the None singleton.

Methods

def all(self, symops, params, centre=None, force=False)
Expand source code
def all(self, symops, params, centre=None, force=False):
    all_coords = {}

    if self._last_centre == centre and  self._all_is_frac == self.is_fractional and not force:
        for opn, symop in symops.items():
            if opn in self._sym_coords.keys():
                all_coords[opn] = self._sym_coords[opn]
            else:
                all_coords[opn] = self.at(symop, params, centre=centre)
    else:
        for opn, symop in symops.items():
            all_coords[opn] = self.at(symop, params, centre=centre)
            self._all_is_frac = self.is_fractional
    return all_coords
def at(self, symop, params, centre=None, centre_is_frac=False)
Expand source code
def at(self, symop, params, centre=None, centre_is_frac=False):
    if not self.is_fractional:
        p2 = np.array(self._to_frac(self.coord, params))
        was_orth = True
    else:
        was_orth = False
        p2 = np.array(self.coord)

    if centre is None:
        if self.is_fractional:
            centre = np.array(self.coord)
        else:
            centre = np.array(self._to_frac(self.coord, params))
    else:
        if centre_is_frac:
            centre = np.array(centre)
        else:
            centre = np.array(self._to_frac(centre, params))

    p2s = np.array(self._symop(p2, symop)) + 99.5
    delta = ( ( p2s - np.array(centre) ) % 1 ) - 0.5
    new = np.array(centre) + delta

    position = (new - p2s + 99.5)
    for p in position:
        assert p % 1 == 0
    position = tuple([int(p) for p in position])
    if position == (0,0,0):
        position = None

    if was_orth:
        new = self._to_orth(new, params)

    return new, position
def closest(self, target, symops, params, centre=None, first_only=True, force=False)
Expand source code
def closest(self, target, symops, params, centre=None, first_only=True, force=False):

    if not self.is_fractional:
        len_fn = length
    else:
        len_fn = flength

    distances_all = {opn:(v,len_fn(vector(v, target), params=params), pos) for opn, (v, pos) in self.all(symops, params, centre, force=force).items()}
    sorted_all = {k: v for k, v in sorted(distances_all.items(), key=lambda x: x[1][1])}
    #print([(k, d, pos ) for k, (v, d, pos) in sorted_all.items()])
    if first_only:
        for opn, (v, d, pos) in sorted_all.items():
            return v, d, opn, pos
    return sorted_all
def copy(self)
Expand source code
def copy(self):
    from copy import deepcopy
    new = deepcopy(self)
    return new
def symop(self, symop, params, position=None)
Expand source code
def symop(self, symop, params, position=None):
    if not self.is_fractional:
        self.to_frac(params)
        was_orth = True
    else:
        was_orth = False

    nx, ny, nz = self._symop(self.coord, symop, position=position)

    self.x = nx
    self.y = ny
    self.z = nz
    self.coord = (self.x, self.y, self.z)
    if was_orth:
        self.to_orth(params)

    return self
def to_frac(self, params)
Expand source code
def to_frac(self, params):
    if self.is_fractional:
        log("Warning", f"Atom: {self} is already fractional")
        raise AlreadyFractional(self)

    self._orth_coords = self.coord

    nx, ny, nz = self._to_frac(self.coord, params)

    self.x = nx
    self.y = ny
    self.z = nz
    self.coord = (self.x, self.y, self.z)
    self.is_fractional = True

    return self
def to_orth(self, params)
Expand source code
def to_orth(self, params):
    if not self.is_fractional:
        log("Warning", f"Atom: {self} is already orthogonal")
        raise AlreadyOrthogonal(self)


    self._frac_coords = self.coord

    tx, ty, tz = self._to_orth(self.coord, params)

    self.x = tx
    self.y = ty
    self.z = tz
    self.coord = (self.x, self.y, self.z)
    self.is_fractional = False

    return self
class Water (atoms, **kwargs)
Expand source code
class Water(object):
        child_class = BIAtom
        type = "water"
        def __init__(self, atoms, **kwargs):
                self.atoms = atoms

                for a in self.atoms:
                        if a.name == "O":
                                self.o = a
                self.resseq = self.o.resseq
                self.id = self.resseq
                self.relevant = False

        def __repr__(self):
                return f"<bi.{self.__class__.__name__} id={self.id}>"

Class variables

var child_class

The type of the None singleton.

var type

The type of the None singleton.