One HTML file. Open it, and it works — offline, with no server, no CDN and no install. Thermodynamic, statistical and stoichiometric analysis of miRNA–mRNA interactions.
MHV v1 had a defect that invalidated every energy it reported. calcDG_1M scored
miRNA position i against the target base at the opposite end of the window:
const c = mrnaRev[len - 1 - i], d = mrnaRev[len - 2 - i] // mirrored register
const k = `${mirna[i]}${mirna[i + 1]}/${c}${d}`
if (NN_DH[k] !== undefined) { dH += NN_DH[k]; ... } // missing key = freeOn a perfectly complementary 22-mer, 20 of 21 nearest-neighbour lookups missed the table and the function returned ΔG = +1.78 kcal/mol instead of −36.8. Because a missing key silently added zero, every mismatch, bulge and internal loop was free. Benchmarked against ViennaRNA across 500 pairs, v1 achieved r = 0.196.
Three further problems compounded it: four entries in the nearest-neighbour table disagreed with their own mirror image (up to 4.44 kcal/mol in ΔH), the helix initiation term (+4.09 kcal/mol) was missing entirely, and the "Owczarzy salt correction" used neither Owczarzy's coefficients nor his functional form.
And underneath all of that, a design problem no bug fix addresses: v1 counted binding sites without ever asking how many sites a comparable random sequence would contain. A site count is not a finding until you know that number.
v2 is a rewrite. Same idea, correct arithmetic, and a null model at the centre instead of bolted on.
Sites — canonical seed classification (8mer, 7mer-m8, 7mer-A1, 6mer) plus offset-6mer, centered and 3′-compensatory sites, with duplex ΔG from a dynamic-programming intermolecular fold over Turner 2004 parameters.
Null model — the part that matters. Three nulls, because they answer three different questions:
| Null | Controls for | Answers |
|---|---|---|
| Mononucleotide shuffle | base composition | weakest baseline |
| Dinucleotide shuffle | composition + dinucleotide bias | default |
| Synonymous recoding | the encoded protein | of all sequences that could encode this polypeptide, is the one that was chosen unusual? |
The third is the one no other miRNA target tool has, and it is the only null that means anything for a codon-optimised therapeutic mRNA. Results come with a z-score, an empirical p-value with the (r+1)/(n+1) correction, and Benjamini–Hochberg q-values across miRNAs.
Pairing architecture — the part most tools skip. Every site is decomposed into the regions that matter, computed from the recovered duplex rather than assumed: A1 anchor, seed (2–8), central (9–12), 3′ supplementary (13–16), 3′ tail (17+), plus the contiguous 3′ run over positions 11–23 with the affinity tiers measured by Hall & Bartel 2025 — 10 nt for 6mer-equivalent binding, 13 nt for 7mer-m8-equivalent. Central pairing is flagged, not rewarded: AGO2's central cleft cannot accommodate it, so it is destabilising in a loaded RISC even though a free-duplex ΔG says otherwise. Sites are classified as seed-only, seed + 3′-supplementary, compound, 3′-only, centered, nucleation-bulge or slicing-like, and you can filter by region.
Stoichiometry — affinity does not decide whether an exogenous transcript sponges a miRNA; abundance does. The calculator sets your numbers against the measured derepression thresholds from Denzler et al. (Mol Cell 2016): ~0.9–2.8 × 10⁴ added sites per cell in mESCs, > 2.1 × 10⁵ in primary hepatocytes. It will tell you when your competitor is three orders of magnitude short.
3D duplex — idealised Arnott A-RNA geometry (32.7° twist, 2.81 Å rise, 15.8° inclination, 4.4 Å displacement), rendered by a dependency-free canvas viewer and exportable as PDB.
Methods export — a paragraph generated from the parameters actually used, including the RNG seed, ready to paste into a manuscript.
Three equivalent ways in:
- Run it online — same file, served from GitHub Pages.
- Download
dist/index.htmland open it. That is the whole procedure: no install, no server, no network. Works from a USB stick. - Build from source — see below.
To build from source:
npm install
npm test # 52 unit tests
npm run build # -> dist/index.html, a single self-contained file
npm run bench # benchmark stage 1 (see bench/)Full report with figures: bench/results/REPORT.md.
v1's README advertised "mean recall 90.0% vs TargetScan 8.0". That number cannot fail: recall rises monotonically with how many sites a tool emits, it is quoted without precision or false-positive control, and TargetScan is a predictor, not ground truth. These four can each come out badly.
B1 — duplex ΔG vs ViennaRNA RNAduplex, an independent implementation of the
same published model (n = 500: 60 perfect duplexes, ~340 real seed sites, 100
non-site controls):
| MHV v2 | MHV v1 | v1 with only the register fixed | |
|---|---|---|---|
| Pearson r | 0.990 | 0.196 | 0.863 |
| MAE (kcal/mol) | 1.22 | 14.45 | 11.09 |
| Bias | +0.92 | +14.30 | +8.98 |
| Within 2 kcal/mol | 79% | 3% | 4% |
On the 60 perfectly complementary duplexes — where any correct implementation should be exact — v2 gives r = 0.9997, MAE = 0.20 kcal/mol; v1 gives r = 0.056, MAE = 37.1.
B3 — are the p-values honest? Feed the tool sequences drawn from the null and check the p-value distribution. 300 trials × 199 replicates: P(p ≤ α) ≤ α at every α, so the test is valid, running mildly conservative (3.3% at a nominal 5%), which is the correct direction to err in for a tool whose job is to stop people over-reading site counts.
B4 — detection power. Implant k real 8mer sites in 1000 nt of random sequence: at k = 0 the detection rate is 5.0%, exactly the nominal false-positive rate; power crosses 80% at ~5 sites per kilobase. Stated plainly: a handful of seed matches scattered across a few kilobases is not a detectable signal.
B5 — discrimination against experimental data. 22,000 miRNA–target pairs from miRBench, where positives are pairs recovered as chimeric reads inside Argonaute — physical evidence, not another prediction — and negatives are frequency-matched per miRNA:
| ROC AUC | CLASH Hejret2023 | eCLIP Klimentova2022 | eCLIP Manakov2022 |
|---|---|---|---|
| MHV v2 — duplex ΔG | 0.818 | 0.714 | 0.735 |
| MHV v2 — seed class alone | 0.610 | 0.610 | 0.687 |
| ViennaRNA RNAduplex (reference) | 0.806 | 0.707 | 0.721 |
| MHV v1 — duplex ΔG | 0.530 | 0.504 | 0.504 |
v1 was at chance. Its energy function carried essentially no information about whether an interaction happens; every ranking it produced was ordering noise.
The same benchmark surfaces a limit that constrains how any seed-based tool should be read: a canonical seed site is present in only 21–39% of real AGO-captured interactions, against 2.6–4.5% of negatives. Roughly 10× enrichment, but low sensitivity — finding a canonical site means something; finding none means very little.
B6 — architecture beyond the seed. Adding the region decomposition and the contiguous 3′ run to the score gives the best result in the suite:
| ROC AUC | CLASH Hejret2023 | eCLIP Klimentova2022 |
|---|---|---|
| Canonical seed class only | 0.612 | 0.677 |
| Contiguous 3′ run alone | 0.673 | 0.637 |
| Duplex ΔG alone | 0.818 | 0.714 |
| ΔG + architecture | 0.831 | 0.730 |
3′-driven sites are genuinely rare — a ≥10 nt 3′ run appears in 3.2% of positives against 0.6% of negatives — which matches the ~1–1.5% frequency Hall & Bartel report. Highly specific, low yield, and MHV says so on the site itself.
ViennaRNA is used only as a reference standard in the benchmark. It is not bundled, not imported by the tool, and not needed to build or run it.
A significant enrichment means the sequence carries more seed matches than a matched random sequence. It does not establish binding, occupancy, or a biological effect.
- Binding needs AGO CLIP/CLASH or a reporter assay with a seed-mutant control.
- Effect needs the stoichiometry, plus a functional readout — and note that qPCR and small RNA-seq measure miRNA abundance, which sponging need not change.
- Free-duplex ΔG is not Argonaute-loaded binding energy. AGO reshapes seed thermodynamics substantially, and no sequence-level tool models that.
The Methods tab states every one of these limits, and the omissions in the thermodynamic and accessibility models, in the text it generates for you.
dist/index.html the tool — one self-contained file, this is the deliverable
index.html dev entry point (Vite)
src/core/
params.ts Turner 2004 nearest-neighbour tables + loop parameters
thermo.ts intermolecular duplex MFE (dynamic program)
seed.ts seed classification, single-anchor coordinates
fold.ts Zuker fold -> dG_open accessibility
shuffle.ts mono / dinucleotide / synonymous null models
codon.ts genetic code, human codon usage, CAI
stats.ts empirical p, Benjamini-Hochberg, AUC, seeded PRNG
cerna.ts sponge stoichiometry vs Denzler 2016 thresholds
scan.ts site scanning, seed-family collapsing
runner.ts worker + main-thread fallback
structure3d.ts idealised A-RNA builder, PDB export
src/ui/
styles.css design tokens, light and dark
charts.ts null histogram, site track, titration curve
viewer3d.ts dependency-free canvas molecular viewer
src/data/mirbase.ts generated: 5,826 mature sequences
src/workers/ null-model scan worker
src/main.ts application
test/ 52 unit tests (thermo, core)
bench/
benchmark.ts stage 1: pairs, v1 regression, calibration, power
mhv1.ts v1 energy function, verbatim from the released file
vienna.py stage 2: ViennaRNA reference (optional, not a dependency)
report.py stage 3: statistics, figures, REPORT.md
results/ benchmark report and figures
docs/USAGE.md user guide
docs/METHODS.md full methods specification and limits
legacy/ MHV v1 as released, kept for provenance
.github/workflows/ ci.yml (typecheck, test, build, no-external-refs check)
pages.yml (build and deploy to GitHub Pages)
Two invariants are enforced by construction rather than by care, because v1 shows what care alone produces:
- The nearest-neighbour table declares half the stacks and generates the mirror images programmatically. A stack and its 180° rotation are the same physical object; v1 typed both halves and four disagreed.
- The duplex partner in the 3D builder is derived by the two-fold rotation about the pseudo-dyad, not typed out separately.
A test asserts each invariant.
The null-model runner tries a Web Worker and falls back to a chunked main-thread
run. This is not defensive padding: Chromium refuses to start a module Worker
from a blob: URL under a file:// origin, so on the tool's primary use case —
double-click the downloaded file — the worker constructs successfully and then
fails asynchronously. Without the fallback the progress bar sits at zero forever.
| Source | Version | Use |
|---|---|---|
| miRBase | 22.1 | bundled mature sequences: 5,826 across 4 organisms (hsa 2,656 · mmu 1,978 · rno 764 · ath 428) |
| Xia et al., Biochemistry 1998;37:14719 | — | Watson–Crick nearest-neighbour parameters |
| Turner 2004 / NNDB | 2004 | G:U stacks, loop initiation, helix initiation, terminal AU |
| Denzler et al., Mol Cell 2016;64:565 | — | measured ceRNA derepression thresholds |
| Grimson et al., Mol Cell 2007;27:91 | — | cooperativity spacing |
| Arnott et al. 1972; Saenger 1984 | — | A-RNA helical parameters |
The v1 README claimed "6,169 sequences across 5 organisms (hsa/mmu/rno/ath/stu)". The bundle actually held 5,826 across 4, with no
stu. The counts above are now generated from the data, and a test asserts them, so the documentation cannot drift again.
Zero runtime dependencies. The thermodynamics, folding, statistics, charts and 3D renderer are all original. v1 loaded 3Dmol.js from a CDN while claiming to work without a network connection; v2 does not load anything.
The manuscript describing this software, its validation suite and the benchmark
results summarised above is in paper/, together with supplementary
information, figures at 300 dpi, benchmark tables in CSV and the Zenodo deposit
metadata.
Kukier P, de Norimont M. miRNA Hybrid Viewer 2: pairing architecture, calibrated null models and stoichiometric context for miRNA-mRNA interaction analysis in a single offline HTML file. Preprint. 2026. https://github.com/cukierrro/MHV
MIT — see LICENSE. Third-party data attribution in
THIRD_PARTY.md.