|
|
|
|
| """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) |
| 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) |
|
|
| |
| |
| cmd_flags = ['--informat', informat] |
| |
| 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 |
|
|