@euphrasiologist/lwphylo 1.2.26 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -938,6 +938,85 @@ function parentFisheye(d, data) {
938
938
  return parent ? { px: parent.fisheye.x, py: parent.fisheye.y } : null;
939
939
  }
940
940
 
941
+ /**
942
+ * Generate a random bifurcating tree with `nTips` tips, in the same
943
+ * parent/children node shape produced by readTree().
944
+ *
945
+ * Topology is grown by repeatedly picking a random extant lineage to split
946
+ * (a Yule/coalescent-style process), so internal branching order is random
947
+ * rather than a fixed balanced/caterpillar shape. Branch lengths are drawn
948
+ * uniformly from [0, maxBranchLength).
949
+ */
950
+
951
+ function randomTree(nTips = 10, {
952
+ maxBranchLength = 1,
953
+ labelPrefix = 't',
954
+ seed = null
955
+ } = {}) {
956
+ if (!Number.isInteger(nTips) || nTips < 1) {
957
+ throw new Error("nTips must be a positive integer");
958
+ }
959
+
960
+ const random = seed == null ? Math.random : mulberry32(seed);
961
+
962
+ let nodeId = 0;
963
+ const makeNode = (parent) => ({
964
+ parent,
965
+ children: [],
966
+ id: nodeId++,
967
+ label: '',
968
+ branchLength: null
969
+ });
970
+
971
+ const root = makeNode(null);
972
+
973
+ if (nTips === 1) {
974
+ root.label = `${labelPrefix}1`;
975
+ return root;
976
+ }
977
+
978
+ // start with two lineages hanging off the root
979
+ let lineages = [makeNode(root), makeNode(root)];
980
+ root.children.push(...lineages);
981
+
982
+ // repeatedly split a random lineage until we have nTips of them
983
+ while (lineages.length < nTips) {
984
+ const i = Math.floor(random() * lineages.length);
985
+ const parent = lineages[i];
986
+ const left = makeNode(parent);
987
+ const right = makeNode(parent);
988
+ parent.children.push(left, right);
989
+ lineages.splice(i, 1, left, right);
990
+ }
991
+
992
+ // assign branch lengths to every non-root node, and tip labels in
993
+ // left-to-right order
994
+ let tipIndex = 0;
995
+ const assign = (node) => {
996
+ for (const child of node.children) {
997
+ child.branchLength = random() * maxBranchLength;
998
+ assign(child);
999
+ }
1000
+ if (node.children.length === 0) {
1001
+ node.label = `${labelPrefix}${++tipIndex}`;
1002
+ }
1003
+ };
1004
+ assign(root);
1005
+
1006
+ return root;
1007
+ }
1008
+
1009
+ // small deterministic PRNG so `seed` gives reproducible trees
1010
+ function mulberry32(seed) {
1011
+ let a = seed >>> 0;
1012
+ return function () {
1013
+ a |= 0; a = (a + 0x6D2B79F5) | 0;
1014
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
1015
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
1016
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
1017
+ };
1018
+ }
1019
+
941
1020
  /**
942
1021
  * Parse a Newick tree string into a doubly-linked list of JS Objects.
943
1022
  * Assigns labels, branch lengths, and node IDs (tips before internals if input emits them that way).
@@ -1834,5 +1913,5 @@ function drawPhylogeny(
1834
1913
  }
1835
1914
  }
1836
1915
 
1837
- export { describeArc, describeArcSweep, drawPhylogeny, parentFisheye, phisheye, polarToCartesian, radialLayout, readTree, rectangleLayout, subTree, unrooted };
1916
+ export { describeArc, describeArcSweep, drawPhylogeny, parentFisheye, phisheye, polarToCartesian, radialLayout, randomTree, readTree, rectangleLayout, subTree, unrooted };
1838
1917
  //# sourceMappingURL=index.js.map