OneScience's picture
Upload folder using huggingface_hub
35cdf53 verified
Raw
History Blame Contribute Delete
4.28 kB
"""A Python wrapper for hmmbuild - construct HMM profiles from MSA."""
import os
import re
import tempfile
from typing import Literal
from flax_model.alphafold3.data import parsers
from flax_model.alphafold3.data.tools import subprocess_utils
class Hmmbuild(object):
"""Python wrapper of the hmmbuild binary."""
def __init__(
self,
*,
binary_path: str,
singlemx: bool = False,
alphabet: str | None = None,
):
"""Initializes the Python hmmbuild wrapper.
Args:
binary_path: The path to the hmmbuild executable.
singlemx: Whether to use --singlemx flag. If True, it forces HMMBuild to
just use a common substitution score matrix.
alphabet: The alphabet to assert when building a profile. Useful when
hmmbuild cannot guess the alphabet. If None, no alphabet is asserted.
Raises:
RuntimeError: If hmmbuild binary not found within the path.
"""
self._binary_path = binary_path
self._singlemx = singlemx
self._alphabet = alphabet
subprocess_utils.check_binary_exists(path=self._binary_path, name='hmmbuild')
def build_profile_from_sto(self, sto: str, model_construction='fast') -> str:
"""Builds a HHM for the aligned sequences given as an A3M string.
Args:
sto: A string with the aligned sequences in the Stockholm format.
model_construction: Whether to use reference annotation in the msa to
determine consensus columns ('hand') or default ('fast').
Returns:
A string with the profile in the HMM format.
Raises:
RuntimeError: If hmmbuild fails.
"""
return self._build_profile(
sto, informat='stockholm', model_construction=model_construction
)
def build_profile_from_a3m(self, a3m: str) -> str:
"""Builds a HHM for the aligned sequences given as an A3M string.
Args:
a3m: A string with the aligned sequences in the A3M format.
Returns:
A string with the profile in the HMM format.
Raises:
RuntimeError: If hmmbuild fails.
"""
lines = []
for sequence, description in parsers.lazy_parse_fasta_string(a3m):
sequence = re.sub('[a-z]+', '', sequence) # Remove inserted residues.
lines.append(f'>{description}\n{sequence}\n')
msa = ''.join(lines)
return self._build_profile(msa, informat='afa')
def _build_profile(
self,
msa: str,
informat: Literal['afa', 'stockholm'],
model_construction: str = 'fast',
) -> str:
"""Builds a HMM for the aligned sequences given as an MSA string.
Args:
msa: A string with the aligned sequences, in A3M or STO format.
informat: One of 'afa' (aligned FASTA) or 'sto' (Stockholm).
model_construction: Whether to use reference annotation in the msa to
determine consensus columns ('hand') or default ('fast').
Returns:
A string with the profile in the HMM format.
Raises:
RuntimeError: If hmmbuild fails.
ValueError: If unspecified arguments are provided.
"""
if model_construction not in {'hand', 'fast'}:
raise ValueError(f'Bad {model_construction=}. Only hand or fast allowed.')
with tempfile.TemporaryDirectory() as query_tmp_dir:
input_msa_path = os.path.join(query_tmp_dir, 'query.msa')
output_hmm_path = os.path.join(query_tmp_dir, 'output.hmm')
with open(input_msa_path, 'w') as f:
f.write(msa)
# Specify the format as we don't specify the input file extension. See
# https://github.com/EddyRivasLab/hmmer/issues/321 for more details.
cmd_flags = ['--informat', informat]
# If adding flags, we have to do so before the output and input:
if model_construction == 'hand':
cmd_flags.append(f'--{model_construction}')
if self._singlemx:
cmd_flags.append('--singlemx')
if self._alphabet:
cmd_flags.append(f'--{self._alphabet}')
cmd_flags.extend([output_hmm_path, input_msa_path])
cmd = [self._binary_path, *cmd_flags]
subprocess_utils.run(
cmd=cmd,
cmd_name='Hmmbuild',
log_stdout=False,
log_stderr=True,
log_on_process_error=True,
)
with open(output_hmm_path) as f:
hmm = f.read()
return hmm