mrna-design-studio / core /analysis /structure.py
offtargeteffect's picture
Deploy mRNA Design Studio (Docker SDK)
99f834c verified
Raw
History Blame Contribute Delete
2.4 kB
"""
RNA secondary structure prediction via ViennaRNA.
ViennaRNA (RNA package) must be installed:
conda install -c bioconda viennarna
or: pip install ViennaRNA (if wheel available for the platform)
Falls back to a stub when ViennaRNA is not available so the rest of the
app can run without it.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
_VIENNARNA_AVAILABLE = False
try:
import RNA # type: ignore[import-untyped]
_VIENNARNA_AVAILABLE = True
except ImportError:
pass
@dataclass
class StructureResult:
sequence: str
structure: str # dot-bracket notation
mfe: float # minimum free energy (kcal/mol)
ensemble_free_energy: Optional[float] = None
centroid_structure: Optional[str] = None
centroid_distance: Optional[float] = None
@property
def is_stub(self) -> bool:
return self.structure == "" and self.mfe == 0.0
def __repr__(self) -> str:
return (
f"StructureResult(mfe={self.mfe:.2f} kcal/mol, "
f"len={len(self.sequence)})"
)
def predict_structure(sequence: str) -> StructureResult:
"""
Predict the MFE secondary structure of an RNA/DNA sequence.
The sequence is automatically converted from DNA to RNA (T→U) before
passing to ViennaRNA, which expects RNA input.
Returns a StructureResult. If ViennaRNA is not installed, returns a
stub result with empty structure and mfe=0.0.
"""
rna_seq = sequence.upper().replace("T", "U")
if not _VIENNARNA_AVAILABLE:
return StructureResult(
sequence=rna_seq,
structure="",
mfe=0.0,
)
# MFE structure
structure, mfe = RNA.fold(rna_seq) # type: ignore[attr-defined]
# Ensemble / centroid (for longer seqs this is informative)
md = RNA.md() # type: ignore[attr-defined]
fc = RNA.fold_compound(rna_seq, md) # type: ignore[attr-defined]
_, ensemble_free_energy = fc.pf()
centroid_structure, centroid_distance = fc.centroid()
return StructureResult(
sequence=rna_seq,
structure=structure,
mfe=mfe,
ensemble_free_energy=ensemble_free_energy,
centroid_structure=centroid_structure,
centroid_distance=centroid_distance,
)
def is_viennarna_available() -> bool:
return _VIENNARNA_AVAILABLE