SeqAn3  3.0.3
The Modern C++ library for sequence analysis.
alignment_algorithm.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2021, Knut Reinert & MPI für molekulare Genetik
4 // This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
5 // shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
6 // -----------------------------------------------------------------------------------------------------
7 
13 #pragma once
14 
15 #include <seqan3/std/iterator>
16 #include <memory>
17 #include <optional>
18 #include <seqan3/std/ranges>
19 #include <type_traits>
20 
39 
40 namespace seqan3::detail
41 {
42 
76 template <typename config_t, typename ...algorithm_policies_t>
77 class alignment_algorithm :
78  public invoke_deferred_crtp_base<algorithm_policies_t, alignment_algorithm<config_t, algorithm_policies_t...>>...
79 {
80 private:
81 
83  using traits_t = alignment_configuration_traits<config_t>;
85  using alignment_column_t = decltype(std::declval<alignment_algorithm>().current_alignment_column());
87  using alignment_column_iterator_t = std::ranges::iterator_t<alignment_column_t>;
89  using alignment_result_t = typename traits_t::alignment_result_type;
90 
91  static_assert(!std::same_as<alignment_result_t, empty_type>, "Alignment result type was not configured.");
92 
94  using score_debug_matrix_t =
95  std::conditional_t<traits_t::is_debug,
96  two_dimensional_matrix<std::optional<typename traits_t::original_score_type>,
98  matrix_major_order::column>,
99  empty_type>;
101  using trace_debug_matrix_t =
102  std::conditional_t<traits_t::is_debug,
103  two_dimensional_matrix<std::optional<trace_directions>,
105  matrix_major_order::column>,
106  empty_type>;
107 
108 public:
112  constexpr alignment_algorithm() = default;
113  constexpr alignment_algorithm(alignment_algorithm const &) = default;
114  constexpr alignment_algorithm(alignment_algorithm &&) = default;
115  constexpr alignment_algorithm & operator=(alignment_algorithm const &) = default;
116  constexpr alignment_algorithm & operator=(alignment_algorithm &&) = default;
117  ~alignment_algorithm() = default;
118 
127  explicit constexpr alignment_algorithm(config_t const & cfg) :
128  invoke_deferred_crtp_base<algorithm_policies_t, alignment_algorithm<config_t, algorithm_policies_t...>>{cfg}...,
129  cfg_ptr{std::make_shared<config_t>(cfg)}
130  {
131  this->scoring_scheme = seqan3::get<align_cfg::scoring_scheme>(*cfg_ptr).scheme;
132  this->initialise_alignment_state(*cfg_ptr);
133  }
135 
181  template <indexed_sequence_pair_range indexed_sequence_pairs_t, typename callback_t>
183  requires (!traits_t::is_vectorised) && std::invocable<callback_t, alignment_result_t>
185  void operator()(indexed_sequence_pairs_t && indexed_sequence_pairs, callback_t && callback)
186  {
187  using std::get;
188 
189  for (auto && [sequence_pair, idx] : indexed_sequence_pairs)
190  compute_single_pair(idx, get<0>(sequence_pair), get<1>(sequence_pair), callback);
191  }
192 
194  template <indexed_sequence_pair_range indexed_sequence_pairs_t, typename callback_t>
196  requires traits_t::is_vectorised && std::invocable<callback_t, alignment_result_t>
198  void operator()(indexed_sequence_pairs_t && indexed_sequence_pairs, callback_t && callback)
199  {
200  assert(cfg_ptr != nullptr);
201 
202  static_assert(simd_concept<typename traits_t::score_type>, "Expected simd score type.");
203  static_assert(simd_concept<typename traits_t::trace_type>, "Expected simd trace type.");
204 
205  // Extract the batch of sequences for the first and the second sequence.
206  auto sequence1_range = indexed_sequence_pairs | views::elements<0> | views::elements<0>;
207  auto sequence2_range = indexed_sequence_pairs | views::elements<0> | views::elements<1>;
208 
209  // Initialise the find_optimum policy in the simd case.
210  this->initialise_find_optimum_policy(sequence1_range,
211  sequence2_range,
212  this->scoring_scheme.padding_match_score());
213 
214  // Convert batch of sequences to sequence of simd vectors.
215  auto simd_sequences1 = convert_batch_of_sequences_to_simd_vector(sequence1_range);
216  auto simd_sequences2 = convert_batch_of_sequences_to_simd_vector(sequence2_range);
217 
218  max_size_in_collection = std::pair{simd_sequences1.size(), simd_sequences2.size()};
219  // Reset the alignment state's optimum between executions of the alignment algorithm.
220  this->alignment_state.reset_optimum();
221 
222  compute_matrix(simd_sequences1, simd_sequences2);
223 
224  make_alignment_result(indexed_sequence_pairs, callback);
225  }
227 
228 private:
242  template <typename sequence_range_t>
243  constexpr auto convert_batch_of_sequences_to_simd_vector(sequence_range_t & sequences)
244  {
245  assert(static_cast<size_t>(std::ranges::distance(sequences)) <= traits_t::alignments_per_vector);
246 
247  using simd_score_t = typename traits_t::score_type;
248 
249  std::vector<simd_score_t, aligned_allocator<simd_score_t, alignof(simd_score_t)>> simd_sequence{};
250 
251  for (auto && simd_vector_chunk : sequences | views::to_simd<simd_score_t>(this->scoring_scheme.padding_symbol))
252  for (auto && simd_vector : simd_vector_chunk)
253  simd_sequence.push_back(std::move(simd_vector));
254 
255  return simd_sequence;
256  }
257 
275  template <std::ranges::forward_range sequence1_t,
276  std::ranges::forward_range sequence2_t,
277  typename callback_t>
278  constexpr void compute_single_pair(size_t const idx,
279  sequence1_t && sequence1,
280  sequence2_t && sequence2,
281  callback_t & callback)
282  {
283  assert(cfg_ptr != nullptr);
284 
285  if constexpr (traits_t::is_debug)
286  initialise_debug_matrices(sequence1, sequence2);
287 
288  // Reset the alignment state's optimum between executions of the alignment algorithm.
289  this->alignment_state.reset_optimum();
290 
291  if constexpr (traits_t::is_banded)
292  {
293  using seqan3::get;
294  // Get the band and check if band configuration is valid.
295  auto const & band = get<align_cfg::band_fixed_size>(*cfg_ptr);
296  check_valid_band_parameter(sequence1, sequence2, band);
297  auto && [subsequence1, subsequence2] = this->slice_sequences(sequence1, sequence2, band);
298  // It would be great to use this interface here instead
299  compute_matrix(subsequence1, subsequence2, band);
300  make_alignment_result(idx, subsequence1, subsequence2, callback);
301  }
302  else
303  {
304  compute_matrix(sequence1, sequence2);
305  make_alignment_result(idx, sequence1, sequence2, callback);
306  }
307  }
308 
325  template <typename sequence1_t, typename sequence2_t>
326  constexpr void check_valid_band_parameter(sequence1_t && sequence1,
327  sequence2_t && sequence2,
328  align_cfg::band_fixed_size const & band)
329  {
330  static_assert(config_t::template exists<align_cfg::band_fixed_size>(),
331  "The band configuration is required for the banded alignment algorithm.");
332 
334  static_assert(std::is_signed_v<diff_type>, "Only signed types can be used to test the band parameters.");
335 
336  if (static_cast<diff_type>(band.lower_diagonal) > std::ranges::distance(sequence1))
337  {
338  throw invalid_alignment_configuration
339  {
340  "Invalid band error: The lower diagonal excludes the whole alignment matrix."
341  };
342  }
343 
344  if (static_cast<diff_type>(band.upper_diagonal) < -std::ranges::distance(sequence2))
345  {
346  throw invalid_alignment_configuration
347  {
348  "Invalid band error: The upper diagonal excludes the whole alignment matrix."
349  };
350  }
351  }
352 
365  template <typename sequence1_t, typename sequence2_t>
366  constexpr void initialise_debug_matrices(sequence1_t & sequence1, sequence2_t & sequence2)
367  {
368  size_t rows = std::ranges::distance(sequence2) + 1;
369  size_t cols = std::ranges::distance(sequence1) + 1;
370 
371  score_debug_matrix = score_debug_matrix_t{number_rows{rows}, number_cols{cols}};
372  trace_debug_matrix = trace_debug_matrix_t{number_rows{rows}, number_cols{cols}};
373  }
374 
382  template <typename sequence1_t, typename sequence2_t>
383  void compute_matrix(sequence1_t & sequence1, sequence2_t & sequence2)
385  requires (!traits_t::is_banded)
387  {
388  // ----------------------------------------------------------------------------
389  // Initialisation phase: allocate memory and initialise first column.
390  // ----------------------------------------------------------------------------
391 
392  this->allocate_matrix(sequence1, sequence2);
393  initialise_first_alignment_column(sequence2);
394 
395  // ----------------------------------------------------------------------------
396  // Recursion phase: compute column-wise the alignment matrix.
397  // ----------------------------------------------------------------------------
398 
399  for (auto const & alphabet1 : sequence1)
400  {
401  compute_alignment_column<true>(this->scoring_scheme_profile_column(alphabet1), sequence2);
402  finalise_last_cell_in_column(true);
403  }
404 
405  // ----------------------------------------------------------------------------
406  // Wrap up phase: track score in last column and prepare the alignment result.
407  // ----------------------------------------------------------------------------
408 
409  finalise_alignment();
410  }
411 
413  template <typename sequence1_t, typename sequence2_t>
414  void compute_matrix(sequence1_t & sequence1, sequence2_t & sequence2, align_cfg::band_fixed_size const & band)
416  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>
596  requires (!traits_t::is_vectorised)
598  constexpr void make_alignment_result([[maybe_unused]] index_t const idx,
599  [[maybe_unused]] sequence1_t & sequence1,
600  [[maybe_unused]] sequence2_t & sequence2,
601  callback_t & callback)
602  {
603  using result_value_t = typename alignment_result_value_type_accessor<alignment_result_t>::type;
604 
605  // ----------------------------------------------------------------------------
606  // Build the alignment result
607  // ----------------------------------------------------------------------------
608 
609  static_assert(seqan3::detail::alignment_configuration_traits<config_t>::has_output_configuration,
610  "The configuration must contain at least one align_cfg::output_* element.");
611 
612  result_value_t res{};
613 
614  if constexpr (traits_t::output_sequence1_id)
615  res.sequence1_id = idx;
616 
617  if constexpr (traits_t::output_sequence2_id)
618  res.sequence2_id = idx;
619 
620  // Choose what needs to be computed.
621  if constexpr (traits_t::compute_score)
622  res.score = this->alignment_state.optimum.score;
623 
624  if constexpr (traits_t::compute_end_positions)
625  {
626  using alignment_coordinate_t = detail::advanceable_alignment_coordinate<>;
627  res.end_positions = alignment_coordinate_t{column_index_type{this->alignment_state.optimum.column_index},
628  row_index_type{this->alignment_state.optimum.row_index}};
629  // At some point this needs to be refactored so that it is not necessary to adapt the coordinate.
630  if constexpr (traits_t::is_banded)
631  res.end_positions.second += res.end_positions.first - this->trace_matrix.band_col_index;
632  }
633 
634  if constexpr (traits_t::compute_begin_positions)
635  {
636  // Get a aligned sequence builder for banded or un-banded case.
637  aligned_sequence_builder builder{sequence1, sequence2};
638 
639  detail::matrix_coordinate const optimum_coordinate
640  {
641  detail::row_index_type{this->alignment_state.optimum.row_index},
642  detail::column_index_type{this->alignment_state.optimum.column_index}
643  };
644  auto trace_res = builder(this->trace_matrix.trace_path(optimum_coordinate));
645  res.begin_positions.first = trace_res.first_sequence_slice_positions.first;
646  res.begin_positions.second = trace_res.second_sequence_slice_positions.first;
647 
648  if constexpr (traits_t::compute_sequence_alignment)
649  res.alignment = std::move(trace_res.alignment);
650  }
651 
652  // Store the matrices in debug mode.
653  if constexpr (traits_t::is_debug)
654  {
655  res.score_debug_matrix = std::move(score_debug_matrix);
656  if constexpr (traits_t::compute_sequence_alignment) // compute alignment
657  res.trace_debug_matrix = std::move(trace_debug_matrix);
658  }
659 
660  callback(std::move(res));
661  }
662 
688  template <typename indexed_sequence_pair_range_t, typename callback_t>
690  requires traits_t::is_vectorised
692  constexpr auto make_alignment_result(indexed_sequence_pair_range_t && index_sequence_pairs,
693  callback_t & callback)
694  {
695  using result_value_t = typename alignment_result_value_type_accessor<alignment_result_t>::type;
696 
697  size_t simd_index = 0;
698  for (auto && [sequence_pairs, alignment_index] : index_sequence_pairs)
699  {
700  (void) sequence_pairs;
701  result_value_t res{};
702 
703  if constexpr (traits_t::output_sequence1_id)
704  res.sequence1_id = alignment_index;
705 
706  if constexpr (traits_t::output_sequence2_id)
707  res.sequence2_id = alignment_index;
708 
709  if constexpr (traits_t::compute_score)
710  res.score = this->alignment_state.optimum.score[simd_index]; // Just take this
711 
712  if constexpr (traits_t::compute_end_positions)
713  {
714  res.end_positions.first = this->alignment_state.optimum.column_index[simd_index];
715  res.end_positions.second = this->alignment_state.optimum.row_index[simd_index];
716  }
717 
718  callback(std::move(res));
719  ++simd_index;
720  }
721  }
722 
731  void dump_alignment_column()
732  {
733  using std::get;
734 
735  auto column = this->current_alignment_column();
736 
737  auto coord = get<1>(column.front()).coordinate;
738  if constexpr (traits_t::is_banded)
739  coord.second += coord.first - this->score_matrix.band_col_index;
740 
741  matrix_offset offset{row_index_type{static_cast<std::ptrdiff_t>(coord.second)},
742  column_index_type{static_cast<std::ptrdiff_t>(coord.first)}};
743 
744  std::ranges::copy(column | std::views::transform([] (auto const & tpl)
745  {
746  using std::get;
747  return get<0>(tpl).current;
748  }), score_debug_matrix.begin() + offset);
749 
750  // if traceback is enabled.
751  if constexpr (traits_t::compute_sequence_alignment)
752  {
753  auto trace_matrix_it = trace_debug_matrix.begin() + offset;
754  std::ranges::copy(column | std::views::transform([] (auto const & tpl)
755  {
756  using std::get;
757  return get<1>(tpl).current;
758  }), trace_debug_matrix.begin() + offset);
759  }
760  }
761 
763  std::shared_ptr<config_t> cfg_ptr{};
765  alignment_column_t alignment_column{};
767  alignment_column_iterator_t alignment_column_it{};
769  score_debug_matrix_t score_debug_matrix{};
771  trace_debug_matrix_t trace_debug_matrix{};
773  std::pair<size_t, size_t> max_size_in_collection{};
774 };
775 
776 } // namespace seqan3::detail
Provides seqan3::detail::align_config_band.
Provides seqan3::align_cfg::scoring_scheme.
Provides seqan3::detail::align_result_selector.
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)
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: traits.hpp:368
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: traits.hpp:388
decltype(detail::transform< trait_t >(list_t{})) transform
Apply a transformation trait to every type in the list and return a seqan3::type_list of the results.
Definition: traits.hpp:471
constexpr auto get
A view calling get on each element in a range.
Definition: elements.hpp:114
constexpr auto slice
A view adaptor that returns a half-open interval on the underlying range.
Definition: slice.hpp:189
auto const move
A view that turns lvalue-references into rvalue-references.
Definition: move.hpp:74
Provides C++20 additions to the <iterator> header.
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:429
T push_back(T... args)
Adaptations of concepts from the Ranges TS.
[DEPRECATED] Provides seqan3::views::take.
Provides seqan3::detail::to_simd view.
Provides the declaration of seqan3::detail::trace_directions.
Provides seqan3::aligned_allocator.
Provides seqan3::simd::simd_concept.
Provides seqan3::simd::simd_type.
Provides seqan3::simd::simd_traits.