Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/build-wasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ name: WebAssembly and web interface

on:
pull_request:
branches: [master]
push:
branches: [master]
workflow_dispatch:
Expand Down Expand Up @@ -36,7 +35,9 @@ jobs:
- name: Cross-compile dependencies and IntaRNA
run: bash scripts/wasm/build.sh
- name: Check real WASM predictions against the native reference
run: node scripts/wasm/smoke.mjs
run: |
node scripts/wasm/smoke.mjs
node scripts/wasm/smoke-protocol.mjs
- name: Stage the static site and notices
run: bash scripts/wasm/stage.sh
- name: Install browser test tools
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [master]
pull_request:
branches: [master]

defaults:
run:
Expand Down
32 changes: 32 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@

## Interface and handling

- extend the browser interface with vaRRI styling, prediction/evaluation tasks,
multi-FASTA, start indices, CLI parameter groups, selectable CSV columns and
additional output previews/downloads; label pair-relative differences mfe Δ
and display actual compiled versions (PR #287 review)

- display sortable/filterable browser results and native CSV downloads;
publish the tested interface alongside existing Pages documentation (#286)

Expand Down Expand Up @@ -75,6 +80,10 @@

## Technical changes and Optimizations

- annotate inherited virtual methods/destructors with override, preserve compiled
WASM asset provenance through staging, and expand protocol/browser validation
for the PR #287 review

- cross-compile IntaRNA and pinned dependencies to WebAssembly (#286, step 1)
- verify browser predictions against native accessibility/energy references and
exercise real WASM cancellation and nested Pages paths in Chromium/Firefox
Expand Down Expand Up @@ -232,6 +241,29 @@ energies, restricted partition sums, and trackers.
################################################################################
################################################################################

261021 Alexander Mitrofanov
* src/IntaRNA/*.h
* Mark 194 inherited virtual methods and 45 derived destructors override;
keep algorithms, interfaces and third-party headers unchanged.
* webinterface/{options,columns,protocol,app,results,intarna-worker}.js
+ Add CLI-based parameter groups and validation, multi-FASTA prediction,
signed per-strand start indices and IntaRNAeval structure evaluation.
+ Provide all CSV columns, table visibility controls, pair-relative mfe Δ,
local SHAPE/accessibility data and previews/downloads of native extra files.
* webinterface/index.html, styles.css, assets/*
* Follow vaRRI main-v2 colors and institutional header; keep one parameter
group open at a time and adapt controls/defaults to the selected task.
* scripts/wasm/{build,stage}.sh, write-build-info.mjs
+ Record actual compiled engine/library versions and asset hashes in text/JSON
at build time; preserve/check provenance during staging and show it in UI.
* Reject inconsistent missing overrides in the Emscripten build.
* webinterface/tests/*, scripts/wasm/{smoke-protocol.mjs,test-browser.py}
+ Exercise task validation/defaults, real WASM evaluation/prediction, multiple
records/coordinates, imported data, columns/reports and worker lifecycle.
* .github/workflows/{build,build-wasm}.yml, README.md, webinterface/docs/*
* Run WASM/browser checks on dependent PRs and document revised behavior,
provenance, native option replacements and browser limits (PR #287 review).

261020 Alexander Mitrofanov
* scripts/wasm/*, .github/workflows/build-wasm.yml, webinterface/app.js
+ Execute dependency API probes and test structured accessibility costs,
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
**Efficient RNA-RNA interaction prediction incorporating accessibility and
seeding of interaction sites**

Predict short RNA pairs locally with the [browser interface](https://backofenlab.github.io/IntaRNA/webinterface/).
See the [WebAssembly build instructions](docs/step1-wasm-compilation.md) and
Predict short RNA pairs or evaluate supplied structures locally with the [browser interface](https://backofenlab.github.io/IntaRNA/webinterface/).
See the [WebAssembly build instructions](webinterface/docs/step1-wasm-compilation.md) and
[interface/deployment notes](webinterface/docs/step4-results-and-deployment.md).


Expand Down
5 changes: 4 additions & 1 deletion scripts/wasm/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export VRNA_LIBS="-L$prefix/lib -lRNA -lm"
(cd "$repo" && bash autotools-init.sh)
mkdir -p "$build/intarna"
(cd "$build/intarna"
CPPFLAGS="-I$build/intarna/src -I$repo/src $CPPFLAGS" emconfigure "$repo/configure" --host=wasm32-unknown-emscripten \
CXXFLAGS="$CXXFLAGS -Werror=inconsistent-missing-override" CPPFLAGS="-I$build/intarna/src -I$repo/src $CPPFLAGS" emconfigure "$repo/configure" --host=wasm32-unknown-emscripten \
--disable-multithreading --disable-log-coloring --with-mdspan=kokkos \
--with-vrna="$prefix" --with-boost="$prefix" \
--with-boost-libdir="$prefix/lib" --with-zlib="$prefix"
Expand All @@ -99,4 +99,7 @@ mkdir -p "$build/intarna"
cp src/bin/IntaRNA.wasm "$build/site/wasm/intarna.wasm")
# ES modules also work in Node smoke tests without changing package scope.
printf '{"type":"module"}\n' > "$build/site/wasm/package.json"
node "$repo/scripts/wasm/write-build-info.mjs" "$repo" "$build/site" \
"$(emcc -dumpversion)" "$(git -C "$repo" rev-parse HEAD)" \
"$(git -C "$repo" status --porcelain --untracked-files=no)"
echo "WASM assets: $build/site/wasm"
47 changes: 47 additions & 0 deletions scripts/wasm/smoke-protocol.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Integration coverage of browser argument translation against the real CLI.
import assert from 'node:assert/strict';
import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { DEFAULTS, EVALUATION_EXAMPLE, prepareRequest, makeArguments } from '../../webinterface/protocol.js';
import { CSV_CATALOG } from '../../webinterface/columns.js';
import { parseResults } from '../../webinterface/results.js';
const asset = resolve(process.argv[2] || 'build-wasm/site/wasm/intarna.js');
const { default: createIntaRNA } = await import(pathToFileURL(asset));
async function run(input, format = 'C') {
const request = prepareRequest({ ...DEFAULTS, ...input });
const diagnostics = [];
const module = await createIntaRNA({ noInitialRun: true, locateFile: () => new URL('./intarna.wasm', pathToFileURL(asset)).href, print: (line) => diagnostics.push(line), printErr: (line) => diagnostics.push(line) });
module.FS.writeFile('/query.fa', request.query.fasta);
module.FS.writeFile('/target.fa', request.target.fasta);
module.FS.mkdir('/output');
for (const [key, value] of Object.entries(request.parameters)) if (/^(q|t)(Shape|AccFile)$/.test(key) && value) module.FS.writeFile(`/${key}.txt`, value + '\n');
const args = makeArguments(request, format);
assert.equal(module.callMain(args), 0, `${args.join(' ')}\n${diagnostics.join('\n')}`);
const text = module.FS.readFile(format === 'C' ? '/results.csv' : '/report.txt', { encoding: 'utf8' });
return { rows: format === 'C' ? parseResults(text) : [], text, fs: module.FS };
}
const defaults = await run({ parameters: { outNumber: 10 } });
assert.equal(defaults.rows.length, 10);
assert.equal(defaults.rows[0].E, -21.4);
const shifted = await run({ qIdxPos0: -3, tIdxPos0: -5 });
assert.deepEqual(['start1', 'end1', 'start2', 'end2'].map((key) => shifted.rows[0][key]), [-5, 5, -3, 7]);
const multi = await run({ query: '>q1\nGGGAGGAGGG\n>q2\nGGGAGGAGGG', target: '>t1\nCCCUCCUCCC\n>t2\nCCCUCCUCCC', outputs: ['qMinE', 'tMinE'] });
assert.equal(multi.rows.length, 4);
assert.ok(multi.rows.every((row) => row.deltaE === 0));
assert.equal(multi.fs.readdir('/output').filter((name) => name.endsWith('.csv')).length, 8);
const pairwise = await run({ query: '>q1\nGGGAGGAGGG\n>q2\nGGGAGGAGGG', target: '>t1\nCCCUCCUCCC\n>t2\nCCCUCCUCCC', parameters: { outPairwise: true } });
assert.equal(pairwise.rows.length, 2);
const evaluation = await run({ task: 'evaluation', rri: EVALUATION_EXAMPLE + ':1|||||||||&2|||||||||', outputs: ['qAcc', 'tPu'], csvColumns: CSV_CATALOG.map((c) => c.key) });
assert.equal(evaluation.rows.length, 2);
assert.equal(evaluation.rows[0].E, -21.4);
assert.equal(evaluation.rows[0].seedE, 'NAN');
assert.ok((await run({ task: 'evaluation', rri: EVALUATION_EXAMPLE }, 'D')).text.includes('-21.4'));
assert.ok((await run({ task: 'evaluation', rri: EVALUATION_EXAMPLE }, 'E')).text.length > 0);
const probabilities = await run({ parameters: { model: 'P' }, csvColumns: CSV_CATALOG.map((c) => c.key), outputs: ['bpProb', 'bpsvg', 'qSpotProb', 'tSpotProb', 'spotProb', 'pMinE'] });
assert.equal(probabilities.rows.length, 1);
assert.ok(probabilities.fs.readFile('/output/bpsvg.svg', { encoding: 'utf8' }).includes('<svg'));
const accessibility = await run({ parameters: { outNumber: 0 }, outputs: ['qPu'] });
const imported = await run({ parameters: { qAcc: 'P', qAccFile: accessibility.fs.readFile('/output/qPu.csv', { encoding: 'utf8' }) } });
assert.equal(imported.rows[0].E, (await run({ parameters: { qIntLenMax: 9 } })).rows[0].E);
for (const parameters of [{ energyVRNA: 'Turner99', temperature: 20 }, { energyVRNA: 'Andronescu07' }, { model: 'B' }, { mode: 'K' }, { noSeed: true, mode: 'M' }, { seedMaxUP: 1 }, { qAccConstr: '..........', qShape: '1 0.2\n2 0.3' }, { outOverlap: 'N', outNumber: 3 }, { outCsvSort: 'E', outNumber: 3 }]) assert.ok((await run({ parameters })).rows.length >= 1);
console.log('Browser protocol: native-reference prediction/evaluation, signed coordinates, multi-FASTA, all columns, imported accessibility, energy/seed/helix/SHAPE options and output files passed');
27 changes: 13 additions & 14 deletions scripts/wasm/stage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ site="$build/site"
test -s "$site/wasm/intarna.js"
test -s "$site/wasm/intarna.wasm"
mkdir -p "$site/licenses"
for file in index.html styles.css app.js mark.svg protocol.js runner.js results.js; do
cp -R "$repo/webinterface/assets" "$site/"
for file in index.html styles.css app.js mark.svg protocol.js runner.js results.js options.js columns.js; do
cp "$repo/webinterface/$file" "$site/$file"
done
cp "$repo/webinterface/intarna-worker.js" "$site/intarna-worker.js"
Expand Down Expand Up @@ -42,18 +43,16 @@ cat > "$site/licenses/index.html" <<'HTML'
</ul><p><a href="../build-info.json">Build provenance</a> · <a href="../">Back to IntaRNA</a></p>
</html>
HTML
python3 - "$repo" "$site" <<'PY'
import json
import subprocess
import sys
# Provenance belongs to the compiled assets, never the later staging revision.
python3 - "$site" <<'PYINFO'
import hashlib, json, sys
from pathlib import Path
repo, site = sys.argv[1:]
metadata = {
"source": "https://github.com/BackofenLab/IntaRNA",
"commit": subprocess.check_output(["git", "-C", repo, "rev-parse", "HEAD"], text=True).strip(),
"emscripten": "4.0.23", "boost": "1.87.0", "viennarna": "2.7.0", "zlib": "1.3.1",
"threading": "single worker per prediction",
}
Path(site, "build-info.json").write_text(json.dumps(metadata, indent=2) + "\n")
PY
site = Path(sys.argv[1])
metadata = json.loads((site / "build-info.json").read_text())
if not (site / "build-info.txt").is_file():
raise SystemExit("Missing compiled version text")
for name, digest in metadata["sha256"].items():
if hashlib.sha256((site / "wasm" / name).read_bytes()).hexdigest() != digest:
raise SystemExit(f"Mismatched engine: {name}")
PYINFO
echo "Static site: $site"
Loading
Loading