UNPKG

phylogician-ts

Version:

Module to read, manipulate and write phylogenetic trees. Written in TypeScript

177 lines 8.3 kB
"use strict"; 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