SeqAn3 3.4.3-rc.1
The Modern C++ library for sequence analysis.
Loading...
Searching...
No Matches
alignment_algorithm.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2006-2026 Knut Reinert & Freie Universität Berlin
2// SPDX-FileCopyrightText: 2016-2026 Knut Reinert & MPI für molekulare Genetik
3// SPDX-License-Identifier: BSD-3-Clause
4
10#pragma once
11
12#include <iterator>
13#include <memory>
14#include <optional>
15#include <ranges>
16#include <type_traits>
17
29#include <seqan3/utility/simd/concept.hpp>
30#include <seqan3/utility/simd/simd.hpp>
31#include <seqan3/utility/simd/simd_traits.hpp>
32#include <seqan3/utility/simd/views/to_simd.hpp>
35
36namespace seqan3::detail
37{
38
72template <typename config_t, typename... algorithm_policies_t>
73class alignment_algorithm :
74 public invoke_deferred_crtp_base<algorithm_policies_t, alignment_algorithm<config_t, algorithm_policies_t...>>...
75{
76private:
78 using traits_t = alignment_configuration_traits<config_t>;
79
90 template <typename alignment_algorithm_t = alignment_algorithm>
91 static auto _alignment_column_t() -> decltype(std::declval<alignment_algorithm_t>().current_alignment_column());
92
94 using alignment_column_t = decltype(_alignment_column_t());
96 using alignment_column_iterator_t = std::ranges::iterator_t<alignment_column_t>;
98 using alignment_result_t = typename traits_t::alignment_result_type;
99
100 static_assert(!std::same_as<alignment_result_t, empty_type>, "Alignment result type was not configured.");
101
103 using score_debug_matrix_t =
104 std::conditional_t<traits_t::is_debug,
105 two_dimensional_matrix<std::optional<typename traits_t::original_score_type>,
107 matrix_major_order::column>,
108 empty_type>;
110 using trace_debug_matrix_t =
111 std::conditional_t<traits_t::is_debug,
112 two_dimensional_matrix<std::optional<trace_directions>,
114 matrix_major_order::column>,
115 empty_type>;
116
117public:
121 constexpr alignment_algorithm() = default;
122 constexpr alignment_algorithm(alignment_algorithm const &) = default;
123 constexpr alignment_algorithm(alignment_algorithm &&) = default;
124 constexpr alignment_algorithm & operator=(alignment_algorithm const &) = default;
125 constexpr alignment_algorithm & operator=(alignment_algorithm &&) = default;
126#if SEQAN3_COMPILER_IS_GCC && (__GNUC__ >= 16)
127# pragma GCC diagnostic push
128# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
129#endif
130 ~alignment_algorithm() = default;
131#if SEQAN3_COMPILER_IS_GCC && (__GNUC__ >= 16)
132# pragma GCC diagnostic pop
133#endif
134
143 explicit constexpr alignment_algorithm(config_t const & cfg) :
144 invoke_deferred_crtp_base<algorithm_policies_t, alignment_algorithm<config_t, algorithm_policies_t...>>{cfg}...,
145 cfg_ptr{std::make_shared<config_t>(cfg)}
146 {
147 this->scoring_scheme = seqan3::get<align_cfg::scoring_scheme>(*cfg_ptr).scheme;
148 this->initialise_alignment_state(*cfg_ptr);
149 }
151
197 template <indexed_sequence_pair_range indexed_sequence_pairs_t, typename callback_t>
198 requires (!traits_t::is_vectorised) && std::invocable<callback_t, alignment_result_t>
199 void operator()(indexed_sequence_pairs_t && indexed_sequence_pairs, callback_t && callback)
200 {
201 using std::get;
202
203 for (auto && [sequence_pair, idx] : indexed_sequence_pairs)
204 compute_single_pair(idx, get<0>(sequence_pair), get<1>(sequence_pair), callback);
205 }
206
208 template <indexed_sequence_pair_range indexed_sequence_pairs_t, typename callback_t>
209 requires traits_t::is_vectorised && std::invocable<callback_t, alignment_result_t>
210 void operator()(indexed_sequence_pairs_t && indexed_sequence_pairs, callback_t && callback)
211 {
212 assert(cfg_ptr != nullptr);
213
214 static_assert(simd_concept<typename traits_t::score_type>, "Expected simd score type.");
215 static_assert(simd_concept<typename traits_t::trace_type>, "Expected simd trace type.");
216
217 // Extract the batch of sequences for the first and the second sequence.
218 auto sequence1_range = indexed_sequence_pairs | views::elements<0> | views::elements<0>;
219 auto sequence2_range = indexed_sequence_pairs | views::elements<0> | views::elements<1>;
220
221 // Initialise the find_optimum policy in the simd case.
222 this->initialise_find_optimum_policy(sequence1_range,
223 sequence2_range,
224 this->scoring_scheme.padding_match_score());
225
226 // Convert batch of sequences to sequence of simd vectors.
227 auto simd_sequences1 = convert_batch_of_sequences_to_simd_vector(sequence1_range);
228 auto simd_sequences2 = convert_batch_of_sequences_to_simd_vector(sequence2_range);
229
230 max_size_in_collection = std::pair{simd_sequences1.size(), simd_sequences2.size()};
231 // Reset the alignment state's optimum between executions of the alignment algorithm.
232 this->alignment_state.reset_optimum();
233
234 compute_matrix(simd_sequences1, simd_sequences2);
235
236 make_alignment_result(indexed_sequence_pairs, callback);
237 }
239
240private:
254 template <typename sequence_range_t>
255 constexpr auto convert_batch_of_sequences_to_simd_vector(sequence_range_t & sequences)
256 {
257 assert(static_cast<size_t>(std::ranges::distance(sequences)) <= traits_t::alignments_per_vector);
258
259 using simd_score_t = typename traits_t::score_type;
260
261 std::vector<simd_score_t, aligned_allocator<simd_score_t, alignof(simd_score_t)>> simd_sequence{};
262
263 for (auto && simd_vector_chunk : sequences | views::to_simd<simd_score_t>(this->scoring_scheme.padding_symbol))
264 for (auto && simd_vector : simd_vector_chunk)
265 simd_sequence.push_back(std::move(simd_vector));
266
267 return simd_sequence;
268 }
269
287 template <std::ranges::forward_range sequence1_t, std::ranges::forward_range sequence2_t, typename callback_t>
288 constexpr void
289 compute_single_pair(size_t const idx, sequence1_t && sequence1, sequence2_t && sequence2, callback_t & callback)
290 {
291 assert(cfg_ptr != nullptr);
292
293 if constexpr (traits_t::is_debug)
294 initialise_debug_matrices(sequence1, sequence2);
295
296 // Reset the alignment state's optimum between executions of the alignment algorithm.
297 this->alignment_state.reset_optimum();
298
299 if constexpr (traits_t::is_banded)
300 {
301 using seqan3::get;
302 // Get the band and check if band configuration is valid.
303 auto const & band = get<align_cfg::band_fixed_size>(*cfg_ptr);
304 check_valid_band_parameter(sequence1, sequence2, band);
305 auto && [subsequence1, subsequence2] = this->slice_sequences(sequence1, sequence2, band);
306 // It would be great to use this interface here instead
307 compute_matrix(subsequence1, subsequence2, band);
308 make_alignment_result(idx, subsequence1, subsequence2, callback);
309 }
310 else
311 {
312 compute_matrix(sequence1, sequence2);
313 make_alignment_result(idx, sequence1, sequence2, callback);
314 }
315 }
316
333 template <typename sequence1_t, typename sequence2_t>
334 constexpr void check_valid_band_parameter(sequence1_t && sequence1,
335 sequence2_t && sequence2,
336 align_cfg::band_fixed_size const & band)
337 {
338 static_assert(config_t::template exists<align_cfg::band_fixed_size>(),
339 "The band configuration is required for the banded alignment algorithm.");
340
342 static_assert(std::is_signed_v<diff_type>, "Only signed types can be used to test the band parameters.");
343
344 if (static_cast<diff_type>(band.lower_diagonal) > std::ranges::distance(sequence1))
345 {
346 throw invalid_alignment_configuration{
347 "Invalid band error: The lower diagonal excludes the whole alignment matrix."};
348 }
349
350 if (static_cast<diff_type>(band.upper_diagonal) < -std::ranges::distance(sequence2))
351 {
352 throw invalid_alignment_configuration{
353 "Invalid band error: The upper diagonal excludes the whole alignment matrix."};
354 }
355 }
356
369 template <typename sequence1_t, typename sequence2_t>
370 constexpr void initialise_debug_matrices(sequence1_t & sequence1, sequence2_t & sequence2)
371 {
372 size_t rows = std::ranges::distance(sequence2) + 1;
373 size_t cols = std::ranges::distance(sequence1) + 1;
374
375 score_debug_matrix = score_debug_matrix_t{number_rows{rows}, number_cols{cols}};
376 trace_debug_matrix = trace_debug_matrix_t{number_rows{rows}, number_cols{cols}};
377 }
378
386 template <typename sequence1_t, typename sequence2_t>
387 void compute_matrix(sequence1_t & sequence1, sequence2_t & sequence2)
388 requires (!traits_t::is_banded)
389 {
390 // ----------------------------------------------------------------------------
391 // Initialisation phase: allocate memory and initialise first column.
392 // ----------------------------------------------------------------------------
393
394 this->allocate_matrix(sequence1, sequence2);
395 initialise_first_alignment_column(sequence2);
396
397 // ----------------------------------------------------------------------------
398 // Recursion phase: compute column-wise the alignment matrix.
399 // ----------------------------------------------------------------------------
400
401 for (auto const & alphabet1 : sequence1)
402 {
403 compute_alignment_column<true>(this->scoring_scheme_profile_column(alphabet1), sequence2);
404 finalise_last_cell_in_column(true);
405 }
406
407 // ----------------------------------------------------------------------------
408 // Wrap up phase: track score in last column and prepare the alignment result.
409 // ----------------------------------------------------------------------------
410
411 finalise_alignment();
412 }
413
415 template <typename sequence1_t, typename sequence2_t>
416 void compute_matrix(sequence1_t & sequence1, sequence2_t & sequence2, align_cfg::band_fixed_size const & band)
417 requires (traits_t::is_banded)
418 {
419 // ----------------------------------------------------------------------------
420 // Initialisation phase: allocate memory and initialise first column.
421 // ----------------------------------------------------------------------------
422
423 // Allocate and initialise first column.
424 this->allocate_matrix(sequence1, sequence2, band, this->alignment_state);
425 using row_index_t = std::ranges::range_difference_t<sequence2_t>;
426 row_index_t last_row_index = this->score_matrix.band_row_index;
427 initialise_first_alignment_column(std::views::take(sequence2, last_row_index));
428
429 // ----------------------------------------------------------------------------
430 // 1st recursion phase: iterate as long as the band intersects with the first row.
431 // ----------------------------------------------------------------------------
432
433 row_index_t sequence2_size = std::ranges::distance(sequence2);
434 for (auto const & seq1_value : std::views::take(sequence1, this->score_matrix.band_col_index))
435 {
436 compute_alignment_column<true>(seq1_value, std::views::take(sequence2, ++last_row_index));
437 // Only if band reached last row of matrix the last cell might be tracked.
438 finalise_last_cell_in_column(last_row_index >= sequence2_size);
439 }
440
441 // ----------------------------------------------------------------------------
442 // 2nd recursion phase: iterate until the end of the matrix.
443 // ----------------------------------------------------------------------------
444
445 size_t first_row_index = 0;
446 for (auto const & seq1_value : std::views::drop(sequence1, this->score_matrix.band_col_index))
447 {
448 // In the second phase the band moves in every column one base down on the second sequence.
449 compute_alignment_column<false>(seq1_value, sequence2 | views::slice(first_row_index++, ++last_row_index));
450 // Only if band reached last row of matrix the last cell might be tracked.
451 finalise_last_cell_in_column(last_row_index >= sequence2_size);
452 }
453
454 // ----------------------------------------------------------------------------
455 // Wrap up phase: track score in last column and prepare the alignment result.
456 // ----------------------------------------------------------------------------
457
458 finalise_alignment();
459 }
460
473 template <typename sequence2_t>
474 auto initialise_first_alignment_column(sequence2_t && sequence2)
475 {
476 // Get the initial column.
477 alignment_column = this->current_alignment_column();
478 assert(!alignment_column.empty()); // Must contain at least one element.
479
480 // Initialise first cell.
481 alignment_column_it = alignment_column.begin();
482 this->init_origin_cell(*alignment_column_it, this->alignment_state);
483
484 // Initialise the remaining cells of this column.
485 for (auto it = std::ranges::begin(sequence2); it != std::ranges::end(sequence2); ++it)
486 this->init_column_cell(*++alignment_column_it, this->alignment_state);
487
488 // Finalise the last cell of the initial column.
489 bool at_last_row = true;
490 if constexpr (traits_t::is_banded) // If the band reaches until the last row of the matrix.
491 at_last_row = static_cast<size_t>(this->score_matrix.band_row_index) == this->score_matrix.num_rows - 1;
492
493 finalise_last_cell_in_column(at_last_row);
494 }
495
511 template <bool initialise_first_cell, typename sequence1_value_t, typename sequence2_t>
512 void compute_alignment_column(sequence1_value_t const & seq1_value, sequence2_t && sequence2)
513 {
514 this->next_alignment_column(); // move to next column and set alignment column iterator accordingly.
515 alignment_column = this->current_alignment_column();
516 alignment_column_it = alignment_column.begin();
517
518 auto seq2_it = std::ranges::begin(sequence2);
519
520 if constexpr (initialise_first_cell) // Initialise first cell if it intersects with the first row of the matrix.
521 {
522 this->init_row_cell(*alignment_column_it, this->alignment_state);
523 }
524 else // Compute first cell of banded column if it does not intersect with the first row of the matrix.
525 {
526 this->compute_first_band_cell(*alignment_column_it,
527 this->alignment_state,
528 this->scoring_scheme.score(seq1_value, *seq2_it));
529 ++seq2_it;
530 }
531
532 for (; seq2_it != std::ranges::end(sequence2); ++seq2_it)
533 this->compute_cell(*++alignment_column_it,
534 this->alignment_state,
535 this->scoring_scheme.score(seq1_value, *seq2_it));
536 }
537
548 constexpr void finalise_last_cell_in_column(bool const at_last_row) noexcept
549 {
550 if (at_last_row)
551 this->check_score_of_last_row_cell(*alignment_column_it, this->alignment_state);
552
553 if constexpr (traits_t::is_debug)
554 dump_alignment_column();
555 }
556
558 constexpr void finalise_alignment() noexcept
559 {
560 // ----------------------------------------------------------------------------
561 // Check for the optimum in last cell/column.
562 // ----------------------------------------------------------------------------
563
564 this->check_score_of_cells_in_last_column(alignment_column, this->alignment_state);
565 this->check_score_of_last_cell(*alignment_column_it, this->alignment_state);
566 }
567
594 template <typename index_t, typename sequence1_t, typename sequence2_t, typename callback_t>
595 requires (!traits_t::is_vectorised)
596 constexpr void make_alignment_result([[maybe_unused]] index_t const idx,
597 [[maybe_unused]] sequence1_t & sequence1,
598 [[maybe_unused]] sequence2_t & sequence2,
599 callback_t & callback)
600 {
601 using result_value_t = typename alignment_result_value_type_accessor<alignment_result_t>::type;
602
603 // ----------------------------------------------------------------------------
604 // Build the alignment result
605 // ----------------------------------------------------------------------------
606
607 static_assert(seqan3::detail::alignment_configuration_traits<config_t>::has_output_configuration,
608 "The configuration must contain at least one align_cfg::output_* element.");
609
610 result_value_t res{};
611
612 if constexpr (traits_t::output_sequence1_id)
613 res.sequence1_id = idx;
614
615 if constexpr (traits_t::output_sequence2_id)
616 res.sequence2_id = idx;
617
618 // Choose what needs to be computed.
619 if constexpr (traits_t::compute_score)
620 res.score = this->alignment_state.optimum.score;
621
622 if constexpr (traits_t::compute_end_positions)
623 {
624 using alignment_coordinate_t = detail::advanceable_alignment_coordinate<>;
625 res.end_positions = alignment_coordinate_t{column_index_type{this->alignment_state.optimum.column_index},
626 row_index_type{this->alignment_state.optimum.row_index}};
627 // At some point this needs to be refactored so that it is not necessary to adapt the coordinate.
628 if constexpr (traits_t::is_banded)
629 {
630 res.end_positions.second += res.end_positions.first - this->trace_matrix.band_col_index;
631 res.end_positions.first = this->to_original_sequence1_position(res.end_positions.first);
632 res.end_positions.second = this->to_original_sequence2_position(res.end_positions.second);
633 }
634 }
635
636 if constexpr (traits_t::compute_begin_positions)
637 {
638 // Get a aligned sequence builder for banded or un-banded case.
639 aligned_sequence_builder builder{sequence1, sequence2};
640
641 detail::matrix_coordinate const optimum_coordinate{
642 detail::row_index_type{this->alignment_state.optimum.row_index},
643 detail::column_index_type{this->alignment_state.optimum.column_index}};
644 auto trace_res = builder(this->trace_matrix.trace_path(optimum_coordinate));
645 res.begin_positions.first =
646 this->to_original_sequence1_position(trace_res.first_sequence_slice_positions.first);
647 res.begin_positions.second =
648 this->to_original_sequence2_position(trace_res.second_sequence_slice_positions.first);
649
650 if constexpr (traits_t::compute_sequence_alignment)
651 res.alignment = std::move(trace_res.alignment);
652 }
653
654 // Store the matrices in debug mode.
655 if constexpr (traits_t::is_debug)
656 {
657 res.score_debug_matrix = std::move(score_debug_matrix);
658 if constexpr (traits_t::compute_sequence_alignment) // compute alignment
659 res.trace_debug_matrix = std::move(trace_debug_matrix);
660 }
661
662 callback(std::move(res));
663 }
664
690 template <typename indexed_sequence_pair_range_t, typename callback_t>
691 requires traits_t::is_vectorised
692 constexpr auto make_alignment_result(indexed_sequence_pair_range_t && index_sequence_pairs, callback_t & callback)
693 {
694 using result_value_t = typename alignment_result_value_type_accessor<alignment_result_t>::type;
695
696 size_t simd_index = 0;
697 for (auto && [sequence_pairs, alignment_index] : index_sequence_pairs)
698 {
699 (void)sequence_pairs;
700 result_value_t res{};
701
702 if constexpr (traits_t::output_sequence1_id)
703 res.sequence1_id = alignment_index;
704
705 if constexpr (traits_t::output_sequence2_id)
706 res.sequence2_id = alignment_index;
707
708 if constexpr (traits_t::compute_score)
709 res.score = this->alignment_state.optimum.score[simd_index]; // Just take this
710
711 if constexpr (traits_t::compute_end_positions)
712 {
713 res.end_positions.first =
714 this->to_original_sequence1_position(this->alignment_state.optimum.column_index[simd_index]);
715 res.end_positions.second =
716 this->to_original_sequence2_position(this->alignment_state.optimum.row_index[simd_index]);
717 }
718
719 callback(std::move(res));
720 ++simd_index;
721 }
722 }
723
732 void dump_alignment_column()
733 {
734 using std::get;
735
736 auto column = this->current_alignment_column();
737
738 auto coord = get<1>(column.front()).coordinate;
739 if constexpr (traits_t::is_banded)
740 coord.second += coord.first - this->score_matrix.band_col_index;
741
742 matrix_offset offset{row_index_type{static_cast<std::ptrdiff_t>(coord.second)},
743 column_index_type{static_cast<std::ptrdiff_t>(coord.first)}};
744
745 std::ranges::copy(column
746 | std::views::transform(
747 [](auto const & tpl)
748 {
749 using std::get;
750 return get<0>(tpl).current;
751 }),
752 score_debug_matrix.begin() + offset);
753
754 // if traceback is enabled.
755 if constexpr (traits_t::compute_sequence_alignment)
756 {
758 column
759 | std::views::transform(
760 [](auto const & tpl)
761 {
762 using std::get;
763 auto trace = get<1>(tpl).current;
764
765 if (auto _up = (trace & trace_directions::up_open); _up == trace_directions::carry_up_open)
766 trace = trace ^ trace_directions::carry_up_open; // remove silent up open signal
767 else if (_up == trace_directions::up_open)
768 trace = trace ^ trace_directions::up; // display up open only with single bit.
769
770 if (auto _left = (trace & trace_directions::left_open);
771 _left == trace_directions::carry_left_open)
772 trace = trace ^ trace_directions::carry_left_open; // remove silent left open signal
773 else if (_left == trace_directions::left_open)
774 trace = trace ^ trace_directions::left; // display left open only with single bit.
775
776 return trace;
777 }),
778 trace_debug_matrix.begin() + offset);
779 }
780 }
781
785 alignment_column_t alignment_column{};
787 alignment_column_iterator_t alignment_column_it{};
789 score_debug_matrix_t score_debug_matrix{};
791 trace_debug_matrix_t trace_debug_matrix{};
793 std::pair<size_t, size_t> max_size_in_collection{};
794};
795
796} // namespace seqan3::detail
Provides seqan3::detail::align_config_band.
Provides seqan3::align_cfg::scoring_scheme.
Provides seqan3::detail::align_result_selector.
Provides seqan3::aligned_allocator.
Provides seqan3::detail::aligned_sequence_builder.
Includes customized exception types for the alignment module .
Provides concepts needed internally for the alignment algorithms.
Provides helper type traits for the configuration and execution of the alignment algorithm.
T begin(T... args)
T copy(T... args)
Provides seqan3::detail::deferred_crtp_base.
Provides seqan3::views::elements.
Provides seqan3::detail::empty_type.
Provides various type traits for use on functions.
@ offset
Sequence (seqan3::field::seq) relative start position (0-based), unsigned value.
typename decltype(detail::split_after< i >(list_t{}))::first_type take
Return a seqan3::type_list of the first n types in the input type list.
Definition type_list/traits.hpp:374
typename decltype(detail::split_after< i >(list_t{}))::second_type drop
Return a seqan3::type_list of the types in the input type list, except the first n.
Definition type_list/traits.hpp:392
constexpr auto slice
A view adaptor that returns a half-open interval on the underlying range.
Definition slice.hpp:137
T move(T... args)
constexpr auto const & get(configuration< configs_t... > const &config) noexcept
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition configuration.hpp:412
SeqAn specific customisations in the standard namespace.
Provides the declaration of seqan3::detail::trace_directions.
Hide me