defmodule Bio.Sequence.RnaDoubleStrand do @behaviour Bio.Sequential alias Bio.Sequence.{Rna, RnaStrand, DnaDoubleStrand} alias Bio.Enum, as: Bnum alias Bio.Sequence.Alphabets.Rna, as: Alpha defstruct top_strand: RnaStrand.new("", length: 0), bottom_strand: RnaStrand.new("", length: 0), complement_offset: 0, label: "", valid?: false, alphabet: nil @impl Bio.Sequential def new(top_strand, opts \\ []) when is_binary(top_strand) do label = Keyword.get(opts, :label, "") offset = Keyword.get(opts, :complement_offset, 0) alphabet = Keyword.get(opts, :alphabet, Alpha.iupac()) top = RnaStrand.new(top_strand, label: nil) given_bottom = Keyword.get(opts, :bottom_strand) cond do given_bottom == nil -> top |> Bnum.slice(offset, top.length) |> Rna.complement(alphabet: alphabet) |> case do {:error, mismatches} -> {:error, mismatches} {:ok, generated_bottom} -> struct!( __MODULE__, %{ top_strand: top, bottom_strand: generated_bottom, complement_offset: offset, label: label, alphabet: alphabet } ) end true -> struct!( __MODULE__, %{ top_strand: top, bottom_strand: RnaStrand.new(given_bottom, label: nil, alphabet: alphabet), complement_offset: offset, label: label, alphabet: alphabet } ) end end @impl Bio.Sequential def converter, do: __MODULE__.Conversions defmodule Conversions do @moduledoc false use Bio.Convertible do def to(DnaDoubleStrand), do: {:ok, &to_dna_double/2, 1} # def to(DnaStrand), do: {:ok, &to_dna/5, 1} end def to_dna_double({:ok, knumeration, data}, _module) do knumeration |> Enum.reduce({"", ""}, fn {top, bottom}, {t, b} -> {t <> dna(top), b <> dna(bottom)} end) |> then(fn {top, bottom} -> DnaDoubleStrand.new(top, bottom_strand: bottom, label: Map.get(data, :label) ) end) end defp dna(base) do case base do "A" -> "A" "U" -> "T" "G" -> "G" "C" -> "C" "a" -> "a" "u" -> "t" "g" -> "g" "c" -> "c" end end end @impl Bio.Sequential def fasta_line(%__MODULE__{top_strand: rna}), do: "#{rna.label}\n#{rna.sequence}\n" end defimpl Bio.Polymeric, for: Bio.Sequence.RnaDoubleStrand do alias Bio.Sequence.RnaDoubleStrand def kmers( %RnaDoubleStrand{ top_strand: top, bottom_strand: bottom, complement_offset: offset, label: label }, k ) do total_span = top.length + abs(offset) spacing = 1..offset |> Enum.reduce([], fn _, acc -> [" " | acc] end) {top, bottom} = cond do offset > 0 -> {Enum.concat(top, spacing), Enum.concat(spacing, bottom)} offset < 0 -> {Enum.concat(spacing, top), Enum.concat(bottom, spacing)} offset == 0 -> {top, bottom} end case rem(total_span, k) do 0 -> {:ok, Enum.zip( top |> Enum.chunk_every(k) |> Enum.map(&Enum.join(&1, "")), bottom |> Enum.chunk_every(k) |> Enum.map(&Enum.join(&1, "")) ), %{label: label, complement_offset: offset}} _ -> {:error, :seq_len_mismatch} end end @doc """ A RnaDoubleStrand validity check A RnaDoubleStrand is only valid when the alphabet matches AND the strands are complementary. """ def valid?( %RnaDoubleStrand{top_strand: top, bottom_strand: bottom} = data, alphabet ) do Bio.Polymeric.valid?(top, alphabet) and Bio.Polymeric.valid?(bottom, alphabet) and complementary?(data, alphabet) end defp complementary?(double_strand, alphabet) do {:ok, kmers, _data} = Bio.Polymeric.kmers(double_strand, 1) kmers |> Enum.map(fn {top, bottom} -> case top do "" -> true _ -> {:ok, check} = Bio.Sequence.Alphabets.Rna.complement(top, alphabet) bottom == check end end) |> Enum.all?() end def validate(%RnaDoubleStrand{} = data, alphabet) do {top_alpha, bottom_alpha} = case alphabet do nil -> {Map.get(data.top_strand, :alphabet), Map.get(data.bottom_strand, :alphabet)} _ -> {alphabet, alphabet} end with {:equal, true} <- {:equal, top_alpha == bottom_alpha}, {:top, {:ok, top}} <- {:top, Bio.Polymeric.validate(data.top_strand, top_alpha)}, {:bottom, {:ok, bottom}} <- {:bottom, Bio.Polymeric.validate(data.bottom_strand, bottom_alpha)} do {:ok, data |> Map.put(:top_strand, top) |> Map.put(:bottom_strand, bottom) |> Map.put(:alphabet, alphabet) |> Map.put(:valid?, true)} else {:equal, false} -> {:error, :mismatch_alpha} {:top, {:error, mismatches}} -> {:error, {:top, mismatches}} {:bottom, {:error, mismatches}} -> {:error, {:bottom, mismatches}} end end end