/* * Copyright (c) 2014 Oculus Info Inc. http://www.oculusinfo.com/ * * Released under the MIT License. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.oculusinfo.tilegen.graph.analytics; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import com.oculusinfo.factory.util.Pair; /** * Class for a storing a graph community object. Used by GraphAnalyticsRecord for graph * analytics tile generation. Contains info about a graph community's size, location, ID, * generic metadata, its parent graph community (if applicable), as well as the MAX_EDGES highest * weighted inter-community and intra-community edges connected to the graph community. * * Edge info is stored in lists of GraphEdge objects, ranked by weight (highest to lowest). * * @param hierLevel * Hierarchy level of community * * @param id * ID of community * * @param coords * x,y coords of centre of community * * @param radius * radius of community * * @param degree * degree of community * * @param numNodes * number of internal raw nodes in this community * * @param metadata * community metdata * * @param bIsPrimaryNode * bool whether or not this community is the 'primary node' within its parent community * (if so, the parent community is labelled with the ID and metadata of this primary community) * * @param parentID * ID of parent community * * @param parentCoords * x,y coords of centre of parent community * * @param parentRadius * radius of parent community * * @param communityStats * generic list of up to MAX_STATS entries to store/aggregate numeric stats about a graph community * * @param interEdges * list of up to MAX_EDGES highest weighted inter-community edges connected to this node (highest to lowest) * * @param intraEdges * list of up to MAX_EDGES highest weighted intra-community edges connected to this node (highest to lowest) */ public class GraphCommunity implements Serializable { /** * */ private static final long serialVersionUID = 2127685671353087035L; private static int MAX_STATS = 32; // max allowable size of _communityStats list private static int MAX_EDGES = 10; // max number of both inter and intra community // edges to keep per record. private int _hierLevel; // hierarchy level of community private long _id; // ID of community private Pair<Double, Double> _coords; // x,y coords of centre of community private double _radius; // radius of community private int _degree; // degree of community private long _numNodes; // number of raw nodes in this community private String _metadata; // community metdata private boolean _bIsPrimaryNode; // bool whether or not this community is a 'primary node' within its parent community private long _parentID; // ID of parent community private Pair<Double, Double> _parentCoords; // x,y coords of centre of parent community private double _parentRadius; // radius of parent community private List<Double> _communityStats; // generic list of numbers to store/aggregate numeric stats about a graph community private List<GraphEdge> _interEdges; // inter-community edges connected to this node private List<GraphEdge> _intraEdges; // intra-community edges connected to this node //---- Constructor GraphCommunity(int hierLevel, long id, Pair<Double, Double> coords, double radius, int degree, long numNodes, String metadata, boolean bIsPrimaryNode, long parentID, Pair<Double, Double> parentCoords, double parentRadius, List<Double> communityStats, List<GraphEdge> interEdges, List<GraphEdge> intraEdges) { _hierLevel = hierLevel; _id = id; _coords = coords; _radius = radius; _degree = degree; _numNodes = numNodes; _metadata = metadata; _bIsPrimaryNode = bIsPrimaryNode; _parentID = parentID; _parentCoords = parentCoords; _parentRadius = parentRadius; if (communityStats == null) { _communityStats = new ArrayList<Double>(); } else { if (communityStats.size() > MAX_STATS) { _communityStats = new ArrayList<Double>(); for (int i=0; i<MAX_STATS; i++) { _communityStats.add(communityStats.get(i)); // save MAX_STATS entries of communityStats } //throw new IllegalArgumentException("Number of communityStats in list must be <= " + MAX_STATS); } else { _communityStats = communityStats; } } if (interEdges == null) { _interEdges = new ArrayList<GraphEdge>(); } else { if (interEdges.size() > MAX_EDGES) { throw new IllegalArgumentException("Number of inter-community edges in list must be <= " + MAX_EDGES); } _interEdges = interEdges; } if (intraEdges == null) { _intraEdges = new ArrayList<GraphEdge>(); } else { if (intraEdges.size() > MAX_EDGES) { throw new IllegalArgumentException("Number of intra-community edges in list must be <= " + MAX_EDGES); } _intraEdges = intraEdges; } } public static void setMaxStats(int max) { //TODO -- This is not the best way to set this static objects for a serializable java class. MAX_STATS = max; // Perhaps set them from within GraphAnalyticsRecordParser object and access them from within the this class constructor instead } public static void setMaxEdges(int max) { MAX_EDGES = max; } public int getHierLevel() { return _hierLevel; } public long getID() { return _id; } public Pair<Double, Double> getCoords() { return _coords; } public double getRadius() { return _radius; } public int getDegree() { return _degree; } public long getNumNodes() { return _numNodes; } public String getMetadata() { return _metadata; } public boolean isPrimaryNode() { return _bIsPrimaryNode; } public long getParentID() { return _parentID; } public Pair<Double, Double> getParentCoords() { return _parentCoords; } public double getParentRadius() { return _parentRadius; } public List<Double> getStatsList() { return _communityStats; } public List<GraphEdge> getInterEdges() { return _interEdges; } public List<GraphEdge> getIntraEdges() { return _intraEdges; } private static void addEdgeInPlace(LinkedList<GraphEdge> accumulatedEdges, GraphEdge newEdge) { ListIterator<GraphEdge> i = accumulatedEdges.listIterator(); int size = 0; while (true) { if (i.hasNext()) { GraphEdge next = i.next(); size++; // Rank edges based on weight if (next.getWeight() < newEdge.getWeight()) { // Insert the new edge if has higher weight i.previous(); i.add(newEdge); size++; i.next(); if (!i.hasNext() && size == MAX_EDGES+1) { // already at end of list but is 1 element too big i.remove(); --size; } else { // ... and trim the list to MAX_EDGES elements while (i.hasNext() && size < MAX_EDGES) { i.next(); ++size; } while (i.hasNext()) { i.next(); i.remove(); } } return; } } else { if (size < MAX_EDGES) { i.add(newEdge); } return; } } } // private static void addEdgesInPlace(LinkedList<GraphEdge> accumulatedEdges, List<GraphEdge> newEdges) { // for (GraphEdge newEdge : newEdges) { // addEdgeInPlace(accumulatedEdges, newEdge); // } // } /** * Add an inter-community edge to an existing community, summing counts as needed, and keeping * the MAX_EDGES largest edges. */ public void addInterEdgeToCommunity(GraphEdge newEdge) { LinkedList<GraphEdge> accumulatedEdges = new LinkedList<>(_interEdges); addEdgeInPlace(accumulatedEdges, newEdge); _interEdges = accumulatedEdges; } /** * Add an intra-community edge to an existing community, summing counts as needed, and keeping * the MAX_EDGES largest edges. */ public void addIntraEdgeToCommunity(GraphEdge newEdge) { LinkedList<GraphEdge> accumulatedEdges = new LinkedList<>(_intraEdges); addEdgeInPlace(accumulatedEdges, newEdge); _intraEdges = accumulatedEdges; } private List<GraphEdge> minOfEdgeLists(GraphEdge accumulatedMin, List<GraphEdge> newMin) { long dstID = accumulatedMin.getDstID(); double x = accumulatedMin.getDstCoords().getFirst(); double y = accumulatedMin.getDstCoords().getSecond(); long weight = accumulatedMin.getWeight(); for (int i = 0; i < newMin.size(); ++i) { dstID = Math.min(dstID, newMin.get(i).getDstID()); x = Math.min(x, newMin.get(i).getDstCoords().getFirst()); y = Math.min(y, newMin.get(i).getDstCoords().getSecond()); weight = Math.min(weight, newMin.get(i).getWeight()); } return Arrays.asList(new GraphEdge(dstID, x, y, weight)); } private List<GraphEdge> maxOfEdgeLists(GraphEdge accumulatedMax, List<GraphEdge> newMax) { long dstID = accumulatedMax.getDstID(); double x = accumulatedMax.getDstCoords().getFirst(); double y = accumulatedMax.getDstCoords().getSecond(); long weight = accumulatedMax.getWeight(); for (int i = 0; i < newMax.size(); ++i) { dstID = Math.max(dstID, newMax.get(i).getDstID()); x = Math.max(x, newMax.get(i).getDstCoords().getFirst()); y = Math.max(y, newMax.get(i).getDstCoords().getSecond()); weight = Math.max(weight, newMax.get(i).getWeight()); } return Arrays.asList(new GraphEdge(dstID, x, y, weight)); } private List<Double> minOfStatsList(double accumulatedMin, List<Double> statsList) { double minVal = accumulatedMin; for (int i = 0; i < statsList.size(); i++) { minVal = Math.min(minVal, statsList.get(i)); } return Arrays.asList(minVal); } private List<Double> maxOfStatsList(double accumulatedMax, List<Double> statsList) { double maxVal = accumulatedMax; for (int i = 0; i < statsList.size(); i++) { maxVal = Math.max(maxVal, statsList.get(i)); } return Arrays.asList(maxVal); } //---- min attribute values between two communities public void minInPlace(GraphCommunity b) { double x = Math.min(this.getCoords().getFirst(), b.getCoords().getFirst()); double y = Math.min(this.getCoords().getSecond(), b.getCoords().getSecond()); double px = Math.min(this.getParentCoords().getFirst(), b.getParentCoords().getFirst()); double py = Math.min(this.getParentCoords().getSecond(), b.getParentCoords().getSecond()); _hierLevel = Math.min(this.getHierLevel(), b.getHierLevel()); _id = Math.min(this.getID(), b.getID()); _coords = new Pair<Double, Double>(x,y); _radius = Math.min(this.getRadius(), b.getRadius()); _degree = Math.min(this.getDegree(), b.getDegree()); _numNodes = Math.min(this.getNumNodes(), b.getNumNodes()); _metadata = ""; _bIsPrimaryNode = false; _parentID = Math.min(this.getParentID(), b.getParentID()); _parentCoords = new Pair<Double, Double>(px,py); _parentRadius = Math.min(this.getParentRadius(), b.getParentRadius()); _communityStats = minOfStatsList(this.getStatsList().get(0), b.getStatsList()); _interEdges = minOfEdgeLists(this.getInterEdges().get(0), b.getInterEdges()); _intraEdges = minOfEdgeLists(this.getIntraEdges().get(0), b.getIntraEdges()); } //---- max attribute values between two communities public void maxInPlace(GraphCommunity b) { double x = Math.max(this.getCoords().getFirst(), b.getCoords().getFirst()); double y = Math.max(this.getCoords().getSecond(), b.getCoords().getSecond()); double px = Math.max(this.getParentCoords().getFirst(), b.getParentCoords().getFirst()); double py = Math.max(this.getParentCoords().getSecond(), b.getParentCoords().getSecond()); _hierLevel = Math.max(this.getHierLevel(), b.getHierLevel()); _id = Math.max(this.getID(), b.getID()); _coords = new Pair<Double, Double>(x,y); _radius = Math.max(this.getRadius(), b.getRadius()); _degree = Math.max(this.getDegree(), b.getDegree()); _numNodes = Math.max(this.getNumNodes(), b.getNumNodes()); _metadata = ""; _bIsPrimaryNode = false; _parentID = Math.max(this.getParentID(), b.getParentID()); _parentCoords = new Pair<Double, Double>(px,py); _parentRadius = Math.max(this.getParentRadius(), b.getParentRadius()); _communityStats = maxOfStatsList(this.getStatsList().get(0), b.getStatsList()); _interEdges = maxOfEdgeLists(this.getInterEdges().get(0), b.getInterEdges()); _intraEdges = maxOfEdgeLists(this.getIntraEdges().get(0), b.getIntraEdges()); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (null == obj) return false; if (!(obj instanceof GraphCommunity)) return false; GraphCommunity that = (GraphCommunity) obj; if (this.getHierLevel() != that.getHierLevel()) { return false; } else if ((this.getID() != that.getID())) { return false; } else if (!(this.getCoords().equals(that.getCoords()))) { return false; } else if ((this.getRadius() != that.getRadius())) { return false; } else if ((this.getDegree() != that.getDegree())) { return false; } else if ((this.getNumNodes() != that.getNumNodes())) { return false; } else if (!(this.getMetadata().equals(that.getMetadata()))) { return false; } else if ((this.isPrimaryNode() != that.isPrimaryNode())) { return false; } else if ((this.getParentID() != that.getParentID())) { return false; } else if (!(this.getParentCoords().equals(that.getParentCoords()))) { return false; } else if ((this.getParentRadius() != that.getParentRadius())) { return false; } else if (!listsEqual(this.getStatsList(), that.getStatsList())) { return false; } else if (!listsEqual(this.getInterEdges(), that.getInterEdges())) { return false; } else if (!listsEqual(this.getIntraEdges(), that.getIntraEdges())) { return false; } else { return true; } } private static boolean objectsEqual(Object a, Object b) { if (null == a) return null == b; return a.equals(b); } private static <T> boolean listsEqual(List<T> a, List<T> b) { if (null == a) return null == b; if (null == b) return false; if (a.size() != b.size()) return false; for (int i = 0; i < a.size(); ++i) { if (!objectsEqual(a.get(i), b.get(i))) return false; } return true; } }