diff --git a/src/main/java/splitstree6/algorithms/AlgorithmList.java b/src/main/java/splitstree6/algorithms/AlgorithmList.java index 47fa9cb2..2332dbe6 100644 --- a/src/main/java/splitstree6/algorithms/AlgorithmList.java +++ b/src/main/java/splitstree6/algorithms/AlgorithmList.java @@ -19,6 +19,7 @@ package splitstree6.algorithms; +import jloda.fx.workflow.NamedBase; import splitstree6.algorithms.trees.trees2trees.ALTSExternal; import splitstree6.algorithms.trees.trees2trees.ALTSNetwork; import splitstree6.main.SplitsTree6; @@ -26,6 +27,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.List; /** @@ -120,12 +122,12 @@ public static List list(String... names0) { add(algorithms, names, new splitstree6.algorithms.trees.trees2splits.SuperNetwork()); add(algorithms, names, new splitstree6.algorithms.trees.trees2splits.TreeSelectorSplits()); add(algorithms, names, new ALTSExternal()); + add(algorithms, names, new ALTSNetwork()); add(algorithms, names, new splitstree6.algorithms.trees.trees2trees.AutumnAlgorithm()); add(algorithms, names, new splitstree6.algorithms.trees.trees2trees.BootstrapTree()); add(algorithms, names, new splitstree6.algorithms.trees.trees2trees.ClusterNetwork()); add(algorithms, names, new splitstree6.algorithms.trees.trees2trees.ConsensusTree()); add(algorithms, names, new splitstree6.algorithms.trees.trees2trees.EnumerateContainedTrees()); - add(algorithms, names, new ALTSNetwork()); add(algorithms, names, new splitstree6.algorithms.trees.trees2trees.LooseAndLacy()); add(algorithms, names, new splitstree6.algorithms.trees.trees2trees.RerootOrReorderTrees()); add(algorithms, names, new splitstree6.algorithms.trees.trees2trees.RootedConsensusTree()); @@ -134,6 +136,7 @@ public static List list(String... names0) { add(algorithms, names, new splitstree6.algorithms.trees.trees2trees.TreesFilter2()); add(algorithms, names, new splitstree6.algorithms.trees.trees2trees.TreesTaxaFilter()); add(algorithms, names, new splitstree6.algorithms.trees.trees2view.ShowTrees()); + algorithms.sort(Comparator.comparing(NamedBase::getName)); return algorithms; } diff --git a/src/main/java/splitstree6/algorithms/trees/trees2trees/ALTSNetwork.java b/src/main/java/splitstree6/algorithms/trees/trees2trees/ALTSNetwork.java index 4082a59b..a67561fb 100644 --- a/src/main/java/splitstree6/algorithms/trees/trees2trees/ALTSNetwork.java +++ b/src/main/java/splitstree6/algorithms/trees/trees2trees/ALTSNetwork.java @@ -22,6 +22,7 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import jloda.fx.window.NotificationManager; +import jloda.phylo.NewickIO; import jloda.phylo.PhyloTree; import jloda.util.progress.ProgressListener; import splitstree6.algorithms.utils.MutualRefinement; @@ -32,6 +33,7 @@ import splitstree6.xtra.kernelize.Kernelize; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -42,8 +44,11 @@ public class ALTSNetwork extends Trees2Trees { private static boolean warned = false; - private final BooleanProperty optionUseKernelization = new SimpleBooleanProperty(this, "optionUseKernelization", false); - private final BooleanProperty optionUseMutualRefinement = new SimpleBooleanProperty(this, "optionUseMutualRefinement", false); + private final BooleanProperty optionMutualRefinement = new SimpleBooleanProperty(this, "optionMutualRefinement", true); + + private final BooleanProperty optionKernelization = new SimpleBooleanProperty(this, "optionKernelization", false); + + private final BooleanProperty optionRemoveDuplicates = new SimpleBooleanProperty(this, "optionRemoveDuplicates", false); @Override public String getCitation() { @@ -54,7 +59,7 @@ public String getCitation() { @Override public List listOptions() { - return List.of(optionUseMutualRefinement.getName(), optionUseKernelization.getName()); + return List.of(optionMutualRefinement.getName(), optionRemoveDuplicates.getName(), optionKernelization.getName()); } @Override @@ -67,13 +72,20 @@ public void compute(ProgressListener progress, TaxaBlock taxaBlock, TreesBlock t try (var progressMover = new ProgressMover(progress)) { Collection result; - Collection inputTrees = treesBlock.getTrees(); - if (isOptionUseMutualRefinement()) { - inputTrees = MutualRefinement.apply(inputTrees, true); + Collection inputTrees; + if (getOptionMutualRefinement()) { + inputTrees = MutualRefinement.apply(treesBlock.getTrees(), MutualRefinement.Strategy.All, true); + if (false) + System.err.println("Refined:\n" + NewickIO.toString(inputTrees, false)); + } else { + inputTrees = new ArrayList<>(); + for (var tree : treesBlock.getTrees()) { + inputTrees.add(new PhyloTree(tree)); + } } if (inputTrees.size() <= 1) { result = inputTrees; - } else if (!isOptionUseKernelization()) { + } else if (!getOptionKernelization()) { result = AltsNonBinary.apply(inputTrees, progress); } else { result = Kernelize.apply(progress, taxaBlock, inputTrees, AltsNonBinary::apply, 100000); @@ -93,27 +105,35 @@ public boolean isApplicable(TaxaBlock taxa, TreesBlock datablock) { return !datablock.isReticulated() && datablock.getNTrees() > 1; } - public boolean isOptionUseKernelization() { - return optionUseKernelization.get(); + public boolean getOptionKernelization() { + return optionKernelization.get(); + } + + public BooleanProperty optionKernelizationProperty() { + return optionKernelization; + } + + public void setOptionKernelization(boolean optionKernelization) { + this.optionKernelization.set(optionKernelization); } - public BooleanProperty optionUseKernelizationProperty() { - return optionUseKernelization; + public boolean getOptionMutualRefinement() { + return optionMutualRefinement.get(); } - public void setOptionUseKernelization(boolean optionUseKernelization) { - this.optionUseKernelization.set(optionUseKernelization); + public BooleanProperty optionMutualRefinementProperty() { + return optionMutualRefinement; } - public boolean isOptionUseMutualRefinement() { - return optionUseMutualRefinement.get(); + public void setOptionMutualRefinement(boolean optionMutualRefinement) { + this.optionMutualRefinement.set(optionMutualRefinement); } - public BooleanProperty optionUseMutualRefinementProperty() { - return optionUseMutualRefinement; + public boolean isOptionRemoveDuplicates() { + return optionRemoveDuplicates.get(); } - public void setOptionUseMutualRefinement(boolean optionUseMutualRefinement) { - this.optionUseMutualRefinement.set(optionUseMutualRefinement); + public BooleanProperty optionRemoveDuplicatesProperty() { + return optionRemoveDuplicates; } } diff --git a/src/main/java/splitstree6/algorithms/utils/MutualRefinement.java b/src/main/java/splitstree6/algorithms/utils/MutualRefinement.java index 5bc59479..d950b706 100644 --- a/src/main/java/splitstree6/algorithms/utils/MutualRefinement.java +++ b/src/main/java/splitstree6/algorithms/utils/MutualRefinement.java @@ -26,57 +26,87 @@ import java.util.*; +import static splitstree6.algorithms.trees.trees2trees.RootedConsensusTree.isCompatibleWithAll; + /** * mutually refines a collection of trees and removes any topological duplicates * Daniel Huson, 2.2024 */ public class MutualRefinement { + public enum Strategy { + All, Majority, Compatible + } + /** * apply mutual refinement * - * @param trees input trees - * @return output trees + * @param trees input trees + * @param strategy All: use all clusters, Majority, use clusters found in the majority of trees, + * compatible: refine only using clusters that are compatible will all trees + * @param removeDuplicates remove any duplicate trees after refining + * @return refined trees, with any duplicates removed */ - public static Collection apply(Collection trees, boolean removeDuplicateTrees) { - var incompatibilityGraph = ClusterIncompatibilityGraph.apply(trees); + public static List apply(Collection trees, Strategy strategy, boolean removeDuplicates) { + var allClusters = switch (strategy) { + case Compatible -> { + var incompatibilityGraph = ClusterIncompatibilityGraph.apply(trees); + yield incompatibilityGraph.nodeStream() + .filter(v -> v.getDegree() == 0) // compatible with all + .filter(v -> ((BitSet) v.getData()).cardinality() < trees.size()) // not in all trees + .map(v -> (BitSet) v.getInfo()).toList(); - var compatibleClusters = incompatibilityGraph.nodeStream() - .filter(v -> v.getDegree() == 0) // compatible with all - .filter(v -> ((BitSet) v.getData()).cardinality() < trees.size()) // not in all trees - .map(v -> (BitSet) v.getInfo()).toList(); + } + case All -> extract(trees, 0); + case Majority -> extract(trees, (int) Math.ceil(0.5 * trees.size())); + }; - if (compatibleClusters.isEmpty()) - return trees; - else { - var result = new ArrayList(); - var seen = new HashSet>(); - var idLabelMap = new HashMap(); - for (var tree : trees) { - tree.nodeStream().filter(v -> tree.getLabel(v) != null && tree.hasTaxa(v)).forEach(v -> idLabelMap.put(tree.getTaxon(v), tree.getLabel(v))); - var clusters = TreesUtils.collectAllHardwiredClusters(tree); - if (!clusters.containsAll(compatibleClusters)) { - clusters.addAll(compatibleClusters); - var newTree = new PhyloTree(); - ClusterPoppingAlgorithm.apply(clusters, newTree); - if (newTree.getRoot().getOutDegree() == 1) { - var v = newTree.getRoot().getFirstOutEdge().getTarget(); - newTree.deleteNode(newTree.getRoot()); - newTree.setRoot(v); - } + var result = new ArrayList(); + var seen = new HashSet>(); + var idLabelMap = new HashMap(); + for (var tree : trees) { + tree.nodeStream().filter(v -> tree.getLabel(v) != null && tree.hasTaxa(v)).forEach(v -> idLabelMap.put(tree.getTaxon(v), tree.getLabel(v))); + var treeClusters = TreesUtils.collectAllHardwiredClusters(tree); - newTree.nodeStream().filter(newTree::hasTaxa).forEach(v -> newTree.setLabel(v, idLabelMap.get(newTree.getTaxon(v)))); - if (!removeDuplicateTrees || !seen.contains(clusters)) { - result.add(newTree); - if (removeDuplicateTrees) - seen.add(clusters); + var added = false; + for (var cluster : allClusters) { + if (!treeClusters.contains(cluster) && isCompatibleWithAll(cluster, treeClusters)) { + treeClusters.add(cluster); + added = true; + } + } + if (!removeDuplicates || !seen.contains(treeClusters)) { + if (!added) { + result.add(new PhyloTree(tree)); + } else { + var newtree = new PhyloTree(); + newtree.setName(tree.getName() + "-refined"); + ClusterPoppingAlgorithm.apply(treeClusters, newtree); + if (newtree.getRoot().getOutDegree() == 1) { + var v = newtree.getRoot().getFirstOutEdge().getTarget(); + newtree.deleteNode(newtree.getRoot()); + newtree.setRoot(v); } - } else if (!removeDuplicateTrees || !seen.contains(clusters)) { - result.add(tree); - if (removeDuplicateTrees) - seen.add(clusters); + newtree.nodeStream().filter(newtree::hasTaxa).forEach(v -> newtree.setLabel(v, idLabelMap.get(newtree.getTaxon(v)))); + result.add(newtree); } + if (removeDuplicates) + seen.add(treeClusters); + } + } + return result; + } + + private static List extract(Collection trees, int threshold) { + var clusterCountMap = new HashMap(); + for (var tree : trees) { + for (var cluster : TreesUtils.collectAllHardwiredClusters(tree)) { + clusterCountMap.put(cluster, clusterCountMap.getOrDefault(cluster, 0) + 1); } - return result; } + var list = new ArrayList<>(clusterCountMap.keySet().stream(). + filter(c -> clusterCountMap.get(c) < trees.size()) + .filter(c -> clusterCountMap.get(c) > threshold).toList()); + list.sort((a, b) -> -Integer.compare(clusterCountMap.get(a), clusterCountMap.get(b))); + return list; } }