/*
* File: MutableNodePartitioning.java
* Authors: Jeremy D. Wendt
* Company: Sandia National Laboratories
* Project: Cognitive Foundry
*
* Copyright 2016, Sandia Corporation.
* Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive
* license for use of this work by or on behalf of the U.S. Government.
* Export of this program may require a license from the United States
* Government. See CopyrightHistory.txt for complete details.
*
*/
package gov.sandia.cognition.graph.community;
import gov.sandia.cognition.graph.DirectedNodeEdgeGraph;
import gov.sandia.cognition.collection.IntArrayList;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This package-private class gives a default implementation to the
* NodePartitioning interface that is mutable (in a package-private manner).
*
* @author jdwendt
*/
class MutableNodePartitioning<NodeNameType>
implements NodePartitioning<NodeNameType>
{
/**
* Stores the partition assignment for each node (by id)
*/
private final IntArrayList nodeToPartitionAssignments;
/**
* The partitions stored herein (nodes stored by id)
*/
private List<Set<Integer>> partitions;
/**
* The graph that has been partitioned. Required so that id-to-name can be
* performed when needed. Note that this is not a deep copy, so if you alter
* the graph after passing it in here, there will be problems.
*/
private final DirectedNodeEdgeGraph<NodeNameType> partitionedGraph;
/**
* Initializes this with each node assigned to its own separate community.
* Note that this implicitly makes a copy of the node list (by assigning
* each a partition) and stores a reference to the input graph. Therefore,
* if you alter the graph after passing it in here, strange things will
* occur.
*
* @param graph The graph that will be partitioned
*/
MutableNodePartitioning(DirectedNodeEdgeGraph<NodeNameType> graph)
{
partitionedGraph = graph;
int n = graph.getNumNodes();
partitions = new ArrayList<>(n);
nodeToPartitionAssignments = new IntArrayList(n);
for (int i = 0; i < n; ++i)
{
partitions.add(new HashSet<>());
partitions.get(i).add(i);
nodeToPartitionAssignments.add(i);
}
}
/**
* Removes empty partitions from the partitions list. Partitions empty by
* having all nodes moved out of them. However, they are not immediately
* removed as sometimes those nodes may move back in (depending on community
* detection algorithm). Also, doing the removal in bulk is less
* computationally expensive.
*
* Note that the remaining partitions will be reassigned to ids [0 ... k).
*/
void removeEmptyPartitions()
{
List<Set<Integer>> updatedPartitions = new ArrayList<>();
int[] partIdMap = new int[getNumPartitions()];
int cnt = 0;
for (Set<Integer> partition : partitions)
{
if (!partition.isEmpty())
{
partIdMap[cnt] = updatedPartitions.size();
updatedPartitions.add(partition);
}
else
{
partIdMap[cnt] = -1;
}
++cnt;
}
partitions = updatedPartitions;
for (int i = 0; i < nodeToPartitionAssignments.size(); ++i)
{
int newPartitionId = partIdMap[nodeToPartitionAssignments.get(i)];
if (newPartitionId == -1)
{
throw new RuntimeException("This should be impossible, but "
+ "node " + i + "'s old partition assignment was found to "
+ "be empty");
}
nodeToPartitionAssignments.set(i, newPartitionId);
}
}
/**
* Moves the node specified by the input id to a new partition and removes
* it from its old partition. Note that if that node was (somehow) not in a
* partition before, this will fail.
*
* @param nodeId The node to move
* @param newPartitionId The new partition to move to. Must be in [0 ...
* numPartitions).
*/
void moveNodeById(int nodeId,
int newPartitionId)
{
if (newPartitionId < 0 || newPartitionId >= getNumPartitions())
{
throw new ArrayIndexOutOfBoundsException("Input partition id ("
+ newPartitionId + ") outside of expected bounds[0, "
+ getNumPartitions() + ")");
}
if (nodeId < 0 || nodeId >= partitionedGraph.getNumNodes())
{
throw new ArrayIndexOutOfBoundsException("Input node id ("
+ nodeId + ") outside of expected bounds[0, "
+ partitionedGraph.getNumNodes() + ")");
}
partitions.get(nodeToPartitionAssignments.get(nodeId)).remove(nodeId);
partitions.get(newPartitionId).add(nodeId);
nodeToPartitionAssignments.set(nodeId, newPartitionId);
}
/**
* Moves the input node to a new partition and removes it from its old
* partition. Note that if that node was (somehow) not in a partition
* before, this will fail.
*
* @param node The node to move
* @param newPartitionId The new partition to move to. Must be in [0 ...
* numPartitions).
*/
void moveNode(NodeNameType node,
int newPartitionId)
{
moveNodeById(partitionedGraph.getNodeId(node), newPartitionId);
}
/**
* @see NodePartitioning#getNumPartitions()
*/
@Override
public int getNumPartitions()
{
return partitions.size();
}
/**
* Private helper that translates the input node ids to node names and
* returns the set of the names
*
* @param ids The node ids needing to be translated
* @return The nodes that were translated from the ids
*/
private Set<NodeNameType> translateIds(Set<Integer> ids)
{
Set<NodeNameType> ret = new HashSet<>(ids.size());
for (int id : ids)
{
ret.add(partitionedGraph.getNode(id));
}
return ret;
}
/**
* @see NodePartitioning#getPartitionMembers(int)
*/
@Override
public Set<NodeNameType> getPartitionMembers(int i)
{
return translateIds(partitions.get(i));
}
/**
* This method that is specific to this implementation is faster than
* getPartitionMembers(int), and so is used when speed is critical.
*
* @param i The partition index
* @return The ids stored in that partition
*/
public Set<Integer> getPartitionMemberIds(int i)
{
return partitions.get(i);
}
/**
* @see NodePartitioning#getAllMembers()
*/
@Override
public Set<NodeNameType> getAllMembers()
{
return new HashSet<>(partitionedGraph.getNodes());
}
/**
* @see NodePartitioning#getPartition(java.lang.Object)
*/
@Override
public int getPartition(NodeNameType node)
{
return nodeToPartitionAssignments.get(partitionedGraph.getNodeId(node));
}
/**
* @see NodePartitioning#getPartitionById(int)
*/
@Override
public int getPartitionById(int nodeId)
{
return nodeToPartitionAssignments.get(nodeId);
}
/**
* @see NodePartitioning#getModularity()
*/
@Override
public Double getModularity()
{
return null;
}
}