Loading...
Searching...
No Matches
format_vienna.hpp
Go to the documentation of this file.
// SPDX-FileCopyrightText: 2006-2025 Knut Reinert & Freie Universität Berlin
// SPDX-FileCopyrightText: 2016-2025 Knut Reinert & MPI für molekulare Genetik
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <algorithm>
#include <cstdio>
#include <iterator>
#include <ranges>
#include <stack>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include <seqan3/alphabet/adaptation/char.hpp>
#include <seqan3/alphabet/concept.hpp>
#include <seqan3/alphabet/structure/wuss.hpp>
#include <seqan3/alphabet/views/char_to.hpp>
#include <seqan3/alphabet/views/to_char.hpp>
#include <seqan3/core/range/detail/misc.hpp>
#include <seqan3/core/range/type_traits.hpp>
#include <seqan3/io/detail/misc.hpp>
#include <seqan3/io/structure_file/detail.hpp>
#include <seqan3/io/structure_file/input_format_concept.hpp>
#include <seqan3/io/structure_file/input_options.hpp>
#include <seqan3/io/structure_file/output_format_concept.hpp>
#include <seqan3/io/structure_file/output_options.hpp>
#include <seqan3/io/views/detail/istreambuf_view.hpp>
#include <seqan3/io/views/detail/take_line_view.hpp>
#include <seqan3/io/views/detail/take_until_view.hpp>
#include <seqan3/utility/char_operations/predicate.hpp>
#include <seqan3/utility/detail/type_name_as_string.hpp>
#include <seqan3/utility/range/to.hpp>
namespace seqan3
{
class format_vienna
{
public:
format_vienna() noexcept = default;
format_vienna(format_vienna const &) noexcept = default;
format_vienna & operator=(format_vienna const &) noexcept = default;
format_vienna(format_vienna &&) noexcept = default;
format_vienna & operator=(format_vienna &&) noexcept = default;
~format_vienna() noexcept = default;
static inline std::vector<std::string> file_extensions{{"dbn"}, {"fasta"}, {"fa"}};
protected:
template <typename stream_type, // constraints checked by file
typename seq_legal_alph_type,
bool structured_seq_combined,
typename seq_type, // other constraints checked inside function
typename id_type,
typename bpp_type,
typename structure_type,
typename energy_type,
typename react_type,
typename comment_type,
typename offset_type>
void
read_structure_record(stream_type & stream,
structure_file_input_options<seq_legal_alph_type, structured_seq_combined> const & options,
seq_type & seq,
id_type & id,
bpp_type & bpp,
structure_type & structure,
energy_type & energy,
react_type & SEQAN3_DOXYGEN_ONLY(react),
react_type & SEQAN3_DOXYGEN_ONLY(react_err),
comment_type & SEQAN3_DOXYGEN_ONLY(comment),
offset_type & SEQAN3_DOXYGEN_ONLY(offset))
{
auto stream_view = detail::istreambuf(stream);
// READ ID (if present)
constexpr auto is_id = is_char<'>'>;
if (is_id(*begin(stream_view)))
{
if constexpr (!detail::decays_to_ignore_v<id_type>)
{
if (options.truncate_ids)
{
std::ranges::copy(stream_view | std::views::drop_while(is_id || is_blank) // skip leading >
| detail::take_until_or_throw(is_cntrl || is_blank)
| views::char_to<std::ranges::range_value_t<id_type>>,
std::back_inserter(id));
detail::consume(stream_view | detail::take_line_or_throw);
}
else
{
std::ranges::copy(stream_view | std::views::drop_while(is_id || is_blank) // skip leading >
| detail::take_line_or_throw
| views::char_to<std::ranges::range_value_t<id_type>>,
std::back_inserter(id));
}
}
else
{
detail::consume(stream_view | detail::take_line_or_throw);
}
}
else if constexpr (!detail::decays_to_ignore_v<id_type>)
{
constexpr auto is_legal_seq = char_is_valid_for<seq_legal_alph_type>;
if (!is_legal_seq(*begin(stream_view))) // if neither id nor seq found: throw
{
throw parse_error{std::string{"Expected to be on beginning of ID or sequence, but "} + is_id.msg
+ " and char_is_valid_for<" + detail::type_name_as_string<seq_legal_alph_type>
+ ">" + " evaluated to false on " + detail::make_printable(*begin(stream_view))};
}
}
// READ SEQUENCE
if constexpr (!detail::decays_to_ignore_v<seq_type>)
{
constexpr auto is_legal_seq = char_is_valid_for<seq_legal_alph_type>;
std::ranges::copy(
stream_view | detail::take_line_or_throw // until end of line
| std::views::filter(!(is_space || is_digit)) // ignore whitespace and numbers
| std::views::transform(
[is_legal_seq](char const c)
{
if (!is_legal_seq(c)) // enforce legal alphabet
{
throw parse_error{std::string{"Encountered an unexpected letter: "}
+ "char_is_valid_for<"
+ detail::type_name_as_string<seq_legal_alph_type>
+ "> evaluated to false on " + detail::make_printable(c)};
}
return c;
})
| views::char_to<std::ranges::range_value_t<seq_type>>, // convert to actual target alphabet
std::back_inserter(seq));
}
else
{
detail::consume(stream_view | detail::take_line_or_throw);
}
// READ STRUCTURE (if present)
[[maybe_unused]] int64_t structure_length{};
if constexpr (!detail::decays_to_ignore_v<structure_type>)
{
if constexpr (structured_seq_combined)
{
assert(std::addressof(seq) == std::addressof(structure));
using alph_type = typename std::ranges::range_value_t<structure_type>::structure_alphabet_type;
// We need the structure_length parameter to count the length of the structure while reading
// because we cannot infer it from the (already resized) structure_seq object.
auto range = read_structure<alph_type>(stream_view);
// Use std::views::take to avoid going out of bounds if the structure is longer than the sequence.
auto res = std::ranges::copy(range | std::views::take(std::ranges::distance(seq)),
std::ranges::begin(structure));
structure_length = std::ranges::distance(std::ranges::begin(structure), res.out);
// If the structure is longer than the sequence, there are characters left.
// std::ranges::distance will also consume the characters in the stream.
structure_length += std::ranges::distance(range);
if constexpr (!detail::decays_to_ignore_v<bpp_type>)
detail::bpp_from_rna_structure<alph_type>(bpp, structure);
}
else
{
using alph_type = std::ranges::range_value_t<structure_type>;
std::ranges::copy(read_structure<alph_type>(stream_view), std::back_inserter(structure));
structure_length = std::ranges::distance(structure);
if constexpr (!detail::decays_to_ignore_v<bpp_type>)
detail::bpp_from_rna_structure<alph_type>(bpp, structure);
}
}
else if constexpr (!detail::decays_to_ignore_v<bpp_type>)
{
detail::bpp_from_rna_structure<wuss51>(bpp, read_structure<wuss51>(stream_view));
structure_length = std::ranges::distance(bpp);
}
else
{
detail::consume(stream_view | detail::take_until(is_space)); // until whitespace
}
if constexpr (!detail::decays_to_ignore_v<seq_type>
&& !(detail::decays_to_ignore_v<structure_type> && detail::decays_to_ignore_v<bpp_type>))
{
if (std::ranges::distance(seq) != structure_length)
throw parse_error{"Found sequence and associated structure of different length."};
}
// READ ENERGY (if present)
if constexpr (!detail::decays_to_ignore_v<energy_type>)
{
std::string e_str = stream_view | detail::take_line
| std::views::filter(!(is_space || is_char<'('> || is_char<')'>))
| ranges::to<std::string>();
if (!e_str.empty())
{
size_t num_processed;
energy = std::stod(e_str, &num_processed);
if (num_processed != e_str.size()) // [[unlikely]]
{
throw parse_error{std::string{"Failed to parse energy value '"} + e_str + "'."};
}
}
}
else
{
detail::consume(stream_view | detail::take_line);
}
detail::consume(stream_view | detail::take_until(!is_space));
}
template <typename stream_type, // constraints checked by file
typename seq_type, // other constraints checked inside function
typename id_type,
typename bpp_type,
typename structure_type,
typename energy_type,
typename react_type,
typename comment_type,
typename offset_type>
void write_structure_record(stream_type & stream,
structure_file_output_options const & options,
seq_type && seq,
id_type && id,
bpp_type && SEQAN3_DOXYGEN_ONLY(bpp),
structure_type && structure,
energy_type && energy,
react_type && SEQAN3_DOXYGEN_ONLY(react),
react_type && SEQAN3_DOXYGEN_ONLY(react_err),
comment_type && SEQAN3_DOXYGEN_ONLY(comment),
offset_type && SEQAN3_DOXYGEN_ONLY(offset))
{
std::ostreambuf_iterator stream_it{stream};
// WRITE ID (optional)
if constexpr (!detail::decays_to_ignore_v<id_type>)
{
if (!std::ranges::empty(id))
{
stream_it = '>';
stream_it = ' ';
std::ranges::copy(id, stream_it);
detail::write_eol(stream_it, options.add_carriage_return);
}
}
// WRITE SEQUENCE
if constexpr (!detail::decays_to_ignore_v<seq_type>)
{
if (std::ranges::empty(seq)) //[[unlikely]]
throw std::runtime_error{"The SEQ field may not be empty when writing Vienna files."};
std::ranges::copy(seq | views::to_char, stream_it);
detail::write_eol(stream_it, options.add_carriage_return);
}
else
{
throw std::logic_error{"The SEQ and STRUCTURED_SEQ fields may not both be set to ignore "
"when writing Vienna files."};
}
// WRITE STRUCTURE (optional)
if constexpr (!detail::decays_to_ignore_v<structure_type>)
{
if (!std::ranges::empty(structure))
std::ranges::copy(structure | views::to_char, stream_it);
// WRITE ENERGY (optional)
if constexpr (!detail::decays_to_ignore_v<energy_type>)
{
if (energy)
{
// TODO(joergi-w) enable the following when std::to_chars is implemented for float types
// auto [endptr, ec] = std::to_chars(str.data(),
// str.data() + str.size(),
// energy,
// std::chars_format::fixed,
// options.precision);
// if (ec == std::errc())
// std::ranges::copy(str.data(), endptr, stream_it);
// else
// throw std::runtime_error{"The energy could not be transformed into a string."};
stream_it = ' ';
stream_it = '(';
std::array<char, 100> str;
int len = std::snprintf(str.data(), 100, "%.*f", options.precision, energy);
if (len < 0 || len >= 100)
throw std::runtime_error{"The energy could not be transformed into a string."};
std::ranges::copy(str.data(), str.data() + len, stream_it);
stream_it = ')';
}
}
detail::write_eol(stream_it, options.add_carriage_return);
}
else if constexpr (!detail::decays_to_ignore_v<energy_type>)
{
throw std::logic_error{"The ENERGY field cannot be written to a Vienna file without providing STRUCTURE."};
}
}
private:
template <typename alph_type, typename stream_view_type>
auto read_structure(stream_view_type & stream_view)
{
constexpr auto is_legal_structure = char_is_valid_for<alph_type>;
return stream_view | detail::take_until(is_space) // until whitespace
| std::views::transform(
[is_legal_structure](char const c)
{
if (!is_legal_structure(c))
{
throw parse_error{std::string{"Encountered an unexpected letter: char_is_valid_for<"}
+ detail::type_name_as_string<alph_type>
+ "> evaluated to false on " + detail::make_printable(c)};
}
return c;
}) // enforce legal alphabet
| views::char_to<alph_type>; // convert to actual target alphabet
}
};
} // namespace seqan3
std::addressof
T addressof(T... args)
algorithm
concept.hpp
Core alphabet concept and free function/type trait wrappers.
std::array
std::back_inserter
T back_inserter(T... args)
std::string
char.hpp
Provides alphabet adaptations for standard char types.
char_to.hpp
Provides seqan3::views::char_to.
seqan3::format_vienna::file_extensions
static std::vector< std::string > file_extensions
The valid file extensions for this format; note that you can modify this value.
Definition format_vienna.hpp:98
seqan3::format_vienna::write_structure_record
void write_structure_record(stream_type &stream, structure_file_output_options const &options, seq_type &&seq, id_type &&id, bpp_type &&bpp, structure_type &&structure, energy_type &&energy, react_type &&react, react_type &&react_err, comment_type &&comment, offset_type &&offset)
Write the given fields to the specified stream.
Definition format_vienna.hpp:275
seqan3::format_vienna::format_vienna
format_vienna() noexcept=default
Defaulted.
seqan3::format_vienna::read_structure_record
void read_structure_record(stream_type &stream, structure_file_input_options< seq_legal_alph_type, structured_seq_combined > const &options, seq_type &seq, id_type &id, bpp_type &bpp, structure_type &structure, energy_type &energy, react_type &react, react_type &react_err, comment_type &comment, offset_type &offset)
Read from the specified stream and back-insert into the given field buffers.
Definition format_vienna.hpp:114
misc.hpp
Provides various utility functions.
type_traits.hpp
Provides various transformation traits used by the range module.
cstdio
std::array::data
T data(T... args)
std::string::empty
T empty(T... args)
std::snprintf
T snprintf(T... args)
seqan3::views::to_char
auto const to_char
A view that calls seqan3::to_char() on each element in the input range.
Definition to_char.hpp:60
seqan3::views::char_to
auto const char_to
A view over an alphabet, given a range of characters.
Definition char_to.hpp:64
seqan3::char_is_valid_for
constexpr auto char_is_valid_for
Returns whether a character is in the valid set of a seqan3::alphabet (usually implies a bijective ma...
Definition alphabet/concept.hpp:661
seqan3::field::energy
@ energy
Energy of a folded sequence, represented by one float number.
Definition record.hpp:72
seqan3::field::comment
@ comment
Comment field of arbitrary content, usually a string.
Definition record.hpp:75
seqan3::field::structure
@ structure
Fixed interactions, usually a string of structure alphabet characters.
Definition record.hpp:70
seqan3::field::bpp
@ bpp
Base pair probability matrix of interactions, usually a matrix of float numbers.
Definition record.hpp:69
seqan3::field::react
@ react
Reactivity values of the sequence characters given in a vector of float numbers.
Definition record.hpp:73
seqan3::field::react_err
@ react_err
Reactivity error values given in a vector corresponding to seqan3::field::react.
Definition record.hpp:74
seqan3::field::offset
@ offset
Sequence (seqan3::field::seq) relative start position (0-based), unsigned value.
Definition record.hpp:66
seqan3::field::seq
@ seq
The "sequence", usually a range of nucleotides or amino acids.
Definition record.hpp:62
seqan3::is_blank
constexpr auto is_blank
Checks whether c is a blank character.
Definition predicate.hpp:139
seqan3::is_digit
constexpr auto is_digit
Checks whether c is a digital character.
Definition predicate.hpp:259
seqan3::is_char
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition predicate.hpp:60
seqan3::is_space
constexpr auto is_space
Checks whether c is a space character.
Definition predicate.hpp:122
seqan3::is_cntrl
constexpr auto is_cntrl
Checks whether c is a control character.
Definition predicate.hpp:87
seqan3::ranges::to
seqan::stl::ranges::to to
Converts a range to a container. <dl class="no-api">This entity is not part of the SeqAn API....
Definition to.hpp:23
misc.hpp
Provides various utility functions.
detail.hpp
Helper functions (e.g. conversions) for the structure IO submodule.
istreambuf_view.hpp
Provides seqan3::detail::istreambuf.
iterator
std::logic_error
seqan3
The main SeqAn3 namespace.
Definition aligned_sequence_concept.hpp:26
std
SeqAn specific customisations in the standard namespace.
std::ostreambuf_iterator
predicate.hpp
Provides character predicates for tokenisation.
std::runtime_error
std::string::size
T size(T... args)
stack
std::stod
T stod(T... args)
string
string_view
seqan3::parse_error
Thrown if there is a parse error, such as reading an unexpected character from an input stream.
Definition io/exception.hpp:45
seqan3::structure_file_input_options
The options type defines various option members that influence the behaviour of all or some formats.
Definition structure_file/input_options.hpp:27
seqan3::structure_file_input_options::truncate_ids
bool truncate_ids
Read the ID string only up until the first whitespace character.
Definition structure_file/input_options.hpp:29
seqan3::structure_file_output_options
The options type defines various option members that influence the behaviour of all or some formats.
Definition structure_file/output_options.hpp:23
seqan3::structure_file_output_options::add_carriage_return
bool add_carriage_return
The default plain text line-ending is "\n", but on Windows an additional carriage return is recommend...
Definition structure_file/output_options.hpp:27
seqan3::structure_file_output_options::precision
int precision
The precision for writing floating point types.
Definition structure_file/output_options.hpp:30
input_format_concept.hpp
Provides seqan3::structure_file_input_format.
input_options.hpp
Provides seqan3::structure_file_input_options.
output_format_concept.hpp
Provides seqan3::structure_file_output_format and auxiliary classes.
output_options.hpp
Provides seqan3::structure_file_output_options.
take_line_view.hpp
Provides seqan3::detail::take_line and seqan3::detail::take_line_or_throw.
take_until_view.hpp
Provides seqan3::views::take_until and seqan3::views::take_until_or_throw.
to.hpp
Provides seqan3::ranges::to.
to_char.hpp
Provides seqan3::views::to_char.
type_name_as_string.hpp
Provides traits to inspect some information of a type, for example its name.
type_traits
vector
wuss.hpp
Provides the WUSS format for RNA structure.
Hide me