File size: 15,104 Bytes
3e62986 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 | // Copyright 2024 DeepMind Technologies Limited
//
// AlphaFold 3 source code is licensed under CC BY-NC-SA 4.0. To view a copy of
// this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// To request access to the AlphaFold 3 model parameters, follow the process set
// out at https://github.com/google-deepmind/alphafold3. You may only use these
// if received directly from Google. Use is subject to terms of use available at
// https://github.com/google-deepmind/alphafold3/blob/main/WEIGHTS_TERMS_OF_USE.md
#include <cstddef>
#include <iterator>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/status/statusor.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_struct_conn.h"
namespace alphafold3 {
namespace {
struct AtomId {
absl::string_view chain_id;
absl::string_view res_id_1;
absl::string_view res_id_2;
absl::string_view atom_name;
absl::string_view alt_id;
friend bool operator==(const AtomId&, const AtomId&) = default;
template <typename H>
friend H AbslHashValue(H h, const AtomId& m) {
return H::combine(std::move(h), m.chain_id, m.res_id_1, m.res_id_2,
m.atom_name, m.alt_id);
}
};
using StringArrayRef = absl::Span<const std::string>;
using BondIndexByAtom = absl::flat_hash_map<AtomId, std::vector<std::size_t>>;
using BondAtomIndices = std::vector<std::size_t>;
// Returns whether each container is the same size.
template <typename C, typename... Cs>
bool AreSameSize(const C& c, const Cs&... cs) {
return ((c.size() == cs.size()) && ...);
}
struct ColumnSpec {
absl::string_view chain_id_col;
absl::string_view res_id_1_col;
absl::string_view res_id_2_col;
absl::string_view atom_name_col;
std::optional<absl::string_view> alt_id_col; // Not used by OpenMM.
};
class AtomColumns {
public:
static absl::StatusOr<AtomColumns> Create(const CifDict& mmcif,
const ColumnSpec& column_spec) {
StringArrayRef chain_id = mmcif[column_spec.chain_id_col];
StringArrayRef res_id_1 = mmcif[column_spec.res_id_1_col];
StringArrayRef res_id_2 = mmcif[column_spec.res_id_2_col];
StringArrayRef atom_name = mmcif[column_spec.atom_name_col];
if (!AreSameSize(chain_id, res_id_1, res_id_2, atom_name)) {
return absl::InvalidArgumentError(absl::StrCat(
"Atom columns are not the same size. ", //
"len(", column_spec.chain_id_col, ")=", chain_id.size(), //
", len(", column_spec.res_id_1_col, ")=", res_id_1.size(), //
", len(", column_spec.res_id_2_col, ")=", res_id_2.size(), //
", len(", column_spec.atom_name_col, ")=", atom_name.size(), //
"."));
}
if (column_spec.alt_id_col.has_value()) {
StringArrayRef alt_id = mmcif[*column_spec.alt_id_col];
if (!AreSameSize(alt_id, chain_id)) {
return absl::InvalidArgumentError(absl::StrCat(
"Atom columns are not the same size. ", //
"len(", column_spec.chain_id_col, ")=", chain_id.size(), //
", len(", *column_spec.alt_id_col, ")=", alt_id.size(), //
"."));
}
return AtomColumns(chain_id, res_id_1, res_id_2, atom_name, alt_id,
column_spec);
} else {
return AtomColumns(chain_id, res_id_1, res_id_2, atom_name, std::nullopt,
column_spec);
}
}
inline std::size_t size() const { return size_; }
absl::string_view GetNormalizedAltId(const std::size_t index) const {
constexpr absl::string_view kFullStop = ".";
if (alt_id_.has_value()) {
absl::string_view alt_id = (*alt_id_)[index];
return alt_id == "?" ? kFullStop : alt_id;
} else {
return kFullStop;
}
}
AtomId GetAtom(const std::size_t index) const {
return {.chain_id = chain_id_[index],
.res_id_1 = res_id_1_[index],
.res_id_2 = res_id_2_[index],
.atom_name = atom_name_[index],
.alt_id = GetNormalizedAltId(index)};
}
std::string GetAtomString(const std::size_t index) const {
std::string alt_id_col;
if (column_spec_.alt_id_col.has_value()) {
alt_id_col = *column_spec_.alt_id_col;
} else {
alt_id_col = "default label_alt_id";
}
return absl::StrCat(
column_spec_.chain_id_col, "=", chain_id_[index], ", ", //
column_spec_.res_id_1_col, "=", res_id_1_[index], ", ", //
column_spec_.res_id_2_col, "=", res_id_2_[index], ", ", //
column_spec_.atom_name_col, "=", atom_name_[index], ", ", //
alt_id_col, "=", GetNormalizedAltId(index)); //
}
private:
AtomColumns(StringArrayRef chain_id, StringArrayRef res_id_1,
StringArrayRef res_id_2, StringArrayRef atom_name,
std::optional<StringArrayRef> alt_id,
const ColumnSpec& column_spec)
: chain_id_(chain_id),
res_id_1_(res_id_1),
res_id_2_(res_id_2),
atom_name_(atom_name),
alt_id_(alt_id),
column_spec_(column_spec),
size_(chain_id.size()) {}
StringArrayRef chain_id_;
StringArrayRef res_id_1_;
StringArrayRef res_id_2_;
StringArrayRef atom_name_;
std::optional<StringArrayRef> alt_id_;
ColumnSpec column_spec_;
std::size_t size_;
};
// Adds the atom index to any rows in the bond table involving that atom.
absl::Status FillInBondsForAtom(const BondIndexByAtom& bond_index_by_atom,
const AtomId& atom,
const std::size_t atom_index,
BondAtomIndices& bond_atom_indices) {
if (auto bond_index_it = bond_index_by_atom.find(atom);
bond_index_it != bond_index_by_atom.end()) {
for (std::size_t bond_index : bond_index_it->second) {
if (bond_index < 0 || bond_index >= bond_atom_indices.size()) {
return absl::OutOfRangeError(
absl::StrCat("Bond index out of range: ", bond_index));
}
bond_atom_indices[bond_index] = atom_index;
}
}
return absl::OkStatus();
}
// Checks that the CifDict has all of the columns in the column spec.
bool HasAllColumns(const CifDict& mmcif, const ColumnSpec& columns) {
return mmcif.Contains(columns.chain_id_col) &&
mmcif.Contains(columns.res_id_1_col) &&
mmcif.Contains(columns.res_id_2_col) &&
mmcif.Contains(columns.atom_name_col) &&
(!columns.alt_id_col.has_value() ||
mmcif.Contains(*columns.alt_id_col));
}
// Fully specified ptnr1 atom.
constexpr ColumnSpec kStructConnPtnr1ColumnsFull{
.chain_id_col = "_struct_conn.ptnr1_label_asym_id",
.res_id_1_col = "_struct_conn.ptnr1_auth_seq_id",
.res_id_2_col = "_struct_conn.pdbx_ptnr1_PDB_ins_code",
.atom_name_col = "_struct_conn.ptnr1_label_atom_id",
.alt_id_col = "_struct_conn.pdbx_ptnr1_label_alt_id",
};
// Fully specified ptnr2 atom.
constexpr ColumnSpec kStructConnPtnr2ColumnsFull{
.chain_id_col = "_struct_conn.ptnr2_label_asym_id",
.res_id_1_col = "_struct_conn.ptnr2_auth_seq_id",
.res_id_2_col = "_struct_conn.pdbx_ptnr2_PDB_ins_code",
.atom_name_col = "_struct_conn.ptnr2_label_atom_id",
.alt_id_col = "_struct_conn.pdbx_ptnr2_label_alt_id",
};
// Columns used by OpenMM for ptnr1 atoms.
constexpr ColumnSpec kStructConnPtnr1OpenMM{
.chain_id_col = "_struct_conn.ptnr1_label_asym_id",
.res_id_1_col = "_struct_conn.ptnr1_label_seq_id",
.res_id_2_col = "_struct_conn.ptnr1_label_comp_id",
.atom_name_col = "_struct_conn.ptnr1_label_atom_id",
.alt_id_col = std::nullopt,
};
// Columns used by OpenMM for ptnr2 atoms.
constexpr ColumnSpec kStructConnPtnr2OpenMM{
.chain_id_col = "_struct_conn.ptnr2_label_asym_id",
.res_id_1_col = "_struct_conn.ptnr2_label_seq_id",
.res_id_2_col = "_struct_conn.ptnr2_label_comp_id",
.atom_name_col = "_struct_conn.ptnr2_label_atom_id",
.alt_id_col = std::nullopt,
};
// Fully specified atom sites.
constexpr ColumnSpec kAtomSiteColumnsFull{
.chain_id_col = "_atom_site.label_asym_id",
.res_id_1_col = "_atom_site.auth_seq_id",
.res_id_2_col = "_atom_site.pdbx_PDB_ins_code",
.atom_name_col = "_atom_site.label_atom_id",
.alt_id_col = "_atom_site.label_alt_id",
};
// Atom site columns used to match OpenMM _struct_conn tables.
constexpr ColumnSpec kAtomSiteColumnsOpenMM{
.chain_id_col = "_atom_site.label_asym_id",
.res_id_1_col = "_atom_site.label_seq_id",
.res_id_2_col = "_atom_site.label_comp_id",
.atom_name_col = "_atom_site.label_atom_id",
.alt_id_col = "_atom_site.label_alt_id",
};
} // namespace
absl::StatusOr<std::pair<BondAtomIndices, BondAtomIndices>> GetBondAtomIndices(
const CifDict& mmcif, absl::string_view model_id) {
ColumnSpec ptnr1_columns, ptnr2_columns, atom_site_columns;
if (HasAllColumns(mmcif, kStructConnPtnr1ColumnsFull) &&
HasAllColumns(mmcif, kStructConnPtnr2ColumnsFull)) {
ptnr1_columns = kStructConnPtnr1ColumnsFull;
ptnr2_columns = kStructConnPtnr2ColumnsFull;
atom_site_columns = kAtomSiteColumnsFull;
} else {
ptnr1_columns = kStructConnPtnr1OpenMM;
ptnr2_columns = kStructConnPtnr2OpenMM;
atom_site_columns = kAtomSiteColumnsOpenMM;
}
absl::StatusOr<AtomColumns> ptnr1_atoms =
AtomColumns::Create(mmcif, ptnr1_columns);
if (!ptnr1_atoms.ok()) {
return ptnr1_atoms.status();
}
absl::StatusOr<AtomColumns> ptnr2_atoms =
AtomColumns::Create(mmcif, ptnr2_columns);
if (!ptnr2_atoms.ok()) {
return ptnr2_atoms.status();
}
StringArrayRef struct_conn_id = mmcif["_struct_conn.id"];
if (!AreSameSize(struct_conn_id, *ptnr1_atoms, *ptnr2_atoms)) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid '_struct_conn.' loop. ", //
"len(id) = ", struct_conn_id.size(), ", ", //
"len(ptnr1_atoms) = ", ptnr1_atoms->size(), ", ", //
"len(ptnr2_atoms) = ", ptnr2_atoms->size(), "." //
));
}
absl::StatusOr<AtomColumns> atoms =
AtomColumns::Create(mmcif, atom_site_columns);
if (!atoms.ok()) {
return atoms.status();
}
StringArrayRef atom_site_id = mmcif["_atom_site.id"];
StringArrayRef atom_site_model_id = mmcif["_atom_site.pdbx_PDB_model_num"];
if (!AreSameSize(atom_site_id, atom_site_model_id, *atoms)) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid '_atom_site.' loop. ", //
"len(id)= ", atom_site_id.size(), ", ", //
"len(pdbx_PDB_model_num)= ", atom_site_model_id.size(), ", ", //
"len(atoms)= ", atoms->size(), ".")); //
}
// Build maps from atom ID tuples to the rows in _struct_conn where that
// atom appears (NB could be multiple).
const std::size_t struct_conn_size = struct_conn_id.size();
BondIndexByAtom ptnr1_rows_by_atom(struct_conn_size);
BondIndexByAtom ptnr2_rows_by_atom(struct_conn_size);
for (std::size_t i = 0; i < struct_conn_size; ++i) {
ptnr1_rows_by_atom[ptnr1_atoms->GetAtom(i)].push_back(i);
ptnr2_rows_by_atom[ptnr2_atoms->GetAtom(i)].push_back(i);
}
// Allocate two output arrays with one element per row in struct_conn, where
// each element will be the index of that atom in the atom_site table.
// Fill the arrays with atom_site_size, which is an invalid value, so that
// we can check at the end that each atom has been found.
const std::size_t atom_site_size = atom_site_id.size();
BondAtomIndices ptnr1_atom_indices(struct_conn_size, atom_site_size);
BondAtomIndices ptnr2_atom_indices(struct_conn_size, atom_site_size);
bool model_id_ecountered = false;
absl::flat_hash_set<absl::string_view> seen_alt_ids;
for (std::size_t atom_i = 0; atom_i < atom_site_size; ++atom_i) {
if (atom_site_model_id[atom_i] != model_id) {
if (!model_id_ecountered) {
continue;
} else {
// Models are contiguous so once we see a different model ID after
// encountering our model ID then we can exit early.
break;
}
} else {
model_id_ecountered = true;
}
AtomId atom = atoms->GetAtom(atom_i);
seen_alt_ids.insert(atom.alt_id);
if (auto fill_in_bonds_status1 = FillInBondsForAtom(
ptnr1_rows_by_atom, atom, atom_i, ptnr1_atom_indices);
!fill_in_bonds_status1.ok()) {
return fill_in_bonds_status1;
}
if (auto fill_in_bonds_status2 = FillInBondsForAtom(
ptnr2_rows_by_atom, atom, atom_i, ptnr2_atom_indices);
!fill_in_bonds_status2.ok()) {
return fill_in_bonds_status2;
}
}
// The seen_alt_ids check is a workaround for a known PDB issue: some mmCIFs
// (2evw, 2g0v, 2g0x, 2g0z, 2g10, 2g11, 2g12, 2g14, 2grz, 2ntw as of 2024)
// have multiple models and they set different whole-chain altloc in each
// model. The bond table however doesn't distinguish between models, so there
// are bonds that are valid only for some models. E.g. 2grz has model 1 with
// chain A with altloc A, and model 2 with chain A with altloc B. The bonds
// table lists a bond for each of these.
// Check that a ptnr1 atom was found for every bond.
if (auto row_it = absl::c_find(ptnr1_atom_indices, atom_site_size);
row_it != ptnr1_atom_indices.end()) {
if (seen_alt_ids.size() > 1 || seen_alt_ids.contains(".") ||
seen_alt_ids.contains("?")) {
std::size_t i = std::distance(ptnr1_atom_indices.begin(), row_it);
return absl::InvalidArgumentError(
absl::StrCat("Error parsing \"", mmcif.GetDataName(), "\". ",
"Cannot find atom for bond ID ", struct_conn_id[i], ": ",
ptnr1_atoms->GetAtomString(i)));
}
}
// Check that a ptnr2 atom was found for every bond.
if (auto row_it = absl::c_find(ptnr2_atom_indices, atom_site_size);
row_it != ptnr2_atom_indices.end()) {
if (seen_alt_ids.size() > 1 || seen_alt_ids.contains(".") ||
seen_alt_ids.contains("?")) {
std::size_t i = std::distance(ptnr2_atom_indices.begin(), row_it);
return absl::InvalidArgumentError(
absl::StrCat("Error parsing \"", mmcif.GetDataName(), "\". ",
"Cannot find atom for bond ID ", struct_conn_id[i], ": ",
ptnr2_atoms->GetAtomString(i)));
}
}
if (!model_id_ecountered) {
return absl::InvalidArgumentError(absl::StrCat(
"Error parsing \"", mmcif.GetDataName(), "\". model_id \"", model_id,
"\" not found in _atom_site.pdbx_PDB_model_num."));
}
return std::make_pair(std::move(ptnr1_atom_indices),
std::move(ptnr2_atom_indices));
}
} // namespace alphafold3
|