Skip to content

Commit

Permalink
Make NodeMapping an interface
Browse files Browse the repository at this point in the history
  • Loading branch information
maarzt committed Jul 19, 2023
1 parent 8f58177 commit f45fd7f
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 103 deletions.
113 changes: 26 additions & 87 deletions src/main/java/org/mastodon/mamut/treesimilarity/NodeMapping.java
Original file line number Diff line number Diff line change
@@ -1,104 +1,43 @@
package org.mastodon.mamut.treesimilarity;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.mastodon.mamut.treesimilarity.tree.Tree;

abstract class NodeMapping< T >
/**
* Helper class for {@link ZhangUnorderedTreeEditDistance}.
* <p>
* Used to represent a mapping between the nodes of two {@link Tree trees}
* together with the associated costs / edit distance.
*
* @param <T> Attribute type for the trees.
*
* @see NodeMappings
*/
interface NodeMapping< T >
{
public static < T > NodeMapping< T > empty( double cost )
{
return new EmptyNodeMapping<>( cost );
}

public static < T > NodeMapping< T > singleton( double cost, Tree< T > tree1, Tree< T > tree2 )
{
return new SingletonNodeMapping<>( tree1, tree2, cost );
}

@SafeVarargs
public static < T > NodeMapping< T > compose( NodeMapping< T >... children )
{
return compose( Arrays.asList( children ) );
}

public static < T > NodeMapping< T > compose( List< NodeMapping< T > > children )
{
return new ComposedNodeMapping<>( children );
}

private final double cost;

protected NodeMapping( double cost )
{
this.cost = cost;
}

public double getCost()
{
return cost;
}
/**
* @return The cost of this mapping.
*/
double getCost();

abstract protected void writeToMap( Map< Tree< T >, Tree< T > > map );
/**
* This method is needed for an efficient implementation of the
* {@link #asMap()} method. It is not meant to be used directly,
* use {@link #asMap()} instead.
*/
void writeToMap( Map< Tree< T >, Tree< T > > map );

public Map< Tree< T >, Tree< T > > asMap()
/**
* @return The mapping as a {@link Map} from nodes of the first tree to
* nodes of the second tree.
*/
default Map< Tree< T >, Tree< T > > asMap()
{
final Map< Tree< T >, Tree< T > > map = new HashMap<>();
writeToMap( map );
return map;
}

static class EmptyNodeMapping< T > extends NodeMapping< T >
{
private EmptyNodeMapping( double cost )
{
super( cost );
}

@Override
public void writeToMap( Map< Tree< T >, Tree< T > > map )
{
// do nothing
}
}

static class SingletonNodeMapping< T > extends NodeMapping< T >
{
private final Tree< T > tree1;

private final Tree< T > tree2;

private SingletonNodeMapping( Tree< T > tree1, Tree< T > tree2, double cost )
{
super( cost );
this.tree1 = tree1;
this.tree2 = tree2;
}

@Override
public void writeToMap( Map< Tree< T >, Tree< T > > map )
{
map.put( tree1, tree2 );
}
}

private static class ComposedNodeMapping< T > extends NodeMapping< T >
{
private final List< NodeMapping< T > > children;

private ComposedNodeMapping( List< NodeMapping< T > > children )
{
super( children.stream().mapToDouble( NodeMapping::getCost ).sum() );
this.children = children;
}

@Override
public void writeToMap( Map< Tree< T >, Tree< T > > map )
{
children.forEach( child -> child.writeToMap( map ) );
}
}
}
130 changes: 130 additions & 0 deletions src/main/java/org/mastodon/mamut/treesimilarity/NodeMappings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package org.mastodon.mamut.treesimilarity;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.mastodon.mamut.treesimilarity.tree.Tree;

/**
* Utility class for {@link ZhangUnorderedTreeEditDistance} that provides
* static factory methods for the easy creation of {@link NodeMapping}s.
*/
class NodeMappings
{
private NodeMappings()
{
// prevent from instantiation
}

/**
* @return An empty {@link NodeMapping} with the specified cost.
* (Please note that the costs for an empty mapping are almost never
* zero!)
*/
public static < T > NodeMapping< T > empty( double cost )
{
return new EmptyNodeMapping<>( cost );
}

/**
* @return A {@link NodeMapping} that represents a singleton Map from
* tree1 to tree2 with the specified cost.
*/
public static < T > NodeMapping< T > singleton( double cost, Tree< T > tree1, Tree< T > tree2 )
{
return new SingletonNodeMapping<>( tree1, tree2, cost );
}

/**
* @return A {@link NodeMapping} that represents a composed that contains
* all the map entries of the given {@code children}. The costs of the
* composed mapping is the sum of the costs of the children.
*/
@SafeVarargs
public static < T > NodeMapping< T > compose( NodeMapping< T >... children )
{
return compose( Arrays.asList( children ) );
}

/**
* @return A {@link NodeMapping} that represents a composed that contains
* all the map entries of the given {@code children}. The costs of the
* composed mapping is the sum of the costs of the children.
*/
public static < T > NodeMapping< T > compose( List< NodeMapping< T > > children )
{
return new ComposedNodeMapping<>( children );
}

private abstract static class AbstractNodeMapping< T > implements NodeMapping< T >
{
private final double cost;

protected AbstractNodeMapping( double cost )
{
this.cost = cost;
}

@Override
public double getCost()
{
return cost;
}

@Override
public abstract void writeToMap( Map< Tree< T >, Tree< T > > map );

}

private static class EmptyNodeMapping< T > extends AbstractNodeMapping< T >
{
private EmptyNodeMapping( double cost )
{
super( cost );
}

@Override
public void writeToMap( Map< Tree< T >, Tree< T > > map )
{
// do nothing
}
}

private static class SingletonNodeMapping< T > extends AbstractNodeMapping< T >
{
private final Tree< T > tree1;

private final Tree< T > tree2;

private SingletonNodeMapping( Tree< T > tree1, Tree< T > tree2, double cost )
{
super( cost );
this.tree1 = tree1;
this.tree2 = tree2;
}

@Override
public void writeToMap( Map< Tree< T >, Tree< T > > map )
{
map.put( tree1, tree2 );
}
}

private static class ComposedNodeMapping< T > extends AbstractNodeMapping< T >
{
private final List< NodeMapping< T > > children;

private ComposedNodeMapping( List< NodeMapping< T > > children )
{
super( children.stream().mapToDouble( NodeMapping::getCost ).sum() );
this.children = children;
}

@Override
public void writeToMap( Map< Tree< T >, Tree< T > > map )
{
children.forEach( child -> child.writeToMap( map ) );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,14 @@ private NodeMapping< T > treeMapping( Tree< T > tree1, Tree< T > tree2 )

private NodeMapping< T > computeTreeMapping( Tree< T > tree1, Tree< T > tree2 )
{
NodeMapping< T > attributeMapping = NodeMapping.singleton( attributeDistances.get( Pair.of( tree1, tree2 ) ), tree1, tree2 );
NodeMapping< T > attributeMapping = NodeMappings.singleton( attributeDistances.get( Pair.of( tree1, tree2 ) ), tree1, tree2 );
if ( tree1.isLeaf() && tree2.isLeaf() )
return attributeMapping;

// NB: the order of the following three lines is important, changing the order will result in a wrong distance.
NodeMapping< T > insertOperationCosts = insertOperationMapping( tree1, tree2 );
NodeMapping< T > deleteOperationCosts = deleteOperationMapping( tree1, tree2 );
NodeMapping< T > changeCosts = NodeMapping.compose( attributeMapping, forestMapping( tree1, tree2 ) );
NodeMapping< T > changeCosts = NodeMappings.compose( attributeMapping, forestMapping( tree1, tree2 ) );
return findBestMapping( insertOperationCosts, deleteOperationCosts, changeCosts );
}

Expand Down Expand Up @@ -290,10 +290,10 @@ private NodeMapping< T > computeForestMapping( Tree< T > forest1, Tree< T > fore
throw new IllegalArgumentException( "The given trees are both leaves and thus they are both not forests." );

if ( forest1IsLeaf )
return NodeMapping.empty( insertCosts.get( forest2 ).forestCost );
return NodeMappings.empty( insertCosts.get( forest2 ).forestCost );

if ( forest2IsLeaf )
return NodeMapping.empty( deleteCosts.get( forest1 ).forestCost );
return NodeMappings.empty( deleteCosts.get( forest1 ).forestCost );

NodeMapping< T > forestInsertCosts = forestInsertMapping( forest1, forest2 );
NodeMapping< T > forestDeleteCosts = forestDeleteMapping( forest1, forest2 );
Expand All @@ -312,9 +312,9 @@ private NodeMapping< T > insertOperationMapping( Tree< T > tree1, Tree< T > tree
double insertCostTree2 = insertCosts.get( tree2 ).treeCost;
return findBestMapping( tree2.getChildren(), child ->
{
NodeMapping< T > insertCosts = NodeMapping.empty( insertCostTree2 - this.insertCosts.get( child ).treeCost );
NodeMapping< T > insertCosts = NodeMappings.empty( insertCostTree2 - this.insertCosts.get( child ).treeCost );
NodeMapping< T > childMapping = treeMapping( tree1, child );
return NodeMapping.compose( insertCosts, childMapping );
return NodeMappings.compose( insertCosts, childMapping );
} );
}

Expand All @@ -329,9 +329,9 @@ private NodeMapping< T > deleteOperationMapping( Tree< T > tree1, Tree< T > tree
double deleteCostTree1 = deleteCosts.get( tree1 ).treeCost;
return findBestMapping( tree1.getChildren(), child ->
{
NodeMapping< T > deleteCosts = NodeMapping.empty( deleteCostTree1 - this.deleteCosts.get( child ).treeCost );
NodeMapping< T > deleteCosts = NodeMappings.empty( deleteCostTree1 - this.deleteCosts.get( child ).treeCost );
NodeMapping< T > childMapping = treeMapping( child, tree2 );
return NodeMapping.compose( deleteCosts, childMapping );
return NodeMappings.compose( deleteCosts, childMapping );
} );
}

Expand All @@ -345,9 +345,9 @@ private NodeMapping< T > forestInsertMapping( Tree< T > forest1, Tree< T > fores
double insertCostForest2 = insertCosts.get( forest2 ).forestCost;
return findBestMapping( forest2.getChildren(), child ->
{
NodeMapping< T > insertCosts = NodeMapping.empty( insertCostForest2 - this.insertCosts.get( child ).forestCost );
NodeMapping< T > insertCosts = NodeMappings.empty( insertCostForest2 - this.insertCosts.get( child ).forestCost );
NodeMapping< T > childMapping = forestMapping( forest1, child );
return NodeMapping.compose( insertCosts, childMapping );
return NodeMappings.compose( insertCosts, childMapping );
} );
}

Expand All @@ -361,9 +361,9 @@ private NodeMapping< T > forestDeleteMapping( Tree< T > forest1, Tree< T > fores
double deleteCostForest1 = deleteCosts.get( forest1 ).forestCost;
return findBestMapping( forest1.getChildren(), child ->
{
NodeMapping< T > deleteCosts = NodeMapping.empty( deleteCostForest1 - this.deleteCosts.get( child ).forestCost );
NodeMapping< T > deleteCosts = NodeMappings.empty( deleteCostForest1 - this.deleteCosts.get( child ).forestCost );
NodeMapping< T > childMapping = forestMapping( child, forest2 );
return NodeMapping.compose( deleteCosts, childMapping );
return NodeMappings.compose( deleteCosts, childMapping );
} );
}

Expand All @@ -374,7 +374,7 @@ private NodeMapping< T > forestDeleteMapping( Tree< T > forest1, Tree< T > fores
*/
private NodeMapping< T > findBestMapping( Collection< Tree< T > > children, Function< Tree< T >, NodeMapping< T > > function )
{
NodeMapping< T > best = NodeMapping.empty( Double.POSITIVE_INFINITY );
NodeMapping< T > best = NodeMappings.empty( Double.POSITIVE_INFINITY );
for ( Tree< T > child : children )
{
NodeMapping< T > nodeMapping = function.apply( child );
Expand Down Expand Up @@ -427,18 +427,18 @@ private NodeMapping< T > minCostMaxFlow( final Tree< T > forest1, final Tree< T

for ( Tree< T > child1 : childrenForest1 )
if ( isFlowEqualToOne( network.getFlow( child1, emptyTree2 ) ) )
childMappings.add( NodeMapping.empty( deleteCosts.get( child1 ).treeCost ) );
childMappings.add( NodeMappings.empty( deleteCosts.get( child1 ).treeCost ) );

for ( Tree< T > child2 : childrenForest2 )
if ( isFlowEqualToOne( network.getFlow( emptyTree1, child2 ) ) )
childMappings.add( NodeMapping.empty( insertCosts.get( child2 ).treeCost ) );
childMappings.add( NodeMappings.empty( insertCosts.get( child2 ).treeCost ) );

for ( Tree< T > child1 : childrenForest1 )
for ( Tree< T > child2 : childrenForest2 )
if ( isFlowEqualToOne( network.getFlow( child1, child2 ) ) )
childMappings.add( treeMapping( child1, child2 ) );

return NodeMapping.compose( childMappings );
return NodeMappings.compose( childMappings );
}

/**
Expand Down

0 comments on commit f45fd7f

Please sign in to comment.