| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include <Python.h> |
|
|
| #include <algorithm> |
| #include <cstddef> |
| #include <cstdint> |
| #include <iterator> |
| #include <memory> |
| #include <new> |
| #include <optional> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
|
|
| #include "numpy/ndarrayobject.h" |
| #include "numpy/ndarraytypes.h" |
| #include "numpy/npy_common.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/memory/memory.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "alphafold3/parsers/cpp/cif_dict_lib.h" |
| #include "alphafold3/structure/cpp/mmcif_altlocs.h" |
| #include "alphafold3/structure/cpp/mmcif_layout.h" |
| #include "pybind11/cast.h" |
| #include "pybind11/gil.h" |
| #include "pybind11/numpy.h" |
| #include "pybind11/pybind11.h" |
| #include "pybind11/pytypes.h" |
| #include "pybind11/stl.h" |
| #include "pybind11_abseil/absl_casters.h" |
|
|
| namespace alphafold3 { |
| namespace { |
| namespace py = pybind11; |
|
|
| struct PyObjectDeleter { |
| inline void operator()(PyObject* obj) const { Py_CLEAR(obj); } |
| }; |
|
|
| using ScopedPyObject = std::unique_ptr<PyObject, PyObjectDeleter>; |
|
|
| using StringArrayRef = absl::Span<const std::string>; |
| using Indexer = absl::flat_hash_map<absl::string_view, std::size_t>; |
|
|
| |
| Indexer MakeIndex(StringArrayRef col) { |
| Indexer index; |
| index.reserve(col.size()); |
| for (std::size_t i = 0; i < col.size(); ++i) { |
| index[col[i]] = i; |
| } |
| return index; |
| } |
|
|
| |
| template <typename C, typename... Cs> |
| bool AreSameSize(C c, const Cs&... cs) { |
| return ((c.size() == cs.size()) && ...); |
| } |
|
|
| |
| |
| struct AtomSiteLoop { |
| explicit AtomSiteLoop(const CifDict& cif_dict) |
| : id(cif_dict["_atom_site.id"]), |
| model_id(cif_dict["_atom_site.pdbx_PDB_model_num"]), |
| chain_id(cif_dict["_atom_site.label_asym_id"]), |
| seq_id(cif_dict["_atom_site.label_seq_id"]), |
| |
| comp_id(cif_dict["_atom_site.label_comp_id"]), |
| atom_id(cif_dict["_atom_site.label_atom_id"]), |
| |
| alt_id(cif_dict["_atom_site.label_alt_id"]), |
| occupancy(cif_dict["_atom_site.occupancy"]) |
| |
| { |
| if (!AreSameSize(id, model_id, chain_id, seq_id, comp_id, atom_id, alt_id, |
| occupancy)) { |
| throw py::value_error( |
| absl::StrCat("Invalid '_atom_site.' loop. ", |
| "len(id)=", id.size(), ", ", |
| "len(pdbx_PDB_model_num)=", model_id.size(), ", ", |
| "len(label_asym_id)=", chain_id.size(), ", ", |
| "len(label_seq_id)=", seq_id.size(), ", ", |
| "len(label_comp_id)=", comp_id.size(), ", ", |
| "len(atom_id)=", atom_id.size(), ", ", |
| "len(label_alt_id)=", alt_id.size(), ", ", |
| "len(occupancy)=", occupancy.size())); |
| } |
| } |
| StringArrayRef id; |
| StringArrayRef model_id; |
| StringArrayRef chain_id; |
| StringArrayRef seq_id; |
| StringArrayRef comp_id; |
| StringArrayRef atom_id; |
| StringArrayRef alt_id; |
| StringArrayRef occupancy; |
| }; |
|
|
| |
| |
| struct EntityLoop { |
| explicit EntityLoop(const CifDict& cif_dict) |
| : id(cif_dict["_entity.id"]), type(cif_dict["_entity.type"]) { |
| if (!AreSameSize(id, type)) { |
| throw py::value_error(absl::StrCat("Invalid '_entity.' loop. ", |
| "len(id)=", id.size(), ", ", |
| "len(type)=", type.size())); |
| } |
| } |
| StringArrayRef id; |
| StringArrayRef type; |
| }; |
|
|
| |
| |
| struct EntityPolyLoop { |
| explicit EntityPolyLoop(const CifDict& cif_dict) |
| : entity_id(cif_dict["_entity_poly.entity_id"]), |
| type(cif_dict["_entity_poly.type"]) { |
| if (!AreSameSize(entity_id, type)) { |
| throw py::value_error(absl::StrCat("Invalid '_entity_poly.' loop. ", |
| "len(entity_id)=", entity_id.size(), |
| ", ", |
| "len(type)=", type.size())); |
| } |
| } |
| StringArrayRef entity_id; |
| StringArrayRef type; |
| }; |
|
|
| |
| |
| absl::flat_hash_set<absl::string_view> SelectChains(const CifDict& mmcif, |
| bool include_nucleotides, |
| bool include_ligands, |
| bool include_water, |
| bool include_other) { |
| EntityLoop entity_loop(mmcif); |
| EntityPolyLoop entity_poly(mmcif); |
| absl::flat_hash_set<absl::string_view> permitted_polymers{"polypeptide(L)"}; |
| absl::flat_hash_set<absl::string_view> forbidden_polymers; |
| for (absl::string_view type : |
| {"polydeoxyribonucleotide", "polyribonucleotide", |
| "polydeoxyribonucleotide/polyribonucleotide hybrid"}) { |
| if (include_nucleotides) { |
| permitted_polymers.emplace(type); |
| } else { |
| forbidden_polymers.emplace(type); |
| } |
| } |
|
|
| absl::flat_hash_set<absl::string_view> permitted_nonpoly_entity_types; |
| absl::flat_hash_set<absl::string_view> forbidden_nonpoly_entity_types; |
| for (absl::string_view type : {"non-polymer", "branched"}) { |
| if (include_ligands) { |
| permitted_nonpoly_entity_types.emplace(type); |
| } else { |
| forbidden_nonpoly_entity_types.emplace(type); |
| } |
| } |
| absl::string_view water_type = "water"; |
| if (include_water) { |
| permitted_nonpoly_entity_types.emplace(water_type); |
| } else { |
| forbidden_nonpoly_entity_types.emplace(water_type); |
| } |
|
|
| StringArrayRef chain_ids = mmcif["_struct_asym.id"]; |
| StringArrayRef entity_ids = mmcif["_struct_asym.entity_id"]; |
| Indexer chain_index = MakeIndex(chain_ids); |
| Indexer entity_poly_index = MakeIndex(entity_poly.entity_id); |
| Indexer entity_id_to_index = MakeIndex(entity_loop.id); |
|
|
| absl::flat_hash_set<absl::string_view> keep_chain_id; |
| for (std::size_t i = 0; i < chain_ids.size(); ++i) { |
| absl::string_view chain_id = chain_ids[i]; |
| absl::string_view entity_id = entity_ids[i]; |
| if (entity_id_to_index.empty() || |
| entity_loop.type[entity_id_to_index[entity_id]] == "polymer") { |
| if (auto it = entity_poly_index.find(entity_id); |
| it != entity_poly_index.end()) { |
| absl::string_view poly_type = entity_poly.type[it->second]; |
| if (include_other) { |
| if (!forbidden_polymers.contains(poly_type)) { |
| keep_chain_id.insert(chain_id); |
| } |
| } else { |
| if (permitted_polymers.contains(poly_type)) { |
| keep_chain_id.insert(chain_id); |
| } |
| } |
| } |
| } else { |
| absl::string_view entity_type = |
| entity_loop.type[entity_id_to_index[entity_id]]; |
| if (include_other) { |
| if (!forbidden_nonpoly_entity_types.contains(entity_type)) { |
| keep_chain_id.insert(chain_id); |
| continue; |
| } |
| } else { |
| if (permitted_nonpoly_entity_types.contains(entity_type)) { |
| keep_chain_id.insert(chain_id); |
| continue; |
| } |
| } |
| } |
| } |
| return keep_chain_id; |
| } |
|
|
| class ProcessResidue { |
| public: |
| explicit ProcessResidue(const char* residue) |
| : residue_(PyUnicode_InternFromString(residue)) {} |
| bool IsResidue(PyObject* residue) { |
| return ArePyObjectsEqual(residue_.get(), residue); |
| } |
|
|
| static bool ArePyObjectsEqual(PyObject* lhs, PyObject* rhs) { |
| switch (PyObject_RichCompareBool(lhs, rhs, Py_EQ)) { |
| case -1: |
| PyErr_Clear(); |
| return false; |
| case 0: |
| return false; |
| default: |
| return true; |
| } |
| } |
|
|
| private: |
| ScopedPyObject residue_; |
| }; |
|
|
| struct Position3 { |
| float x; |
| float y; |
| float z; |
| }; |
|
|
| float DistanceSquared(Position3 v1, Position3 v2) { |
| float dx = v1.x - v2.x; |
| float dy = v1.y - v2.y; |
| float dz = v1.z - v2.z; |
| return dx * dx + dy * dy + dz * dz; |
| } |
|
|
| class FixArginine : public ProcessResidue { |
| public: |
| FixArginine() |
| : ProcessResidue("ARG"), |
| cd_(PyUnicode_InternFromString("CD")), |
| nh1_(PyUnicode_InternFromString("NH1")), |
| nh2_(PyUnicode_InternFromString("NH2")), |
| hh11_(PyUnicode_InternFromString("HH11")), |
| hh21_(PyUnicode_InternFromString("HH21")), |
| hh12_(PyUnicode_InternFromString("HH12")), |
| hh22_(PyUnicode_InternFromString("HH22")) {} |
| void Fix(absl::Span<PyObject*> atom_ids, absl::Span<const float> atom_x, |
| absl::Span<const float> atom_y, absl::Span<const float> atom_z) { |
| std::ptrdiff_t cd_index = -1; |
| std::ptrdiff_t nh1_index = -1; |
| std::ptrdiff_t nh2_index = -1; |
| std::ptrdiff_t hh11_index = -1; |
| std::ptrdiff_t hh21_index = -1; |
| std::ptrdiff_t hh12_index = -1; |
| std::ptrdiff_t hh22_index = -1; |
| for (std::ptrdiff_t index = 0; index < atom_ids.size(); ++index) { |
| PyObject* atom_id = atom_ids[index]; |
| if (cd_index == -1 && ArePyObjectsEqual(atom_id, cd_.get())) { |
| cd_index = index; |
| } else if (nh1_index == -1 && ArePyObjectsEqual(atom_id, nh1_.get())) { |
| nh1_index = index; |
| } else if (nh2_index == -1 && ArePyObjectsEqual(atom_id, nh2_.get())) { |
| nh2_index = index; |
| } else if (hh11_index == -1 && ArePyObjectsEqual(atom_id, hh11_.get())) { |
| hh11_index = index; |
| } else if (hh21_index == -1 && ArePyObjectsEqual(atom_id, hh21_.get())) { |
| hh21_index = index; |
| } else if (hh12_index == -1 && ArePyObjectsEqual(atom_id, hh12_.get())) { |
| hh12_index = index; |
| } else if (hh22_index == -1 && ArePyObjectsEqual(atom_id, hh22_.get())) { |
| hh22_index = index; |
| } |
| } |
| if (cd_index < 0 || nh1_index < 0 || nh2_index < 0) { |
| return; |
| } |
| Position3 cd_pos(atom_x[cd_index], atom_y[cd_index], atom_z[cd_index]); |
| Position3 nh1_pos(atom_x[nh1_index], atom_y[nh1_index], atom_z[nh1_index]); |
| Position3 nh2_pos(atom_x[nh2_index], atom_y[nh2_index], atom_z[nh2_index]); |
| if (DistanceSquared(nh1_pos, cd_pos) <= DistanceSquared(nh2_pos, cd_pos)) { |
| return; |
| } |
| std::swap(atom_ids[nh1_index], atom_ids[nh2_index]); |
| if (hh11_index >= 0 && hh21_index >= 0) { |
| std::swap(atom_ids[hh11_index], atom_ids[hh21_index]); |
| } else if (hh11_index >= 0) { |
| Py_DECREF(atom_ids[hh11_index]); |
| Py_INCREF(hh21_.get()); |
| atom_ids[hh11_index] = hh21_.get(); |
| } else if (hh21_index >= 0) { |
| Py_DECREF(atom_ids[hh21_index]); |
| Py_INCREF(hh11_.get()); |
| atom_ids[hh21_index] = hh11_.get(); |
| } |
| if (hh12_index >= 0 && hh22_index >= 0) { |
| std::swap(atom_ids[hh12_index], atom_ids[hh22_index]); |
| } else if (hh12_index >= 0) { |
| Py_DECREF(atom_ids[hh12_index]); |
| Py_INCREF(hh22_.get()); |
| atom_ids[hh12_index] = hh22_.get(); |
| } else if (hh22_index >= 0) { |
| Py_DECREF(atom_ids[hh22_index]); |
| Py_INCREF(hh21_.get()); |
| atom_ids[hh22_index] = hh21_.get(); |
| } |
| } |
|
|
| private: |
| ScopedPyObject cd_; |
| ScopedPyObject nh1_; |
| ScopedPyObject nh2_; |
| ScopedPyObject hh11_; |
| ScopedPyObject hh21_; |
| ScopedPyObject hh12_; |
| ScopedPyObject hh22_; |
| }; |
|
|
| |
| inline MmcifLayout ReadMmcifLayout(const CifDict& mmcif, |
| absl::string_view model_id = "") { |
| py::gil_scoped_release release; |
| auto mmcif_layout = MmcifLayout::Create(mmcif, model_id); |
| if (mmcif_layout.ok()) { |
| return *mmcif_layout; |
| } |
|
|
| throw py::value_error(std::string(mmcif_layout.status().message())); |
| } |
|
|
| std::pair<py::object, MmcifLayout> MmcifFilter( |
| const CifDict& mmcif, |
| bool include_nucleotides, |
| bool include_ligands, |
| bool include_water, |
| bool include_other, |
| absl::string_view model_id) { |
| if (_import_array() < 0) { |
| throw py::import_error("Failed to import NumPy."); |
| } |
| auto layout = ReadMmcifLayout(mmcif, model_id); |
| std::unique_ptr<std::vector<std::uint64_t>> keep_indices; |
| size_t new_num_atoms; |
|
|
| { |
| py::gil_scoped_release release; |
|
|
| AtomSiteLoop atom_site(mmcif); |
|
|
| auto keep_chain_ids = |
| SelectChains(mmcif, include_nucleotides, include_ligands, include_water, |
| include_other); |
|
|
| std::vector<std::size_t> chain_indices; |
| chain_indices.reserve(keep_chain_ids.size()); |
| for (std::size_t i = 0; i < layout.num_chains(); ++i) { |
| if (keep_chain_ids.contains( |
| atom_site.chain_id[layout.atom_site_from_chain_index(i)])) { |
| chain_indices.push_back(i); |
| } |
| } |
|
|
| keep_indices = |
| absl::WrapUnique(new std::vector<std::uint64_t>(ResolveMmcifAltLocs( |
| layout, atom_site.comp_id, atom_site.atom_id, atom_site.alt_id, |
| atom_site.occupancy, chain_indices))); |
| new_num_atoms = keep_indices->size(); |
|
|
| if (layout.num_models() > 1) { |
| keep_indices->reserve(layout.num_models() * new_num_atoms); |
| std::uint64_t* start = &(*keep_indices->begin()); |
| std::size_t num_atom = keep_indices->size(); |
| |
| for (std::size_t i = 1; i < layout.num_models(); ++i) { |
| std::size_t offset = i * layout.num_atoms(); |
| std::transform(start, start + num_atom, |
| std::back_inserter(*keep_indices), |
| [offset](std::size_t v) { return v + offset; }); |
| } |
| } |
| } |
|
|
| layout.Filter(*keep_indices); |
|
|
| npy_intp shape[] = {static_cast<npy_intp>(layout.num_models()), |
| static_cast<npy_intp>(new_num_atoms)}; |
| PyObject* arr = |
| PyArray_SimpleNewFromData(2, shape, NPY_INT64, keep_indices->data()); |
| |
| |
| PyObject* capsule = PyCapsule_New( |
| keep_indices.release(), nullptr, +[](PyObject* capsule_cleanup) { |
| void* memory = PyCapsule_GetPointer(capsule_cleanup, nullptr); |
| delete static_cast<std::vector<std::size_t>*>(memory); |
| }); |
| PyArray_SetBaseObject(reinterpret_cast<PyArrayObject*>(arr), capsule); |
|
|
| return std::make_pair(py::reinterpret_steal<py::object>(arr), |
| std::move(layout)); |
| } |
|
|
| void MmcifFixResidues( |
| const MmcifLayout& layout, |
| absl::Span<PyObject*> comp_id, |
| absl::Span<PyObject*> atom_id, |
| absl::Span<const float> atom_x, |
| absl::Span<const float> atom_y, |
| absl::Span<const float> atom_z, |
| bool fix_arginine |
| ) { |
| std::optional<FixArginine> arginine; |
| std::size_t num_atoms = layout.num_atoms(); |
| if (comp_id.size() != num_atoms || atom_id.size() != num_atoms || |
| atom_x.size() != num_atoms || atom_y.size() != num_atoms || |
| atom_z.size() != num_atoms) { |
| throw py::value_error( |
| absl::StrCat("Sizes must match. ", |
| "num_atoms=", num_atoms, ", ", |
| "len(comp_id)=", comp_id.size(), ", ", |
| "len(atom_id)=", atom_id.size(), ", ", |
| "len(atom_x)=", atom_x.size(), ", ", |
| "len(atom_y)=", atom_y.size(), ", ", |
| "len(atom_z)=", atom_z.size())); |
| } |
|
|
| if (fix_arginine) { |
| arginine.emplace(); |
| } |
| if (!arginine.has_value()) { |
| return; |
| } |
|
|
| for (std::size_t res_index = 0; res_index < layout.num_residues(); |
| ++res_index) { |
| auto [atom_start, atom_end] = layout.atom_range(res_index); |
| std::size_t atom_count = atom_end - atom_start; |
| PyObject* resname = comp_id[atom_start]; |
| if (arginine.has_value() && arginine->IsResidue(resname)) { |
| arginine->Fix(atom_id.subspan(atom_start, atom_count), |
| atom_x.subspan(atom_start, atom_count), |
| atom_y.subspan(atom_start, atom_count), |
| atom_z.subspan(atom_start, atom_count)); |
| } |
| } |
| } |
|
|
| std::vector<bool> SelectedPolymerResidueMask( |
| const MmcifLayout& layout, |
| const std::vector<absl::string_view>& atom_site_label_asym_ids, |
| const std::vector<absl::string_view>& atom_site_label_seq_ids, |
| const std::vector<absl::string_view>& atom_site_label_comp_ids, |
| const std::vector<absl::string_view>& poly_seq_asym_ids, |
| const std::vector<absl::string_view>& poly_seq_seq_ids, |
| const std::vector<absl::string_view>& poly_seq_mon_ids |
| ) { |
| absl::flat_hash_map<std::pair<absl::string_view, absl::string_view>, |
| absl::string_view> |
| selected; |
| selected.reserve(layout.num_residues()); |
| |
| const std::vector<std::size_t>& residue_starts = layout.residue_starts(); |
| for (int i = 0; i < layout.residues().size(); ++i) { |
| std::size_t res_start = residue_starts[i]; |
| std::size_t res_end = layout.residues()[i]; |
| if (res_start == res_end) { |
| continue; |
| } |
|
|
| absl::string_view label_seq_id = atom_site_label_seq_ids[i]; |
| if (label_seq_id == ".") { |
| continue; |
| } |
|
|
| absl::string_view label_asym_id = atom_site_label_asym_ids[i]; |
| absl::string_view label_comp_id = atom_site_label_comp_ids[i]; |
| selected[std::make_pair(label_asym_id, label_seq_id)] = label_comp_id; |
| } |
|
|
| std::vector<bool> mask; |
| mask.reserve(poly_seq_mon_ids.size()); |
| for (int i = 0; i < poly_seq_mon_ids.size(); ++i) { |
| absl::string_view poly_seq_asym_id = poly_seq_asym_ids[i]; |
| absl::string_view poly_seq_seq_id = poly_seq_seq_ids[i]; |
| absl::string_view poly_seq_mon_id = poly_seq_mon_ids[i]; |
|
|
| auto it = selected.find(std::make_pair(poly_seq_asym_id, poly_seq_seq_id)); |
| if (it != selected.end()) { |
| mask.push_back(it->second == poly_seq_mon_id); |
| } else { |
| mask.push_back(true); |
| } |
| } |
| return mask; |
| } |
|
|
| std::pair<std::vector<bool>, std::vector<bool>> SelectedLigandResidueMask( |
| const MmcifLayout& layout, |
| const std::vector<absl::string_view>& atom_site_label_asym_ids, |
| const std::vector<absl::string_view>& atom_site_label_seq_ids, |
| const std::vector<absl::string_view>& atom_site_auth_seq_ids, |
| const std::vector<absl::string_view>& atom_site_label_comp_ids, |
| const std::vector<absl::string_view>& atom_site_pdbx_pdb_ins_codes, |
| const std::vector<absl::string_view>& nonpoly_asym_ids, |
| const std::vector<absl::string_view>& nonpoly_auth_seq_ids, |
| const std::vector<absl::string_view>& nonpoly_pdb_ins_codes, |
| const std::vector<absl::string_view>& nonpoly_mon_ids, |
| const std::vector<absl::string_view>& branch_asym_ids, |
| const std::vector<absl::string_view>& branch_auth_seq_ids, |
| const std::vector<absl::string_view>& branch_pdb_ins_codes, |
| const std::vector<absl::string_view>& branch_mon_ids) { |
| absl::flat_hash_map< |
| std::tuple<absl::string_view, absl::string_view, absl::string_view>, |
| absl::string_view> |
| selected; |
| selected.reserve(layout.num_residues()); |
| |
| const std::vector<std::size_t>& residue_starts = layout.residue_starts(); |
| for (int i = 0; i < layout.residues().size(); ++i) { |
| std::size_t res_start = residue_starts[i]; |
| std::size_t res_end = layout.residues()[i]; |
| if (res_start == res_end) { |
| continue; |
| } |
|
|
| absl::string_view label_seq_id = atom_site_label_seq_ids[i]; |
| if (label_seq_id != ".") { |
| continue; |
| } |
|
|
| absl::string_view label_asym_id = atom_site_label_asym_ids[i]; |
| absl::string_view auth_seq_id = atom_site_auth_seq_ids[i]; |
| absl::string_view ins_code = atom_site_pdbx_pdb_ins_codes[i]; |
| ins_code = ins_code == "?" ? "." : ins_code; |
| absl::string_view label_comp_id = atom_site_label_comp_ids[i]; |
| selected[std::make_tuple(label_asym_id, auth_seq_id, ins_code)] = |
| label_comp_id; |
| } |
|
|
| std::vector<bool> nonpoly_mask; |
| nonpoly_mask.reserve(nonpoly_asym_ids.size()); |
| for (int i = 0; i < nonpoly_asym_ids.size(); ++i) { |
| absl::string_view nonpoly_asym_id = nonpoly_asym_ids[i]; |
| absl::string_view nonpoly_auth_seq_id = nonpoly_auth_seq_ids[i]; |
| absl::string_view nonpoly_ins_code = nonpoly_pdb_ins_codes[i]; |
| |
| nonpoly_ins_code = nonpoly_ins_code == "?" ? "." : nonpoly_ins_code; |
| absl::string_view nonpoly_mon_id = nonpoly_mon_ids[i]; |
|
|
| auto it = selected.find(std::make_tuple( |
| nonpoly_asym_id, nonpoly_auth_seq_id, nonpoly_ins_code)); |
| if (it != selected.end()) { |
| nonpoly_mask.push_back(it->second == nonpoly_mon_id); |
| } else { |
| nonpoly_mask.push_back(true); |
| } |
| } |
|
|
| std::vector<bool> branch_mask; |
| branch_mask.reserve(branch_asym_ids.size()); |
| for (int i = 0; i < branch_asym_ids.size(); ++i) { |
| absl::string_view branch_asym_id = branch_asym_ids[i]; |
| absl::string_view branch_auth_seq_id = branch_auth_seq_ids[i]; |
|
|
| |
| |
| absl::string_view branch_ins_code; |
| if (i < branch_pdb_ins_codes.size()) { |
| branch_ins_code = branch_pdb_ins_codes[i]; |
| |
| branch_ins_code = branch_ins_code == "?" ? "." : branch_ins_code; |
| } else { |
| branch_ins_code = "."; |
| } |
|
|
| absl::string_view branch_mon_id = branch_mon_ids[i]; |
|
|
| auto it = selected.find( |
| std::make_tuple(branch_asym_id, branch_auth_seq_id, branch_ins_code)); |
| if (it != selected.end()) { |
| branch_mask.push_back(it->second == branch_mon_id); |
| } else { |
| branch_mask.push_back(true); |
| } |
| } |
|
|
| return std::make_pair(nonpoly_mask, branch_mask); |
| } |
|
|
| constexpr char kReadMmcifLayout[] = R"( |
| Returns the layout of the cif_dict. |
| |
| Args: |
| mmcif: mmCIF to calculate the layout for. |
| model_id: If non-empty the layout of the given model is returned |
| otherwise the layout of all models are returned. |
| Raises: |
| ValueError: if the mmCIF is malformed or the number of atoms in each |
| model are inconsistent. |
| )"; |
|
|
| constexpr char kMmcifFilter[] = R"( |
| Returns NumpyArray of selected rows in `_atom_site` and new layout. |
| |
| Args: |
| mmcif: mmCIF to filter. |
| include_nucleotides: Whether to include polymer entities of type: |
| "polypeptide(L)\", "polydeoxyribonucleotide", "polyribonucleotide". |
| Otherwise only "polypeptide(L)\". ("polypeptide(D)\" is never included.) |
| include_ligands: Whether to include non-polymer entities of type: |
| "non-polymer", "branched". |
| include_water: Whether to include entities of type water. |
| include_other: Whether to include other (non-standard) entity types |
| that are not covered by any of the above parameters. |
| model_id: If non-empty the model with given name is selected otherwise |
| all models are selected. |
| |
| Returns: |
| A tuple containing a numpy array with a shape (num_models, num_atoms) |
| with the atom_site indices selected and the new layout. |
| |
| Raises: |
| ValueError error if mmCIF dict does not have all required fields. |
| )"; |
|
|
| constexpr char kMmcifFixResidues[] = R"( |
| Fixes residue columns in-place. |
| |
| Args: |
| layout: layout from filter command. |
| comp_id: '_atom_site.label_comp_id' of first model. |
| group: '_atom_site.group_PDB' of first model. |
| atom_id: '_atom_site.label_atom_id' of first model. |
| type_symbol: '_atom_site.type_symbol' of first model. |
| atom_x: '_atom_site.Cartn_x' of first model. |
| atom_y: '_atom_site.Cartn_y' of first model. |
| atom_z: '_atom_site.Cartn_z' of first model. |
| fix_mse: Whether to convert MSE residues into MET residues. |
| fix_arg: Whether to ensure the atoms in ARG are in the correct order. |
| fix_unknown_dna: Whether to convert DNA residues from N to DN. |
| dna_mask: Which atoms are from DNA chains. |
| |
| Raises: |
| ValueError: If shapes are invalid. |
| )"; |
|
|
| constexpr char kSelectedPolymerResidueMask[] = R"( |
| Returns a _pdbx_poly_seq_scheme mask for selected hetero residues. |
| |
| Should be called after filtering the layout using mmcif_utils.filter. |
| |
| Args: |
| layout: Layout defining the _atom_site residue selection. |
| atom_site_label_asym_ids: Internal (label) chain ID, per selected residue. |
| atom_site_label_seq_ids: Internal (label) residue ID, per selected residue. |
| atom_site_label_comp_ids: Residue name, per selected residue. |
| poly_seq_asym_ids: Internal (label) chain ID, per residue. |
| poly_seq_seq_ids: Internal (label) residue ID, per residue. |
| poly_seq_mon_ids: Residue name, per residue. |
| |
| Returns: |
| A mask for the _pdbx_poly_seq_scheme table. If residues are selected |
| using this mask, they will have consistent heterogeneous residue |
| selection with the _atom_site table. |
| )"; |
|
|
| constexpr char kSelectedLigandResidueMask[] = R"( |
| Returns masks for selected ligand hetero residues. |
| |
| Should be called after filtering the layout using mmcif_utils.filter. |
| |
| Args: |
| layout: Layout defining the _atom_site residue selection. |
| atom_site_label_asym_ids: Internal (label) chain ID, per selected residue. |
| atom_site_label_seq_ids: Internal (author) residue ID, per selected residue. |
| atom_site_auth_seq_ids: External (author) residue ID, per selected residue. |
| atom_site_label_comp_ids: Residue name, per selected residue. |
| atom_site_pdbx_pdb_ins_codes: Insertion code, per selected residue. |
| nonpoly_asym_ids: Internal (label) chain ID, per residue from |
| _pdbx_nonpoly_scheme. |
| nonpoly_auth_seq_ids: External (author) residue ID, per residue from |
| _pdbx_nonpoly_scheme. |
| nonpoly_pdb_ins_codes: Residue name, per residue from |
| _pdbx_nonpoly_scheme. |
| nonpoly_mon_ids: Insertion code, per residue from _pdbx_nonpoly_scheme. |
| branch_asym_ids: Internal (label) chain ID, per residue from |
| _pdbx_branch_scheme. |
| branch_auth_seq_ids: External (author) residue ID, per residue from |
| _pdbx_branch_scheme. |
| branch_pdb_ins_codes: Residue name, per residue from _pdbx_branch_scheme. |
| branch_mon_ids: Insertion code, per residue from _pdbx_branch_scheme. |
| |
| Returns: |
| A tuple with masks for _pdbx_nonpoly_scheme and _pdbx_branch_scheme. If |
| residues are selected using these masks, they will have consistent |
| heterogeneous residue selection with the _atom_site table. |
| )"; |
|
|
| } |
|
|
| void RegisterModuleMmcifUtils(pybind11::module m) { |
| m.def("read_layout", ReadMmcifLayout, |
| py::arg("mmcif"), |
| py::arg("model_id") = "", |
| py::doc(kReadMmcifLayout + 1) |
| ); |
|
|
| m.def("filter", MmcifFilter, |
| py::arg("mmcif"), |
| py::arg("include_nucleotides"), |
| py::arg("include_ligands") = false, |
| py::arg("include_water") = false, |
| py::arg("include_other") = false, |
| py::arg("model_id") = "", |
| py::doc(kMmcifFilter + 1) |
| ); |
|
|
| m.def("fix_residues", MmcifFixResidues, |
| py::arg("layout"), |
| py::arg("comp_id"), |
| py::arg("atom_id"), |
| py::arg("atom_x"), |
| py::arg("atom_y"), |
| py::arg("atom_z"), |
| py::arg("fix_arg") = false, |
| py::doc(kMmcifFixResidues + 1) |
| ); |
|
|
| m.def("selected_polymer_residue_mask", SelectedPolymerResidueMask, |
| py::arg("layout"), |
| py::arg("atom_site_label_asym_ids"), |
| py::arg("atom_site_label_seq_ids"), |
| py::arg("atom_site_label_comp_ids"), |
| py::arg("poly_seq_asym_ids"), |
| py::arg("poly_seq_seq_ids"), |
| py::arg("poly_seq_mon_ids"), |
| py::call_guard<py::gil_scoped_release>(), |
| py::doc(kSelectedPolymerResidueMask + 1) |
| ); |
|
|
| m.def("selected_ligand_residue_mask", SelectedLigandResidueMask, |
| py::arg("layout"), |
| py::arg("atom_site_label_asym_ids"), |
| py::arg("atom_site_label_seq_ids"), |
| py::arg("atom_site_auth_seq_ids"), |
| py::arg("atom_site_label_comp_ids"), |
| py::arg("atom_site_pdbx_pdb_ins_codes"), |
| py::arg("nonpoly_asym_ids"), |
| py::arg("nonpoly_auth_seq_ids"), |
| py::arg("nonpoly_pdb_ins_codes"), |
| py::arg("nonpoly_mon_ids"), |
| py::arg("branch_asym_ids"), |
| py::arg("branch_auth_seq_ids"), |
| py::arg("branch_pdb_ins_codes"), |
| py::arg("branch_mon_ids"), |
| py::call_guard<py::gil_scoped_release>(), |
| py::doc(kSelectedLigandResidueMask + 1) |
| ); |
| } |
|
|
| } |
|
|