Skip to content

Commit

Permalink
mutual refinement
Browse files Browse the repository at this point in the history
  • Loading branch information
dhuson committed Feb 21, 2024
1 parent fd044f0 commit c1a2510
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 56 deletions.
5 changes: 4 additions & 1 deletion src/main/java/splitstree6/algorithms/AlgorithmList.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@

package splitstree6.algorithms;

import jloda.fx.workflow.NamedBase;
import splitstree6.algorithms.trees.trees2trees.ALTSExternal;
import splitstree6.algorithms.trees.trees2trees.ALTSNetwork;
import splitstree6.main.SplitsTree6;
import splitstree6.workflow.Algorithm;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

/**
Expand Down Expand Up @@ -120,12 +122,12 @@ public static List<Algorithm> 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());
Expand All @@ -134,6 +136,7 @@ public static List<Algorithm> 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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() {
Expand All @@ -54,7 +59,7 @@ public String getCitation() {

@Override
public List<String> listOptions() {
return List.of(optionUseMutualRefinement.getName(), optionUseKernelization.getName());
return List.of(optionMutualRefinement.getName(), optionRemoveDuplicates.getName(), optionKernelization.getName());
}

@Override
Expand All @@ -67,13 +72,20 @@ public void compute(ProgressListener progress, TaxaBlock taxaBlock, TreesBlock t
try (var progressMover = new ProgressMover(progress)) {
Collection<PhyloTree> result;

Collection<PhyloTree> inputTrees = treesBlock.getTrees();
if (isOptionUseMutualRefinement()) {
inputTrees = MutualRefinement.apply(inputTrees, true);
Collection<PhyloTree> 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);
Expand All @@ -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;
}
}
102 changes: 66 additions & 36 deletions src/main/java/splitstree6/algorithms/utils/MutualRefinement.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<PhyloTree> apply(Collection<PhyloTree> trees, boolean removeDuplicateTrees) {
var incompatibilityGraph = ClusterIncompatibilityGraph.apply(trees);
public static List<PhyloTree> apply(Collection<PhyloTree> 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<PhyloTree>();
var seen = new HashSet<Set<BitSet>>();
var idLabelMap = new HashMap<Integer, String>();
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<PhyloTree>();
var seen = new HashSet<Set<BitSet>>();
var idLabelMap = new HashMap<Integer, String>();
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<BitSet> extract(Collection<PhyloTree> trees, int threshold) {
var clusterCountMap = new HashMap<BitSet, Integer>();
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;
}
}

0 comments on commit c1a2510

Please sign in to comment.