# Directed graph similarity calculation — optimizing Mathematica code

I am looking to optimize the following computation. First, some explanations:

Explanation

I have a directed graph whose adjacency matrix may only contain 1s and 0s. The similarity of nodes i and j is defined as follows:

1. count how many nodes have an edge pointing to both i and j, and count how many nodes do both i and j point to
2. divide the sum of these two counts by the total number of nodes either i or j is connected to

I need to find the maximum similarity of each node in graph (its similarity with any other node except itself), and compute the average of these numbers.

The graphs I'm working with are not necessarily connected, but they don't have components of size 1 (i.e. nodes that are not connected to any other nodes).

The code

meanMaxSim[g_Graph] :=
Module[{am, pList, similarityMatrix},
pList = Transpose@Join[am, Transpose[am]];
similarityMatrix = Outer[Total[#1 #2]/Total[Sign[#1 + #2]] &, pList, pList, 1];
Mean[Max /@ dropDiagonal[similarityMatrix]]
]


(Sign is used to keep 0s and convert anything larger to 1s.)

This function just removes the elements on the diagonal of an n by n matrix, ending up with an n by n - 1 matrix:

dropDiagonal[matrix_] :=


Graph[] is only available in Mathematica 8, but this is irrelevant here as most of the time is spent in Outer[] anyway. If you have an earlier version, just assume that you have the adjacency matrix as a SparseArray as starting point.

I am looking for modifications that speed this up significantly (more than 3x), but any other comment on the code is most welcome. Hopefully a speedup is possible without a significant increase in code complexity.

Notes

Doing the computations with machine-numbers is okay, but I didn't bother doing that since it doesn't provide a significant speedup.

A note about self-loops: they are allowed in the graph, and yes, they're double-counted in this implementation.

EDIT:

I was too quick to ask this question. Here's a ~4x speedup using Compile. I'm still interested in comments / further speedups.

simMatCompiled =
Compile[{{p, _Real, 2}},
Table[Total[a b]/Total@Sign[a + b], {a, p}, {b, p}]
];

mxSim[g_Graph] :=
Module[{am, pList, similarityMatrix},
pList = Transpose@Join[am, Transpose[am]];
similarityMatrix = simMatCompiled[Normal[pList]];
Mean[Max /@ dropDiagonal[similarityMatrix]]
]


I needed to change Outer to Table in order for Compile to be able to do its job.

• Please see my latest edit. – Mr.Wizard May 12 '11 at 15:35

Minor improvements, but consider using:

pList = ArrayFlatten[{{am\[Transpose], am}}];


and

dropDiagonal = MapThread[Delete, {#, Range@Length@#}] &;


I also tried dropDiagonal = MapIndexed[Drop, #] & but this is slower, at least in v7.

Replace:

Total[#1 #2]/Total[Sign[#1 + #2]] &


With:

#.#2 / Tr@Unitize[#1 + #2] &


I believe this is an improvement to your compiled function:

Compile[{{p, _Real, 2}},
With[{b = p\[Transpose]},
#.b / Total@Sign[# + b] & /@ p
]
]