mrna-design-studio / models /rna_structure_scorer.py
offtargeteffect's picture
Deploy mRNA Design Studio (Docker SDK)
99f834c verified
Raw
History Blame Contribute Delete
4.31 kB
"""
RNAstructure MFE Scorer — Secondary structure prediction model.
This model uses ViennaRNA to predict the minimum free energy (MFE) of the
mRNA secondary structure. Lower MFE indicates more stable secondary structures.
Score convention: Negative MFE values are normalized to a 0-100 scale where
higher scores indicate more favorable (more stable) structures.
"""
from __future__ import annotations
from typing import Any, Dict, Optional
from models.base import ScoringModel
from core.models.sequence import mRNASequence
class RNAStructureMFEScorer(ScoringModel):
"""
Scores mRNA sequences based on predicted secondary structure MFE.
Uses ViennaRNA RNAfold to compute minimum free energy. More negative
MFE values indicate stronger secondary structure formation, which can
affect translation efficiency and mRNA stability.
Score scale:
- 0-40: Weak/unstable secondary structure (may be too unstructured)
- 40-70: Moderate secondary structure (good balance)
- 70-100: Strong secondary structure (may inhibit translation)
"""
@property
def name(self) -> str:
return "RNAstructure MFE"
@property
def description(self) -> str:
return (
"Predicts minimum free energy (MFE) of mRNA secondary structure "
"using ViennaRNA. Scores from 0-100 where moderate values (40-70) "
"indicate optimal structure balance for translation efficiency."
)
@property
def version(self) -> str:
return "1.0"
def score(self, sequence: mRNASequence, metadata: Optional[Dict[str, Any]] = None) -> float:
"""
Calculate MFE-based structure score.
Returns:
float: Score from 0-100, where 40-70 is optimal range
If ViennaRNA is not available, returns GC-based proxy score
"""
# Get assembled sequence
seq = sequence.assembled_sequence
if not seq:
return 0.0
try:
import RNA # ViennaRNA Python bindings
except ImportError:
# Fallback: Use GC content as a proxy for structure stability
return self._gc_based_fallback(seq)
# Calculate MFE using ViennaRNA
structure, mfe = RNA.fold(seq)
# Normalize MFE to 0-100 scale
# Typical MFE range for mRNA: -200 to 0 kcal/mol
# More negative = stronger structure
# Target range: -100 to -50 kcal/mol for optimal balance
seq_length = len(seq)
mfe_per_nt = mfe / seq_length if seq_length > 0 else 0
# Normalize based on MFE per nucleotide
# Optimal range: -0.3 to -0.15 kcal/mol per nt
if mfe_per_nt >= -0.05:
# Too unstable
score = max(0, 40 + mfe_per_nt * 800) # 0-40 range
elif mfe_per_nt >= -0.15:
# Optimal lower bound
score = 40 + ((-0.15 - mfe_per_nt) / 0.10) * 30 # 40-70 range
elif mfe_per_nt >= -0.30:
# Optimal upper bound
score = 70 - ((-0.30 - mfe_per_nt) / 0.15) * 30 # 70-40 range
else:
# Too stable (may inhibit translation)
score = max(0, 40 + (mfe_per_nt + 0.30) * 100) # 40-0 range
return max(0.0, min(100.0, score))
def _gc_based_fallback(self, seq: str) -> float:
"""
GC-content based proxy score when ViennaRNA is unavailable.
GC content correlates with secondary structure stability:
- Higher GC = more stable structures (stronger base stacking)
- Optimal GC for mRNA: 40-60%
"""
gc_count = seq.count('G') + seq.count('C')
gc_percent = (gc_count / len(seq)) * 100 if len(seq) > 0 else 50
# Map GC% to structure stability score
# 30-40% GC = weak structure (score ~40)
# 50-60% GC = moderate structure (score ~55)
# 70%+ GC = strong structure (score ~70)
if gc_percent < 35:
score = 30 + (gc_percent / 35) * 10 # 30-40
elif gc_percent < 50:
score = 40 + ((gc_percent - 35) / 15) * 15 # 40-55
elif gc_percent < 65:
score = 55 + ((gc_percent - 50) / 15) * 15 # 55-70
else:
score = 70 - ((gc_percent - 65) / 35) * 30 # 70-40
return max(30.0, min(70.0, score))