// 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 #include #include #include #include #include "absl/strings/ascii.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "pybind11/pybind11.h" #include "pybind11/stl.h" namespace { namespace py = pybind11; std::vector ConvertA3MToStockholm( std::vector a3m_sequences) { std::vector stockholm_sequences(a3m_sequences.size()); auto max_length_element = std::max_element(a3m_sequences.begin(), a3m_sequences.end(), [](absl::string_view lhs, absl::string_view rhs) { return lhs.size() < rhs.size(); }); for (auto& out : stockholm_sequences) { out.reserve(max_length_element->size()); } // While any sequence has remaining columns. while (std::any_of(a3m_sequences.begin(), a3m_sequences.end(), [](absl::string_view in) { return !in.empty(); })) { if (std::any_of(a3m_sequences.begin(), a3m_sequences.end(), [](absl::string_view in) { return !in.empty() && absl::ascii_islower(in.front()); })) { // Insertion(s) found at column. for (std::size_t i = 0; i < a3m_sequences.size(); ++i) { absl::string_view& in = a3m_sequences[i]; std::string& out = stockholm_sequences[i]; if (!in.empty() && absl::ascii_islower(in.front())) { // Consume insertion. out.push_back(absl::ascii_toupper(in.front())); in.remove_prefix(1); } else { // Row requires padding. out.push_back('-'); } } } else { // No insertions found. for (std::size_t i = 0; i < a3m_sequences.size(); ++i) { absl::string_view& in = a3m_sequences[i]; std::string& out = stockholm_sequences[i]; if (!in.empty()) { // Consume entire column. out.push_back(in.front()); in.remove_prefix(1); } else { // One alignment is shorter than the others. Should not happen with // valid A3M input. throw std::invalid_argument(absl::StrFormat( "a3m rows have inconsistent lengths; row %d has no columns left " "but not all rows are exhausted", i)); } } } } return stockholm_sequences; } std::string AlignSequenceToGaplessQuery(absl::string_view sequence, absl::string_view query_sequence) { if (sequence.size() != query_sequence.size()) { throw py::value_error( absl::StrFormat("The sequence (%d) and the query sequence (%d) don't " "have the same length.", sequence.size(), query_sequence.size())); } std::string output; for (std::size_t residue_index = 0, sequence_length = sequence.size(); residue_index < sequence_length; ++residue_index) { const char query_residue = query_sequence[residue_index]; const char residue = sequence[residue_index]; if (query_residue != '-') { // No gap in the query, so the residue is aligned. output += residue; } else if (residue == '-') { // Gap in both sequence and query, simply skip. continue; } else { // Gap only in the query, so this must be an inserted residue. output += absl::ascii_tolower(residue); } } return output; } constexpr char kConvertA3mToStockholm[] = R"( Converts a list of sequences in a3m format to stockholm format sequences. As an example if the input is: abCD CgD fCDa Then the output will be: ABC-D- --CGD- F-C-DA Args: a3m_sequences: A list of strings in a3m format. Returns A list of strings converted to stockholm format. )"; constexpr char kAlignSequenceToGaplessQuery[] = R"( Aligns a sequence to a gapless query sequence. This is useful when converting Stockholm MSA to A3M MSA. Example: Seq : AB--E Query: A--DE Output: Ab-E. Args: sequence: A string containing to be aligned. query_sequence: A string containing the reference sequence to align to. Returns The input sequence with gaps dropped where both the `sequence` and `query_sequence` have gaps, and sequence elements non-capitalized where the `query_sequence` has a gap, but the `sequence` does not. )"; } // namespace namespace alphafold3 { void RegisterModuleMsaConversion(pybind11::module m) { m.def("convert_a3m_to_stockholm", &ConvertA3MToStockholm, py::arg("a3m_sequences"), py::call_guard(), py::doc(kConvertA3mToStockholm + 1)); m.def("align_sequence_to_gapless_query", &AlignSequenceToGaplessQuery, py::arg("sequence"), py::arg("query_sequence"), py::call_guard(), py::doc(kAlignSequenceToGaplessQuery + 1)); } } // namespace alphafold3