From e1373824074dd4bcb6a51bbf467213446aa71505 Mon Sep 17 00:00:00 2001 From: Lars-B Date: Mon, 26 May 2025 08:48:10 -0700 Subject: [PATCH 1/4] Gracefully exit when sampled ancestors are present and CA heights are chosen --- .../app/treeannotator/TreeAnnotator.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/beastfx/app/treeannotator/TreeAnnotator.java b/src/beastfx/app/treeannotator/TreeAnnotator.java index 4986153..064e7b3 100644 --- a/src/beastfx/app/treeannotator/TreeAnnotator.java +++ b/src/beastfx/app/treeannotator/TreeAnnotator.java @@ -611,8 +611,8 @@ public void run(final int burninPercentage, Log.err.println("A tree with a sampled ancestor is found. Turning on\n the sampled ancestor " + "summary analysis."); if (nodeHeightSettingService.getServiceName().equals("CA")) { - throw new RuntimeException("The common ancestor height is not \n available for trees with sampled " + - "ancestors. Please choose \n another height summary option"); + throw new RuntimeException("Common ancestor height is unavailable for trees with sampled ancestors.\n" + + "Please select a different height summary option."); } } totalTreesUsed++; @@ -1485,11 +1485,14 @@ public void run() throws Exception { try { run(burninPercentage, lowMem, posteriorLimit, hpd2D, targetTreeFileName, inputFileName, outputFileName); - //} catch (IOException e) { - // throw e; } catch (Exception e) { - e.printStackTrace(); - } + String msg = e.getMessage(); + if (msg != null && msg.contains("Common ancestor height")) { + Log.err("[Error]:\n " + msg.replace("\n", "\n ")); + } else { + e.printStackTrace(); + } + } // if (args.length == 0) { // // only need exit when in GUI mode @@ -1650,7 +1653,8 @@ boolean setTreeHeightsByCA(Tree targetTree, Target targetOption) throws IOExcept } private CladeSystem cladeSystem = null; - + + // todo is this function irrelevant? public CladeSystem getCladeSystem() { burninPercentage = burnInPercentageInput.get(); CladeSystem cladeSystem = new CladeSystem(); @@ -1666,8 +1670,8 @@ public CladeSystem getCladeSystem() { Log.err.println("A tree with a sampled ancestor is found. Turning on\n the sampled ancestor " + "summary analysis."); if (nodeHeightSettingService.getServiceName().equals("CA")) { - throw new RuntimeException("The common ancestor height is not \n available for trees with sampled " + - "ancestors. Please choose \n another height summary option"); + throw new RuntimeException("Common ancestor height is unavailable for trees with sampled ancestors.\n" + + "Please select a different height summary option."); } cladeSystem.setProcessSA(true); } From 120e6065047a21046a95501a2d1a0e2d8b5b925d Mon Sep 17 00:00:00 2001 From: Lars-B Date: Mon, 26 May 2025 08:56:39 -0700 Subject: [PATCH 2/4] warning about using CCDs with SA trees, not directly supported but will still produce a tree --- .../app/treeannotator/TreeAnnotator.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/beastfx/app/treeannotator/TreeAnnotator.java b/src/beastfx/app/treeannotator/TreeAnnotator.java index 064e7b3..c268d8f 100644 --- a/src/beastfx/app/treeannotator/TreeAnnotator.java +++ b/src/beastfx/app/treeannotator/TreeAnnotator.java @@ -599,7 +599,8 @@ public void run(final int burninPercentage, Log.err.println("Error Parsing Input Tree: " + e.getMessage()); return; } - + + Log.err.println(); if (!topologySettingService.getServiceName().equals(UserTargetTreeTopologyService.SERVICE_NAME)) { // even when a user specified target tree is provided we still need to count the totalTreesUsed for subsequent steps. treeSet.reset(); @@ -608,12 +609,23 @@ public void run(final int burninPercentage, tree.getLeafNodeCount(); if (tree.getDirectAncestorNodeCount() > 0 && !SAmode && processSA) { SAmode = true; - Log.err.println("A tree with a sampled ancestor is found. Turning on\n the sampled ancestor " + - "summary analysis."); + Log.err(""" + [Info] A tree with sampled acnestors was detected. + Turning on sampled ancestor summary analysis. + """ + ); if (nodeHeightSettingService.getServiceName().equals("CA")) { throw new RuntimeException("Common ancestor height is unavailable for trees with sampled ancestors.\n" + "Please select a different height summary option."); } + if (topologySettingService.getServiceName().contains("CCD")) { + Log.err(""" + [Warning] Sampled ancestors are not directly supported by CCDs. + They will be treated as if they were tips of the tree. + Please interpret the resulting summary tree accordingly. + """ + ); + } } totalTreesUsed++; } From 240e7810cb68948f13014845643799c18d8d9934 Mon Sep 17 00:00:00 2001 From: Lars-B Date: Mon, 26 May 2025 09:01:44 -0700 Subject: [PATCH 3/4] commenting never used bits of code --- .../app/treeannotator/TreeAnnotator.java | 278 +++++++++--------- 1 file changed, 139 insertions(+), 139 deletions(-) diff --git a/src/beastfx/app/treeannotator/TreeAnnotator.java b/src/beastfx/app/treeannotator/TreeAnnotator.java index c268d8f..0bdddf2 100644 --- a/src/beastfx/app/treeannotator/TreeAnnotator.java +++ b/src/beastfx/app/treeannotator/TreeAnnotator.java @@ -770,52 +770,52 @@ private void setupAttributes(Tree tree) { } } - private Tree summarizeTrees(CladeSystem cladeSystem, boolean useSumCladeCredibility) throws IOException { - - Tree bestTree = null; - double bestScore = Double.NEGATIVE_INFINITY; - - progressStream.println("Analyzing " + totalTreesUsed + " trees..."); - progressStream.println("0 25 50 75 100"); - progressStream.println("|--------------|--------------|--------------|--------------|"); - - int stepSize = Math.max(totalTreesUsed / 60, 1); - int reported = 0; - - int counter = 0; - treeSet.reset(); - while (treeSet.hasNext()) { - Tree tree = treeSet.next(); - double score = scoreTree(tree, cladeSystem, useSumCladeCredibility); - if (score > bestScore) { - bestTree = tree; - bestScore = score; - } - while (reported < 61 && 1000.0*reported < 61000.0 * (counter + 1) / totalTreesUsed) { - progressStream.print("*"); - reported++; - progressStream.flush(); - } - counter++; - } - progressStream.println(); - progressStream.println(); - if (useSumCladeCredibility) { - progressStream.println("Highest Sum Clade Credibility: " + bestScore); - } else { - progressStream.println("Highest Log Clade Credibility: " + bestScore); - } - - return bestTree; - } +// private Tree summarizeTrees(CladeSystem cladeSystem, boolean useSumCladeCredibility) throws IOException { +// +// Tree bestTree = null; +// double bestScore = Double.NEGATIVE_INFINITY; +// +// progressStream.println("Analyzing " + totalTreesUsed + " trees..."); +// progressStream.println("0 25 50 75 100"); +// progressStream.println("|--------------|--------------|--------------|--------------|"); +// +// int stepSize = Math.max(totalTreesUsed / 60, 1); +// int reported = 0; +// +// int counter = 0; +// treeSet.reset(); +// while (treeSet.hasNext()) { +// Tree tree = treeSet.next(); +// double score = scoreTree(tree, cladeSystem, useSumCladeCredibility); +// if (score > bestScore) { +// bestTree = tree; +// bestScore = score; +// } +// while (reported < 61 && 1000.0*reported < 61000.0 * (counter + 1) / totalTreesUsed) { +// progressStream.print("*"); +// reported++; +// progressStream.flush(); +// } +// counter++; +// } +// progressStream.println(); +// progressStream.println(); +// if (useSumCladeCredibility) { +// progressStream.println("Highest Sum Clade Credibility: " + bestScore); +// } else { +// progressStream.println("Highest Log Clade Credibility: " + bestScore); +// } +// +// return bestTree; +// } - public double scoreTree(Tree tree, CladeSystem cladeSystem, boolean useSumCladeCredibility) { - if (useSumCladeCredibility) { - return cladeSystem.getSumCladeCredibility(tree.getRoot(), null); - } else { - return cladeSystem.getLogCladeCredibility(tree.getRoot(), null); - } - } +// public double scoreTree(Tree tree, CladeSystem cladeSystem, boolean useSumCladeCredibility) { +// if (useSumCladeCredibility) { +// return cladeSystem.getSumCladeCredibility(tree.getRoot(), null); +// } else { +// return cladeSystem.getLogCladeCredibility(tree.getRoot(), null); +// } +// } private void annotateTree(CladeSystem cladeSystem, Node node, BitSet bits) { //, HeightsSummary heightsOption) { @@ -1569,100 +1569,100 @@ public static interface TreeAnnotationPlugin { boolean handleAttribute(Node node, String attributeName, double[] values); } - boolean setTreeHeightsByCA(Tree targetTree, Target targetOption) throws IOException - { - progressStream.println("Setting node heights..."); - progressStream.println("0 25 50 75 100"); - progressStream.println("|--------------|--------------|--------------|--------------|"); - - int reportStepSize = totalTreesUsed / 60; - if (reportStepSize < 1) reportStepSize = 1; - int reported = 0; - - - // this call increments the clade counts and it shouldn't - // this is remedied with removeClades call after while loop below - CladeSystem cladeSystem = new CladeSystem(targetTree); - final int clades = cladeSystem.getCladeMap().size(); - - // allocate posterior tree nodes order once - int[] postOrderList = new int[clades]; - BitSet[] ctarget = new BitSet[clades]; - BitSet[] ctree = new BitSet[clades]; - - for (int k = 0; k < clades; ++k) { - ctarget[k] = new BitSet(); - ctree[k] = new BitSet(); - } - - cladeSystem.getTreeCladeCodes(targetTree, ctarget); - - // temp collecting heights inside loop allocated once - double[][] hs = new double[clades][treeSet.totalTrees - treeSet.burninCount]; - - // heights total sum from posterior trees - double[] ths = new double[clades]; - - int totalTreesUsed = 0; - - int counter = 0; - treeSet.reset(); - while (treeSet.hasNext()) { - Tree tree = treeSet.next(); - TreeUtils.preOrderTraversalList(tree, postOrderList); - cladeSystem.getTreeCladeCodes(tree, ctree); - for (int k = 0; k < clades; ++k) { - int j = postOrderList[k]; - for (int i = 0; i < clades; ++i) { - if( CollectionUtils.isSubSet(ctarget[i], ctree[j]) ) { - hs[i][counter] = tree.getNode(j).getHeight(); - } - } - } - for (int k = 0; k < clades; ++k) { - ths[k] += hs[k][counter]; - } - totalTreesUsed += 1; - while (reported < 61 && 1000.0*reported < 61000.0 * (counter + 1) / this.totalTreesUsed) { - progressStream.print("*"); - reported++; - progressStream.flush(); - } - counter++; - - } - - if (targetOption != Target.USER_TARGET_TREE) - targetTree.initAndValidate(); - - cladeSystem.removeClades(targetTree.getRoot(), true); - for (int k = 0; k < clades; ++k) { - ths[k] /= totalTreesUsed; - final Node node = targetTree.getNode(k); - node.setHeight(ths[k]); - String attributeName = "CAheight"; - double [] values = hs[k]; - double min = values[0]; - double max = values[0]; - for (double d : values) { - min = Math.min(d, min); - max = Math.max(d, max); - } - if (Math.abs(min - max) > 1e-10) { - annotateMeanAttribute(node, attributeName + "_mean", values); - annotateMedianAttribute(node, attributeName + "_median", values); - annotateHPDAttribute(node, attributeName + "_95%_HPD", 0.95, values); - annotateRangeAttribute(node, attributeName + "_range", values); - } - } - - assert (totalTreesUsed == this.totalTreesUsed); - this.totalTreesUsed = totalTreesUsed; - progressStream.println(); - progressStream.println(); - - return true; - } +// boolean setTreeHeightsByCA(Tree targetTree, Target targetOption) throws IOException +// { +// progressStream.println("Setting node heights..."); +// progressStream.println("0 25 50 75 100"); +// progressStream.println("|--------------|--------------|--------------|--------------|"); +// +// int reportStepSize = totalTreesUsed / 60; +// if (reportStepSize < 1) reportStepSize = 1; +// int reported = 0; +// +// +// // this call increments the clade counts and it shouldn't +// // this is remedied with removeClades call after while loop below +// CladeSystem cladeSystem = new CladeSystem(targetTree); +// final int clades = cladeSystem.getCladeMap().size(); +// +// // allocate posterior tree nodes order once +// int[] postOrderList = new int[clades]; +// BitSet[] ctarget = new BitSet[clades]; +// BitSet[] ctree = new BitSet[clades]; +// +// for (int k = 0; k < clades; ++k) { +// ctarget[k] = new BitSet(); +// ctree[k] = new BitSet(); +// } +// +// cladeSystem.getTreeCladeCodes(targetTree, ctarget); +// +// // temp collecting heights inside loop allocated once +// double[][] hs = new double[clades][treeSet.totalTrees - treeSet.burninCount]; +// +// // heights total sum from posterior trees +// double[] ths = new double[clades]; +// +// int totalTreesUsed = 0; +// +// int counter = 0; +// treeSet.reset(); +// while (treeSet.hasNext()) { +// Tree tree = treeSet.next(); +// TreeUtils.preOrderTraversalList(tree, postOrderList); +// cladeSystem.getTreeCladeCodes(tree, ctree); +// for (int k = 0; k < clades; ++k) { +// int j = postOrderList[k]; +// for (int i = 0; i < clades; ++i) { +// if( CollectionUtils.isSubSet(ctarget[i], ctree[j]) ) { +// hs[i][counter] = tree.getNode(j).getHeight(); +// } +// } +// } +// for (int k = 0; k < clades; ++k) { +// ths[k] += hs[k][counter]; +// } +// totalTreesUsed += 1; +// while (reported < 61 && 1000.0*reported < 61000.0 * (counter + 1) / this.totalTreesUsed) { +// progressStream.print("*"); +// reported++; +// progressStream.flush(); +// } +// counter++; +// +// } +// +// if (targetOption != Target.USER_TARGET_TREE) +// targetTree.initAndValidate(); +// +// cladeSystem.removeClades(targetTree.getRoot(), true); +// for (int k = 0; k < clades; ++k) { +// ths[k] /= totalTreesUsed; +// final Node node = targetTree.getNode(k); +// node.setHeight(ths[k]); +// String attributeName = "CAheight"; +// double [] values = hs[k]; +// double min = values[0]; +// double max = values[0]; +// for (double d : values) { +// min = Math.min(d, min); +// max = Math.max(d, max); +// } +// if (Math.abs(min - max) > 1e-10) { +// annotateMeanAttribute(node, attributeName + "_mean", values); +// annotateMedianAttribute(node, attributeName + "_median", values); +// annotateHPDAttribute(node, attributeName + "_95%_HPD", 0.95, values); +// annotateRangeAttribute(node, attributeName + "_range", values); +// } +// } +// +// assert (totalTreesUsed == this.totalTreesUsed); +// this.totalTreesUsed = totalTreesUsed; +// progressStream.println(); +// progressStream.println(); +// +// return true; +// } private CladeSystem cladeSystem = null; From 61ac20647a4f0e3dfe3748fad63cefabdc7fa388 Mon Sep 17 00:00:00 2001 From: Lars-B Date: Mon, 28 Jul 2025 12:30:09 -0700 Subject: [PATCH 4/4] adding option to set seed for alignment generation --- .../app/seqgen/SimulatedAlignment.java | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/beastfx/app/seqgen/SimulatedAlignment.java b/src/beastfx/app/seqgen/SimulatedAlignment.java index ee712e7..f64bba6 100644 --- a/src/beastfx/app/seqgen/SimulatedAlignment.java +++ b/src/beastfx/app/seqgen/SimulatedAlignment.java @@ -6,6 +6,7 @@ import beast.base.core.Description; import beast.base.core.Input; import beast.base.core.Input.Validate; +import beast.base.core.Log; import beast.base.evolution.alignment.Alignment; import beast.base.evolution.alignment.Sequence; import beast.base.evolution.branchratemodel.BranchRateModel; @@ -32,7 +33,12 @@ public class SimulatedAlignment extends Alignment { final public Input m_sequenceLengthInput = new Input<>("sequencelength", "nr of samples to generate (default 1000).", 1000); final public Input m_outputFileNameInput = new Input<>( "outputFileName", - "If provided, simulated alignment is additionally written to this file."); + "If provided, simulated alignment is additionally written to this file."); + final public Input localSeedInput = new Input<>( + "seed", + "Optional local random seed for simulating this alignment. If not set, global seed is used.", + Input.Validate.OPTIONAL + ); /** * nr of samples to generate * @@ -87,9 +93,24 @@ public void initAndValidate() { m_outputFileName = m_outputFileNameInput.get(); sequenceInput.get().clear(); - - simulate(); - + + Long customSeed = localSeedInput.get(); + long originalSeed = Randomizer.getSeed(); + long seedToUse = customSeed != null ? customSeed : originalSeed; + + if (customSeed != null) { + Log.info.println(); + Log.info.println("Random number seed for alignment simulation: " + customSeed); + Log.info.println(); + } + + try { + Randomizer.setSeed(seedToUse); + simulate(); + } finally { + Randomizer.setSeed(originalSeed); + } + // Write simulated alignment to disk if requested: if (m_outputFileName != null) { PrintStream pstream;