add unit tests

This commit is contained in:
Arthur Lu 2024-05-28 19:40:16 +00:00
parent 685f43c19b
commit d04679819d
8 changed files with 1641 additions and 66 deletions

View File

@ -4,13 +4,13 @@
"description": "Wavefront alignment algorithm in JS", "description": "Wavefront alignment algorithm in JS",
"main": "src/main.js", "main": "src/main.js",
"type": "module", "type": "module",
"dependencies": {},
"devDependencies": { "devDependencies": {
"eslint": "^8.43.0", "eslint": "^8.43.0",
"eslint-config-standard": "^17.1.0", "eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^16.0.1", "eslint-plugin-n": "^16.0.1",
"eslint-plugin-promise": "^6.1.1" "eslint-plugin-promise": "^6.1.1",
"progress": "^2.0.3"
}, },
"scripts": { "scripts": {
"lint": "DEBUG=eslint:cli-engine eslint --fix ." "lint": "DEBUG=eslint:cli-engine eslint --fix ."

View File

@ -1,14 +1,31 @@
import wf_align from "./wfa.js"; import wf_align from "./wfa.js";
import fs from "fs";
import ProgressBar from "progress";
const p = { let data = fs.readFileSync("./tests/tests.json");
x: 4, data = JSON.parse(data);
o: 6, const sequences = fs.readFileSync("./tests/sequences").toString().split("\n");
e: 2 //const total = sequences.length;
}; const total = 500; // skip the later tests because of memory usage
// this should be score=24, Alignment=XDIX for (const test_name of Object.keys(data)) {
console.time("time") const test = data[test_name];
const {CIGAR, score} = wf_align("TCTTTACTCGCGCGTTGGAGAAATACAATAGT", "TCTATACTGCGCGTTTGGAGAAATAAAATAGT", p) const penalties = test.penalties;
console.timeEnd("time") const solutions = fs.readFileSync(test.solutions).toString().split("\n");
console.log(`score: ${score}`); const bar = new ProgressBar(":bar :current/:total", { total: total / 2 });
console.log(`CIGAR: ${CIGAR}`); console.log(`test: ${test_name}`);
let correct = 0;
let j = 0;
for (let i = 0; i < total; i += 2) {
const s1 = sequences[i].replace(">");
const s2 = sequences[i + 1].replace("<");
const { CIGAR, score } = wf_align(s1, s2, penalties);
const solution_score = Number(solutions[j].split("\t")[0]);
if (solution_score === -score) {
correct += 1;
}
j += 1;
bar.tick();
}
console.log(`correct: ${correct}\ntotal: ${total / 2}\n`);
}

View File

@ -5,6 +5,7 @@ class WavefrontComponent {
this.W = []; // wavefront diag distance for each wavefront this.W = []; // wavefront diag distance for each wavefront
this.A = []; // compact CIGAR for backtrace this.A = []; // compact CIGAR for backtrace
} }
// get value for wavefront=score, diag=k // get value for wavefront=score, diag=k
get_val (score, k) { get_val (score, k) {
if (this.W[score] !== undefined && this.W[score][k] !== undefined) { if (this.W[score] !== undefined && this.W[score][k] !== undefined) {
@ -14,6 +15,7 @@ class WavefrontComponent {
return NaN; return NaN;
} }
} }
// set value for wavefront=score, diag=k // set value for wavefront=score, diag=k
set_val (score, k, val) { set_val (score, k, val) {
if (this.W[score]) { if (this.W[score]) {
@ -24,6 +26,7 @@ class WavefrontComponent {
this.W[score][k] = val; this.W[score][k] = val;
} }
} }
// get alignment traceback // get alignment traceback
get_traceback (score, k) { get_traceback (score, k) {
if (this.A[score] !== undefined && this.A[score][k] !== undefined) { if (this.A[score] !== undefined && this.A[score][k] !== undefined) {
@ -33,6 +36,7 @@ class WavefrontComponent {
return undefined; return undefined;
} }
} }
// set alignment traceback // set alignment traceback
set_traceback (score, k, traceback) { set_traceback (score, k, traceback) {
if (this.A[score]) { if (this.A[score]) {
@ -43,24 +47,29 @@ class WavefrontComponent {
this.A[score][k] = traceback; this.A[score][k] = traceback;
} }
} }
// get hi for wavefront=score // get hi for wavefront=score
get_hi (score) { get_hi (score) {
const hi = this.hi[score]; const hi = this.hi[score];
return isNaN(hi) ? 0 : hi; return isNaN(hi) ? 0 : hi;
} }
// set hi for wavefront=score // set hi for wavefront=score
set_hi (score, hi) { set_hi (score, hi) {
this.hi[score] = hi; this.hi[score] = hi;
} }
// get lo for wavefront=score // get lo for wavefront=score
get_lo (score) { get_lo (score) {
const lo = this.lo[score]; const lo = this.lo[score];
return isNaN(lo) ? 0 : lo; return isNaN(lo) ? 0 : lo;
} }
// set lo for wavefront=score // set lo for wavefront=score
set_lo (score, lo) { set_lo (score, lo) {
this.lo[score] = lo; this.lo[score] = lo;
} }
// string representation of all wavefronts // string representation of all wavefronts
toString () { toString () {
const traceback_str = ["OI", "EI", "OD", "ED", "SB", "IN", "DL", "EN"]; const traceback_str = ["OI", "EI", "OD", "ED", "SB", "IN", "DL", "EN"];
@ -85,7 +94,7 @@ class WavefrontComponent {
s += "|"; s += "|";
} }
} }
s += ">\t<" s += ">\t<";
for (let k = min_lo; k <= max_hi; k++) { for (let k = min_lo; k <= max_hi; k++) {
s += FormatNumberLength(k, 2); s += FormatNumberLength(k, 2);
if (k < max_hi) { if (k < max_hi) {
@ -143,8 +152,8 @@ const traceback = {
Sub: 4, Sub: 4,
Ins: 5, Ins: 5,
Del: 6, Del: 6,
End: 7, End: 7
} };
function FormatNumberLength (num, length) { function FormatNumberLength (num, length) {
let r = "" + num; let r = "" + num;
@ -170,13 +179,8 @@ function max (args) {
return max === -Infinity ? NaN : max; return max === -Infinity ? NaN : max;
} }
function argmin (args) {
const val = min(args)
return args.indexOf(val);
}
function argmax (args) { function argmax (args) {
const val = max(args) const val = max(args);
return args.indexOf(val); return args.indexOf(val);
} }
@ -190,7 +194,7 @@ export default function wf_align (s1, s2, penalties) {
M.set_val(0, 0, 0); M.set_val(0, 0, 0);
M.set_hi(0, 0); M.set_hi(0, 0);
M.set_lo(0, 0); M.set_lo(0, 0);
M.set_traceback(0, 0, traceback.End) M.set_traceback(0, 0, traceback.End);
const I = new WavefrontComponent(); const I = new WavefrontComponent();
const D = new WavefrontComponent(); const D = new WavefrontComponent();
while (true) { while (true) {
@ -244,7 +248,7 @@ function wf_next (M, I, D, score, penalties) {
I.set_traceback(score, k, [traceback.OpenIns, traceback.ExtdIns][argmax([ I.set_traceback(score, k, [traceback.OpenIns, traceback.ExtdIns][argmax([
M.get_val(score - o - e, k - 1), M.get_val(score - o - e, k - 1),
I.get_val(score - e, k - 1) I.get_val(score - e, k - 1)
])]) ])]);
D.set_val(score, k, max([ D.set_val(score, k, max([
M.get_val(score - o - e, k + 1), M.get_val(score - o - e, k + 1),
D.get_val(score - e, k + 1) D.get_val(score - e, k + 1)
@ -252,7 +256,7 @@ function wf_next (M, I, D, score, penalties) {
D.set_traceback(score, k, [traceback.OpenDel, traceback.ExtdDel][argmax([ D.set_traceback(score, k, [traceback.OpenDel, traceback.ExtdDel][argmax([
M.get_val(score - o - e, k + 1), M.get_val(score - o - e, k + 1),
D.get_val(score - e, k + 1) D.get_val(score - e, k + 1)
])]) ])]);
M.set_val(score, k, max([ M.set_val(score, k, max([
M.get_val(score - x, k) + 1, M.get_val(score - x, k) + 1,
I.get_val(score, k), I.get_val(score, k),
@ -301,24 +305,24 @@ function wf_backtrace (M, I, D, score, penalties, A_k) {
break; break;
case traceback.Sub: case traceback.Sub:
tb_s = tb_s - x; tb_s = tb_s - x;
tb_k = tb_k; // tb_k = tb_k;
current_traceback = M.get_traceback(tb_s, tb_k); current_traceback = M.get_traceback(tb_s, tb_k);
break; break;
case traceback.Ins: case traceback.Ins:
tb_s = tb_s; // tb_s = tb_s;
tb_k = tb_k; // tb_k = tb_k;
current_traceback = I.get_traceback(tb_s, tb_k); current_traceback = I.get_traceback(tb_s, tb_k);
break; break;
case traceback.Del: case traceback.Del:
tb_s = tb_s; // tb_s = tb_s;
tb_k = tb_k; // tb_k = tb_k;
current_traceback = D.get_traceback(tb_s, tb_k) current_traceback = D.get_traceback(tb_s, tb_k);
break; break;
case traceback.End: case traceback.End:
done = true done = true;
break; break;
} }
} }
const CIGAR = Array.from(CIGAR_rev).reverse().join(""); const CIGAR = Array.from(CIGAR_rev).reverse().join("");
return {CIGAR, score}; return { CIGAR, score };
} }

610
tests/sequences Normal file

File diff suppressed because one or more lines are too long

305
tests/test_affine_p0_sol Normal file

File diff suppressed because one or more lines are too long

305
tests/test_affine_p1_sol Normal file

File diff suppressed because one or more lines are too long

305
tests/test_affine_p2_sol Normal file

File diff suppressed because one or more lines are too long

29
tests/tests.json Normal file
View File

@ -0,0 +1,29 @@
{
"p0": {
"penalties": {
"m": 0,
"x": 1,
"o": 2,
"e": 1
},
"solutions": "./tests/test_affine_p0_sol"
},
"p1": {
"penalties": {
"m": 0,
"x": 3,
"o": 1,
"e": 4
},
"solutions": "./tests/test_affine_p1_sol"
},
"p2": {
"penalties": {
"m": 0,
"x": 5,
"o": 3,
"e": 2
},
"solutions": "./tests/test_affine_p2_sol"
}
}