Module bioiain.base.entity

Classes

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)