phylogician-ts
Version:
Module to read, manipulate and write phylogenetic trees. Written in TypeScript
177 lines • 8.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const loglevel_colored_prefix_1 = require("loglevel-colored-prefix");
const seedrandom_1 = __importDefault(require("seedrandom"));
class BalancedSample {
constructor(tree, loglevel = 'info') {
this.tree = tree;
this.nodePickCounter = Array(this.tree.nodes.length).fill(1);
this.logLevel = loglevel;
this.logger = new loglevel_colored_prefix_1.Logger(this.logLevel);
this.sampleTracker = [];
}
getScores() {
return this.nodePickCounter;
}
setScores() {
const log = this.logger.getLogger('BalancedSample::setScores');
log.info(`setting the node scores`);
const setScoresFromList = (nodeId) => {
let numberOfLeafs = 0;
this.tree.getNode(nodeId).children.forEach(childId => {
const childNode = this.tree.getNode(childId);
numberOfLeafs += childNode.isLeaf() ? 1 : setScoresFromList(childId);
});
this.nodePickCounter[nodeId] = numberOfLeafs;
log.debug(`node ${nodeId} has ${numberOfLeafs} leafs.`);
return numberOfLeafs;
};
const rootNode = this.tree.getRootNode();
if (rootNode) {
this.nodePickCounter[rootNode.id] = setScoresFromList(rootNode.id);
}
return this;
}
pick(picked, baseNodeId, N, add = false, seed) {
return this.setScores().selectBalancedSample(picked, baseNodeId, N, add, seed);
}
pickMixed(picked, baseNodeId, N, add = false, seed) {
return this.setScores().selectBalancedSample2(picked, baseNodeId, N, add, seed);
}
pickRandom(N, listOfNodes, seed) {
const log = this.logger.getLogger('BalancedSample::pickRandom');
const rng = seedrandom_1.default(seed);
const sample = [];
if (listOfNodes.length < N) {
return listOfNodes;
}
log.debug(`picking ${N} samples from ${listOfNodes}`);
while (sample.length < N) {
const rn = rng();
const i = rn * (listOfNodes.length - 1);
log.debug(`current List of nodes: ${listOfNodes}`);
log.debug(`value: ${i}`);
sample.push(listOfNodes.splice(i, 1)[0]);
log.debug(`tmp sample: ${sample}\n`);
}
return sample;
}
selectBalancedSample2(picked = [], baseNodeIds, N, add = false, seed, thread = []) {
const log = this.logger.getLogger('BalancedSample::selectBalancedSample2');
log.debug(`Working on ${baseNodeIds} to pick ${N} samples.`);
let samples = [];
const rng = seedrandom_1.default(seed);
const children = this.tree.getNode(baseNodeIds).children;
const leafs = children.filter(id => this.tree.getNode(id).isLeaf());
const internal = children.filter(id => !this.tree.getNode(id).isLeaf());
const odds = [leafs.length];
const maxValue = internal.reduce((sum, b, i) => (odds[i + 1] = sum + this.nodePickCounter[b]), leafs.length);
const sampleAssignment = Array(odds.length).fill(0);
log.debug(`options: ${leafs.length}-leafs,${internal}`);
log.debug(`maxLength: ${maxValue} odds ${JSON.stringify(odds)}`);
for (let i = 0; i < N; i++) {
const rnum = rng();
const value = rnum * maxValue;
let choice = 0;
while (choice < odds.length) {
if (odds[choice] > value) {
break;
}
choice++;
}
sampleAssignment[choice]++;
}
log.debug(`sampleAssignment: ${sampleAssignment}`);
const rn = rng();
samples = samples.concat(this.pickRandom(sampleAssignment[0], leafs, rn.toString()));
if (sampleAssignment[0] > samples.length) {
sampleAssignment[1] += sampleAssignment[0] - samples.length;
}
internal.forEach((id, i) => {
const n = sampleAssignment[i + 1];
log.debug(`follow up on ${id} to pick ${n}`);
if (n > 0) {
if (this.nodePickCounter[id] <= n) {
samples = samples.concat(this.tree.getLeafsIds(id));
if (i < internal.length) {
sampleAssignment[i + 2] += n - this.nodePickCounter[id];
}
}
else if (n === 1) {
const newSeed = rng();
samples = samples.concat(this.selectBalancedSample([], id, 1, false, newSeed.toString()));
}
else {
const newSeed = rng();
samples = samples.concat(this.selectBalancedSample2([], id, n, false, newSeed.toString()));
}
}
else {
log.debug(`skip`);
}
});
return samples;
}
selectBalancedSample(picked = [], baseNodeId, N, add = false, seed, thread = []) {
const log = this.logger.getLogger('BalancedSample::selectBalancedSample');
const selected = picked.map(id => id);
const rng = seedrandom_1.default(seed);
const baseNode = this.tree.getNode(baseNodeId);
let paths = baseNode.children.filter(d => selected.indexOf(d) === -1 && this.nodePickCounter[d] > 0);
log.debug(`Init search for ${baseNodeId}`);
log.debug(`already selected: ${selected}`);
log.debug(`possible paths: ${paths}`);
const totalNumber = add ? picked.length + N : N;
log.debug(`will pick additional: ${totalNumber - selected.length}`);
while (selected.length < totalNumber && paths.length > 0) {
const thisThread = thread.map(id => id);
thisThread.push(baseNodeId);
log.debug(`${thisThread} - getting to tree ${baseNodeId}`);
log.debug(`${thisThread} - state of counter: ${JSON.stringify(this.nodePickCounter)}`);
const rn = rng();
paths = baseNode.children.filter(d => selected.indexOf(d) === -1 && this.nodePickCounter[d] > 0);
log.debug(`${thisThread} - selected: ${selected}`);
log.debug(`${thisThread} - possible paths ${paths}`);
const ruler = [];
const fullLength = paths.reduce((a, b, i) => (ruler[i] = a + this.nodePickCounter[b]), 0);
log.debug(`${thisThread} - maxLength: ${fullLength} ruler ${JSON.stringify(ruler)}`);
const value = rn * fullLength;
log.debug(`${thisThread} - value: ${value}`);
let choice = 0;
while (choice < paths.length) {
if (ruler[choice] > value) {
break;
}
choice++;
}
const nextNode = this.tree.getNode(paths[choice]);
log.debug(`${thisThread} - pick: ${nextNode.id}\n`);
if (!nextNode.isLeaf()) {
log.debug(`${thisThread} - ${nextNode.id} is not a leaf\n`);
const newSelected = this.selectBalancedSample(selected, nextNode.id, 1, true, rn.toString(), thisThread);
log.debug(`${thisThread} - old selection: ${selected} new selection: ${newSelected} from ${nextNode.id} for ${baseNodeId}\n`);
if (newSelected.toString() === selected.toString()) {
log.debug(`${thisThread} - eliminating path ${paths[choice]}: no use`);
paths.splice(choice, 1);
}
else {
log.debug(`${thisThread} - Found ${newSelected[newSelected.length - 1]} on path ${paths[choice]}: adding`);
selected.push(newSelected[newSelected.length - 1]);
}
}
else {
log.debug(`${thisThread} - is leaf!\n`);
selected.push(nextNode.id);
thisThread.forEach(id => {
this.nodePickCounter[id]--;
});
}
}
return selected;
}
}
exports.BalancedSample = BalancedSample;
//# sourceMappingURL=BalancedSample.js.map