| """ |
| 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 |
| """ |
| |
| seq = sequence.assembled_sequence |
| if not seq: |
| return 0.0 |
|
|
| try: |
| import RNA |
| except ImportError: |
| |
| return self._gc_based_fallback(seq) |
|
|
| |
| structure, mfe = RNA.fold(seq) |
|
|
| |
| |
| |
| |
|
|
| seq_length = len(seq) |
| mfe_per_nt = mfe / seq_length if seq_length > 0 else 0 |
|
|
| |
| |
| if mfe_per_nt >= -0.05: |
| |
| score = max(0, 40 + mfe_per_nt * 800) |
| elif mfe_per_nt >= -0.15: |
| |
| score = 40 + ((-0.15 - mfe_per_nt) / 0.10) * 30 |
| elif mfe_per_nt >= -0.30: |
| |
| score = 70 - ((-0.30 - mfe_per_nt) / 0.15) * 30 |
| else: |
| |
| score = max(0, 40 + (mfe_per_nt + 0.30) * 100) |
|
|
| 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 |
|
|
| |
| |
| |
| |
|
|
| if gc_percent < 35: |
| score = 30 + (gc_percent / 35) * 10 |
| elif gc_percent < 50: |
| score = 40 + ((gc_percent - 35) / 15) * 15 |
| elif gc_percent < 65: |
| score = 55 + ((gc_percent - 50) / 15) * 15 |
| else: |
| score = 70 - ((gc_percent - 65) / 35) * 30 |
|
|
| return max(30.0, min(70.0, score)) |
|
|