// 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 "alphafold3/structure/cpp/mmcif_altlocs.h" #include #include #include #include #include #include #include #include #include "absl/algorithm/container.h" #include "absl/log/log.h" #include "absl/strings/numbers.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" #include "alphafold3/structure/cpp/mmcif_layout.h" namespace alphafold3 { namespace { float OccupancyToFloat(absl::string_view occupancy) { float result = 0.0f; LOG_IF(ERROR, !absl::SimpleAtof(occupancy, &result)) << "Invalid Occupancy: " << occupancy; return result; } // Deuterium is the same atom as Hydrogen so keep equivalent for grouping. bool AtomEquiv(absl::string_view lhs, absl::string_view rhs) { if (lhs == rhs) return true; if (lhs.empty() != rhs.empty()) return false; // Both lhs and rhs are guaranteed to be non-empty after this. char first_lhs = lhs.front(); char second_rhs = rhs.front(); if ((first_lhs == 'H' && second_rhs == 'D') || (first_lhs == 'D' && second_rhs == 'H')) { lhs.remove_prefix(1); rhs.remove_prefix(1); return lhs == rhs; } return false; } // Calls group_callback with that start index and count for each group of // equivalent values in `values`, starting at `start` and ending at `count`. // Example: // GroupBy({"B", "B", "B", "C", "C"}, 0, 5, [](size_t start, size_t count) { // absl::Printf("start=%d, count=%d\n", start, count); // }); // Would print: // start=0, count=3 // start=3, count=2 template > void GroupBy(absl::Span values, std::size_t start, std::size_t count, GroupCallback&& group_callback, IsEqual&& is_equal = std::equal_to{}) { std::size_t span_start = start; if (count > 0) { for (std::size_t i = start + 1; i < start + count; ++i) { if (!is_equal(values[i], values[span_start])) { group_callback(span_start, i - span_start); span_start = i; } } group_callback(span_start, start + count - span_start); } } void ProcessAltLocGroupsWhole(std::size_t alt_loc_start, std::size_t alt_loc_count, absl::Span comp_ids, absl::Span atom_ids, absl::Span alt_ids, absl::Span occupancies, std::vector& in_out_keep_indices) { std::pair best_split = {alt_loc_start, alt_loc_count}; std::vector alt_loc_groups; float best_occupancy = -std::numeric_limits::infinity(); char best_group = alt_ids[alt_loc_start].front(); std::vector> occupancy_stats; // Group by residue type. GroupBy(comp_ids, alt_loc_start, alt_loc_count, [&](std::size_t start, std::size_t count) { // This callback selects the best residue group and the best // Alt-loc char within that group. alt_loc_groups.clear(); occupancy_stats.clear(); // Calculate total occupancy for residue type. for (std::size_t i = 0; i < count; ++i) { char alt_loc_id = alt_ids[start + i].front(); float occupancy = OccupancyToFloat(occupancies[start + i]); if (auto loc = absl::c_find(alt_loc_groups, alt_loc_id); loc == alt_loc_groups.end()) { occupancy_stats.emplace_back(1, occupancy); alt_loc_groups.push_back(alt_loc_id); } else { auto& stat = occupancy_stats[std::distance(alt_loc_groups.begin(), loc)]; ++stat.first; stat.second += occupancy; } } float total_occupancy = 0.0; for (auto& stat : occupancy_stats) { total_occupancy += stat.second / stat.first; } char group = *absl::c_min_element(alt_loc_groups); // Compares occupancy of residue to best seen so far. // Tie breaks alphabetic. if (total_occupancy > best_occupancy || (total_occupancy == best_occupancy && group < best_group)) { // Selects the best sub group. best_group = alt_loc_groups.front(); float best_amount = occupancy_stats.front().second / occupancy_stats.front().first; for (std::size_t i = 1; i < occupancy_stats.size(); ++i) { float amount = occupancy_stats[i].second / occupancy_stats[i].first; char group = alt_loc_groups[i]; if (amount > best_amount || (amount == best_amount && group < best_group)) { best_amount = amount; best_group = group; } } best_occupancy = total_occupancy; best_split = {start, count}; } }); // Now that the best residue type has been selected and the best alt-loc // within that has been selected add indices of indices to keep to the keep // list. auto [split_start, split_count] = best_split; GroupBy( atom_ids, split_start, split_count, [&in_out_keep_indices, &alt_ids, best_group](std::size_t start, std::size_t count) { // This makes sure we select an atom for each atom id even if it does // not have our selected alt-loc char. std::size_t best_index = start; for (std::size_t i = 1; i < count; ++i) { if (alt_ids[start + i].front() == best_group) { best_index = start + i; break; } } in_out_keep_indices.push_back(best_index); }, AtomEquiv); } // Finds the alt-loc group with the highest score and pushes the indices on to // the back of in_out_keep_indices. void ProcessAltLocGroupPartial( std::size_t alt_loc_start, std::size_t alt_loc_count, absl::Span atom_ids, absl::Span alt_ids, absl::Span occupancies, std::vector& in_out_keep_indices) { GroupBy( atom_ids, alt_loc_start, alt_loc_count, [&](std::size_t start, std::size_t count) { if (count == 1) { in_out_keep_indices.push_back(start); } else { float best_occ = OccupancyToFloat(occupancies[start]); std::size_t best_index = start; char best_group = alt_ids[start].front(); for (std::size_t i = 0; i < count; ++i) { float occ = OccupancyToFloat(occupancies[start + i]); char group = alt_ids[start + i].front(); if (occ > best_occ || (occ == best_occ && group < best_group)) { best_group = group; best_index = start + i; best_occ = occ; } } in_out_keep_indices.push_back(best_index); } }, AtomEquiv); } } // namespace // Resolves alt-locs returning the atom indices that will be left. std::vector ResolveMmcifAltLocs( const MmcifLayout& layout, absl::Span comp_ids, absl::Span atom_ids, absl::Span alt_ids, absl::Span occupancies, absl::Span chain_indices) { std::vector keep_indices; keep_indices.reserve(layout.num_atoms()); std::size_t alt_loc_start = 0; for (std::size_t chain_index : chain_indices) { auto [residues_start, residues_end] = layout.residue_range(chain_index); for (std::size_t residue = residues_start; residue < residues_end; ++residue) { std::size_t alt_loc_count = 0; auto [atom_start, atom_end] = layout.atom_range(residue); for (std::size_t i = atom_start; i < atom_end; ++i) { char alt_loc_id = alt_ids[i].front(); if (alt_loc_id == '.' || alt_loc_id == '?') { if (alt_loc_count > 0) { ProcessAltLocGroupPartial(alt_loc_start, alt_loc_count, atom_ids, alt_ids, occupancies, keep_indices); alt_loc_count = 0; } keep_indices.push_back(i); } else { if (alt_loc_count == 0) { alt_loc_start = i; } ++alt_loc_count; } } if (alt_loc_count > 0) { if (atom_end - atom_start == alt_loc_count) { ProcessAltLocGroupsWhole(alt_loc_start, alt_loc_count, comp_ids, atom_ids, alt_ids, occupancies, keep_indices); } else { ProcessAltLocGroupPartial(alt_loc_start, alt_loc_count, atom_ids, alt_ids, occupancies, keep_indices); } } } } return keep_indices; } } // namespace alphafold3