Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions q2_alignment/_mafft.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def run_command(cmd, output_fp, verbose=True, env=None):


def _mafft(sequences_fp, alignment_fp, n_threads, parttree, addfragments,
keeplength, large):
keeplength, large, strategy, maxiterate, retree):
# Save original sequence IDs since long ids (~250 chars) can be truncated
# by mafft. We'll replace the IDs in the aligned sequences file output by
# mafft with the originals.
Expand Down Expand Up @@ -107,6 +107,15 @@ def _mafft(sequences_fp, alignment_fp, n_threads, parttree, addfragments,
env.update({'MAFFT_TMPDIR': get_cache().get_tmp_path()})
cmd += ['--large']

if strategy:
cmd += [("--" + strategy)]

if maxiterate is not None:
cmd += ['--maxiterate', str(maxiterate)]

if retree is not None:
cmd += ['--retree', str(retree)]

if alignment_fp is not None:
add_flag = '--addfragments' if addfragments else '--add'
cmd += [add_flag, sequences_fp, alignment_fp]
Expand Down Expand Up @@ -141,9 +150,15 @@ def _mafft(sequences_fp, alignment_fp, n_threads, parttree, addfragments,
def mafft(sequences: DNAFASTAFormat,
n_threads: int = 1,
parttree: bool = False,
large: bool = False) -> AlignedDNAFASTAFormat:
large: bool = False,
strategy: str | None = None,
maxiterate: int | None = None,
retree: int | None = None,) -> AlignedDNAFASTAFormat:
sequences_fp = str(sequences)
return _mafft(sequences_fp, None, n_threads, parttree, False, False, large)
return _mafft(
sequences_fp, None, n_threads, parttree, False, False, large,
strategy, maxiterate, retree
)


def mafft_add(alignment: AlignedDNAFASTAFormat,
Expand All @@ -152,9 +167,13 @@ def mafft_add(alignment: AlignedDNAFASTAFormat,
parttree: bool = False,
addfragments: bool = False,
keeplength: bool = False,
large: bool = False) -> AlignedDNAFASTAFormat:
large: bool = False,
strategy: str | None = None,
maxiterate: int | None = None,
retree: int | None = None) -> AlignedDNAFASTAFormat:
alignment_fp = str(alignment)
sequences_fp = str(sequences)
return _mafft(
sequences_fp, alignment_fp, n_threads, parttree, addfragments,
keeplength, large)
keeplength, large, strategy, maxiterate, retree
)
75 changes: 47 additions & 28 deletions q2_alignment/plugin_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,45 @@
# ----------------------------------------------------------------------------

from qiime2.plugin import (
Plugin, Float, Bool, Range, Citations, Threads)
Plugin, Float, Bool, Range, Citations, Threads, Int, Str, Choices)
from q2_types.feature_data import FeatureData, Sequence, AlignedSequence

import q2_alignment

mafft_params = {
"n_threads": Threads,
"parttree": Bool,
"large": Bool,
"strategy": Str % Choices({
"auto", "nofft", "globalpair", "localpair", "genafpair",
}),
"maxiterate": Int % Range(0, None),
"retree": Int % Range(0, None),
}
mafft_param_descriptions = {
"n_threads": "The number of threads. (Use `auto` to automatically use "
"all available cores)",
"parttree": "This flag is required if the number of sequences being "
"aligned are larger than 1,000,000. Disabled by default.",
"large": "This flag is required when aligning very large datasets "
"that do not otherwise fit into memory. Temporary data is "
"then stored in files, instead of RAM. The --use-cache "
"flag specifies the storage location of the temporary files "
"created. By default, $TMP/qiime2/ is used.",
"strategy": "Specifies the multiple alignment strategy to use. "
"Exactly one strategy may be specified. Valid options "
"are: 'auto', 'nofft', 'globalpair', 'localpair', "
"and 'genafpair'. Default strategy: FFT-NS.",
'maxiterate': 'Specifies how many iterative refinement cycles are '
'performed after the initial progressive alignment. '
'By default, no iterative refinement is performed.',
'retree': 'Specifies the number of times the guide tree is rebuilt '
'during the progressive stage. Typically, tree topology '
'stabilizes after 2-3 iterations and higher values rarely '
'improves alignment quality enough to justify the extra '
'computation.',
}

citations = Citations.load('citations.bib', package='q2_alignment')
plugin = Plugin(
name='alignment',
Expand All @@ -26,21 +60,14 @@
plugin.methods.register_function(
function=q2_alignment.mafft,
inputs={'sequences': FeatureData[Sequence]},
parameters={'n_threads': Threads,
'parttree': Bool,
'large': Bool},
parameters={
**mafft_params,
},
outputs=[('alignment', FeatureData[AlignedSequence])],
input_descriptions={'sequences': 'The sequences to be aligned.'},
parameter_descriptions={
'n_threads': 'The number of threads. (Use `auto` to automatically use '
'all available cores)',
'parttree': 'This flag is required if the number of sequences being '
'aligned are larger than 1000000. Disabled by default',
'large': 'This flag is required when aligning very large datasets '
'that do not otherwise fit into memory. Temporary data is '
'then stored in files, instead of RAM. The --use-cache '
'flag specifies the storage location of the temporary files '
'created. By default, $TMP/qiime2/ is used.'},
**mafft_param_descriptions,
},
output_descriptions={'alignment': 'The aligned sequences.'},
name='De novo multiple sequence alignment with MAFFT',
description=("Perform de novo multiple sequence alignment using MAFFT."),
Expand All @@ -51,20 +78,17 @@
function=q2_alignment.mafft_add,
inputs={'alignment': FeatureData[AlignedSequence],
'sequences': FeatureData[Sequence]},
parameters={'n_threads': Threads,
'parttree': Bool,
'addfragments': Bool,
'keeplength': Bool,
'large': Bool},
parameters={
**mafft_params,
'addfragments': Bool,
'keeplength': Bool,
},
outputs=[('expanded_alignment', FeatureData[AlignedSequence])],
input_descriptions={'alignment': 'The alignment to which '
'sequences should be added.',
'sequences': 'The sequences to be added.'},
parameter_descriptions={
'n_threads': 'The number of threads. (Use `auto` to automatically use '
'all available cores)',
'parttree': 'This flag is required if the number of sequences being '
'aligned are larger than 1000000. Disabled by default',
**mafft_param_descriptions,
'addfragments': 'Optimize for the addition of short sequence '
'fragments (for example, primer or amplicon '
'sequences). If not set, default sequence addition '
Expand All @@ -73,12 +97,7 @@
'Any added sequence that would otherwise introduce new '
'insertions into the alignment, will have those '
'insertions deleted, to preserve original alignment '
'length.',
'large': 'This flag is required when aligning very large datasets '
'that do not otherwise fit into memory. Temporary data is '
'then stored in files, instead of RAM. The --use-cache '
'flag specifies the storage location of the temporary files '
'created. By default, $TMP/qiime2/ is used.'},
'length.'},
output_descriptions={
'expanded_alignment': 'Alignment containing the provided aligned and '
'unaligned sequences.'},
Expand Down
120 changes: 120 additions & 0 deletions q2_alignment/tests/test_mafft.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ def test_mafft_parttree_exception(self):
with redirected_stdio(stderr=os.devnull):
mafft(input_sequences)

@patch('q2_alignment._mafft.skbio.TabularMSA.read')
@patch('q2_alignment._mafft.run_command')
def test_mafft_parttree_flag(self, mock_run_cmd, mock_read):
input_sequences, exp = self._prepare_sequence_data()
mock_read.return_value = exp

mafft(input_sequences, parttree=True)

mock_run_cmd.assert_called_with(
["mafft", "--preservecase", "--inputorder",
"--thread", "1", "--parttree", ANY],
ANY,
env=None
)

def test_mafft_large(self):
input_sequences, exp = self._prepare_sequence_data()

Expand All @@ -91,6 +106,111 @@ def test_mafft_large(self):
constructor=skbio.DNA)
self.assertEqual(obs, exp)

@patch('q2_alignment._mafft.skbio.TabularMSA.read')
@patch('q2_alignment._mafft.run_command')
def test_mafft_globalpair_strategy(self, mock_run_cmd, mock_read):
input_sequences, exp = self._prepare_sequence_data()
mock_read.return_value = exp

mafft(input_sequences, strategy="globalpair")

mock_run_cmd.assert_called_with(
["mafft", "--preservecase", "--inputorder",
"--thread", "1", "--globalpair", ANY],
ANY,
env=None
)

@patch('q2_alignment._mafft.skbio.TabularMSA.read')
@patch('q2_alignment._mafft.run_command')
def test_mafft_localpair_strategy(self, mock_run_cmd, mock_read):
input_sequences, exp = self._prepare_sequence_data()
mock_read.return_value = exp

mafft(input_sequences, strategy="localpair")

mock_run_cmd.assert_called_with(
["mafft", "--preservecase", "--inputorder",
"--thread", "1", "--localpair", ANY],
ANY,
env=None
)

@patch('q2_alignment._mafft.skbio.TabularMSA.read')
@patch('q2_alignment._mafft.run_command')
def test_mafft_genafpair_strategy(self, mock_run_cmd, mock_read):
input_sequences, exp = self._prepare_sequence_data()
mock_read.return_value = exp

mafft(input_sequences, strategy="genafpair")

mock_run_cmd.assert_called_with(
["mafft", "--preservecase", "--inputorder",
"--thread", "1", "--genafpair", ANY],
ANY,
env=None
)

@patch('q2_alignment._mafft.skbio.TabularMSA.read')
@patch('q2_alignment._mafft.run_command')
def test_mafft_maxiterate_flag(self, mock_run_cmd, mock_read):
input_sequences, exp = self._prepare_sequence_data()
mock_read.return_value = exp

mafft(input_sequences, maxiterate=1000)

mock_run_cmd.assert_called_with(
["mafft", "--preservecase", "--inputorder",
"--thread", "1", "--maxiterate", "1000", ANY],
ANY,
env=None
)

@patch('q2_alignment._mafft.skbio.TabularMSA.read')
@patch('q2_alignment._mafft.run_command')
def test_mafft_retree_flag(self, mock_run_cmd, mock_read):
input_sequences, exp = self._prepare_sequence_data()
mock_read.return_value = exp

mafft(input_sequences, retree=3)

mock_run_cmd.assert_called_with(
["mafft", "--preservecase", "--inputorder",
"--thread", "1", "--retree", "3", ANY],
ANY,
env=None
)

@patch('q2_alignment._mafft.skbio.TabularMSA.read')
@patch('q2_alignment._mafft.run_command')
def test_mafft_nofft_strategy(self, mock_run_cmd, mock_read):
input_sequences, exp = self._prepare_sequence_data()
mock_read.return_value = exp

mafft(input_sequences, strategy="nofft")

mock_run_cmd.assert_called_with(
["mafft", "--preservecase", "--inputorder",
"--thread", "1", "--nofft", ANY],
ANY,
env=None
)

@patch('q2_alignment._mafft.skbio.TabularMSA.read')
@patch('q2_alignment._mafft.run_command')
def test_mafft_auto_strategy(self, mock_run_cmd, mock_read):
input_sequences, exp = self._prepare_sequence_data()
mock_read.return_value = exp

mafft(input_sequences, strategy="auto")

mock_run_cmd.assert_called_with(
["mafft", "--preservecase", "--inputorder",
"--thread", "1", "--auto", ANY],
ANY,
env=None
)


class MafftAddTests(TestPluginBase):
package = 'q2_alignment.tests'
Expand Down
Loading