Source: cna.maia

///
/// @license
/// Copyright 2020 Roberto Luiz Souza Monteiro,
///                Renata Souza Barreto,
///                Hernane Borges de Barros Pereira.
///
/// Licensed under the Apache License, Version 2.0 (the 'License');
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at;
///
///   http://www.apache.org/licenses/LICENSE-2.0;
///
/// Unless required by applicable law or agreed to in writing, software;
/// distributed under the License is distributed on an 'AS IS' BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, eitherMath.express or implied.
/// See the License for the specific language governing permissions and;
/// limitations under the License.
///

 ///
 /// MaiaScript Social and Complex Network Analysis library.
 /// @namespace cna
 ///
namespace cna {
    ///
    /// Creates an artificial network.
    /// @method createNetwork
    /// @memberof cna
    /// @param {string}   topology - Graph topology. It can be:
    ///                              complete, random, small world,
    ///                              scale-free or hybrid.
    /// @param {number}   numVertices - Number of vertices.
    /// @param {number}   numEdges - Number of edges.
    /// @param {number}   edgeProbability - Edge probability.
    /// @param {number}   averageDegree - Average degree.
    /// @return {object}  An artificial network.
    ///
    function createNetwork(topology, numVertices, numEdges, edgeProbability, averageDegree) {
        adj = ann.createANN(topology, numVertices, numEdges, edgeProbability, averageDegree)
        return(adj)
    }

    ///
    /// Creates a Pajek file.
    /// @method createPajekFile
    /// @memberof cna
    /// @param {object}   adj - Adjascence matrix.
    /// @param {string}   type - File type (edges, arcs or matrix).
    /// @return {string}  A Pajek file.
    ///
    function createPajekFile(adj, type) {
        if (core.type(type) == "undefined") {
            type = "edges"
        }
        dimAdj = core.dim(adj)
        dimI = dimAdj[0]
        dimJ = dimAdj[1]
        // Each line in a Pajek files must terminate with CR+LF.
        newLine = "\r\n"
        // Save vertices.
        fileContents = "*Vertices " + (dimI - 1) + newLine
        for (i = 1; i < dimI; i = i + 1) {
            fileContents = fileContents + i + " \"" + adj[i, 0] + "\"" + newLine
        }
        // Save edges.
        if (type == "edges") {
            fileContents = fileContents + "*Edges" + newLine
            for (i = 1; i < dimI; i = i + 1) {
                for (j = 1; j < dimJ; j = j + 1) {
                    if (adj[i, j] != 0) {
                        fileContents = fileContents + i + " " + j + " " + adj[i, j] + newLine
                    }
                }
            }
        // Save arcs.
        } elseif (type == "arcs") {
            fileContents = fileContents + "*Arcs" + newLine
            for (i = 1; i < dimI; i = i + 1) {
                for (j = 1; j < dimJ; j = j + 1) {
                    if (adj[i, j] != 0) {
                        fileContents = fileContents + i + " " + j + " " + adj[i, j] + newLine
                    }
                }
            }
        // Save matrix.
        } elseif (type == "matrix") {
            fileContents = fileContents + "*Matrix" + newLine
            for (i = 1; i < dimI; i = i + 1) {
                for (j = 1; j < dimJ; j = j + 1) {
                    fileContents = fileContents + " " + adj[i, j]
                }
                fileContents = fileContents + newLine
            }
        }
        return(fileContents)
    }
    
    ///
    /// Parses a Pajek file.
    /// @method parsePajekFile
    /// @memberof cna
    /// @param {string}   fileContents - Pajek file contents.
    /// @param {object}   properties - Network properties (n, m and directed).
    /// @return {object}  An adjascence matrix.
    ///
    function parsePajekFile(fileContents, properties) {
        if (core.type(properties) == "undefined") {
            properties = {
                "n": 0,
                "m": 0,
                "directed": false
            }
        } else {
            properties.n = 0
            properties.m = 0
            properties.directed = false
        }

        adj = []
        fileSection = "none"
        fileLines = core.split(core.replace(fileContents, "\r\n", "\n"), "\n")
        for (l = 0; l < fileLines.length; l = l + 1) {
            line = fileLines[l]
            record = core.splitCSV(line.trim(), " ", true)
            if ((core.toLowerCase(record[0]) == "*vertices") || (core.toLowerCase(record[0]) == "*edges") || (core.toLowerCase(record[0]) == "*arcs") || (core.toLowerCase(record[0]) == "*matrix")) {
                fileSection = core.toLowerCase(record[0])
                if (fileSection == "*vertices") {
                    n = ""
                    for (j = 1; j < record.length; j = j + 1) {
                        if (record[j] == "") {
                            continue
                        }
                        if (n == "") {
                            n = core.toNumber(record[j])
                            break
                        }
                    }
                    if (n == "") {
                        break
                    } else {
                        adj = core.matrix(0, n + 1, n + 1)
                        properties.n = n
                    }
                } elseif (fileSection == "*arcs") {
                    properties.directed = true
                }
                continue
            }
            if (core.length(record) >= 2) {
                if (fileSection == "*vertices") {
                    id = ""
                    label = ""
                    for (j = 0; j < record.length; j = j + 1) {
                        if (record[j] == "") {
                            continue
                        }
                        if (id == "") {
                            id = core.toNumber(record[j])
                            continue
                        }
                        if (label == "") {
                            label = core.replace(record[j], core.regExp("\"", "g"), "")
                            continue
                        }
                    }
                    adj[0, id] = label
                    adj[id, 0] = label
                } elseif (fileSection == "*edges") {
                    source = ""
                    target = ""
                    weight = ""
                    for (j = 0; j < record.length; j = j + 1) {
                        if (record[j] == "") {
                            continue
                        }
                        if (source == "") {
                            source = core.toNumber(record[j])
                            continue
                        }
                        if (target == "") {
                            target = core.toNumber(record[j])
                            continue
                        }
                        if (weight == "") {
                            weight = core.toNumber(record[j])
                            continue
                        }
                    }
                    if (weight == "") {
                        weight = 1
                    }
                    adj[source, target] = weight
                    adj[target, source] = weight
                    properties.m = properties.m + 1
                } elseif (fileSection == "*arcs") {
                    source = ""
                    target = ""
                    weight = ""
                    for (j = 0; j < record.length; j = j + 1) {
                        if (record[j] == "") {
                            continue
                        }
                        if (source == "") {
                            source = core.toNumber(record[j])
                            continue
                        }
                        if (target == "") {
                            target = core.toNumber(record[j])
                            continue
                        }
                        if (weight == "") {
                            weight = core.toNumber(record[j])
                            continue
                        }
                    }
                    if (weight == "") {
                        weight = 1
                    }
                    adj[source, target] = weight
                    properties.m = properties.m + 1
                }
            }
        }
        return(adj)
    }

    ///
    /// Parses a CSV matrix file.
    /// @method parseMatrixFile
    /// @memberof cna
    /// @param {string}   fileContents - Pajek file contents.
    /// @param {object}   properties - Network properties (n, m and directed).
    /// @param {string}   char - The separator character.
    /// @param {boolean}  replaceCommas - Replace commas by dots in edges weights.
    /// @return {object}  An adjascence matrix.
    ///
    function parseMatrixFile(fileContents, properties, char, replaceCommas) {
        if (core.type(properties) == "undefined") {
            properties = {
                "n": 0,
                "m": 0,
                "directed": false
            }
        } else {
            properties.n = 0
            properties.m = 0
            properties.directed = false
        }
        if (core.type(char) == "undefined") {
            char = ","
        }
        if (core.type(replaceCommas) == "undefined") {
            replaceCommas = false
        }

        adj = []
        fileSection = "none"
        fileLines = core.split(core.replace(fileContents, "\r\n", "\n"), "\n")
        properties.n = core.length(fileLines) - 1
        for (l = 0; l < (fileLines.length - 1); l = l + 1) {
            line = fileLines[l]
            record = core.splitCSV(line.trim(), char, true)
            row = []
            if (l == 0) {
                row.push("")
            } else {
                row.push(adj[0, l])
            }
            for (j = 1; j < record.length; j = j + 1) {
                if (l == 0) {
                    column = record[j]
                } else {
                    if (replaceCommas) {
                        column = core.toNumber(core.replace(record[j], ",", "."))
                    } else {
                        column = core.toNumber(record[j])
                    }
                }
                row.push(column)
                if ((j > 0) && (column != "") && (column != 0)) {
                    properties.m = properties.m + 1
                }
            }
            if (row.length > 0) {
                adj.push(row)
            }
        }
        return(adj)
    }
    
    ///
    /// Convert a file in Pajek format to JSON.
    /// @method pajekFileToJson
    /// @memberof cna
    /// @param {string}   fileContents - Pajek file contents.
    /// @param {object}   properties - Network properties (n, m and directed).
    /// @return {object}  A JSON containing the network.
    ///
    function pajekFileToJson(fileContents, properties) {
        if (core.type(properties) == "undefined") {
            properties = {
                "n": 0,
                "m": 0,
                "directed": false
            }
        } else {
            properties.n = 0
            properties.m = 0
            properties.directed = false
        }
        
        network = {
            "nodes": [],
            "edges": []
        }

        fileSection = "none"
        fileLines = core.split(core.replace(fileContents, "\r\n", "\n"), "\n")
        for (l = 0; l < fileLines.length; l = l + 1) {
            line = fileLines[l]
            record = core.splitCSV(line.trim(), " ", true)
            if ((core.toLowerCase(record[0]) == "*vertices") || (core.toLowerCase(record[0]) == "*edges") || (core.toLowerCase(record[0]) == "*arcs") || (core.toLowerCase(record[0]) == "*matrix")) {
                fileSection = core.toLowerCase(record[0])
                if (fileSection == "*vertices") {
                    n = ""
                    for (j = 1; j < record.length; j = j + 1) {
                        if (record[j] == "") {
                            continue
                        }
                        if (n == "") {
                            n = core.toNumber(record[j])
                            break
                        }
                    }
                    if (n == "") {
                        break
                    } else {
                        properties.n = n
                    }
                } elseif (fileSection == "*arcs") {
                    properties.directed = true
                }
                i = 0
                continue
            }
            if (core.length(record) >= 2) {
                if (fileSection == "*vertices") {
                    id = ""
                    label = ""
                    x = ""
                    y = ""
                    size = ""
                    for (j = 0; j < record.length; j = j + 1) {
                        if (record[j] == "") {
                            continue
                        }
                        if (id == "") {
                            id = core.toString(record[j])
                            continue
                        }
                        if (label == "") {
                            label = core.replace(record[j], core.regExp("\"", "g"), "")
                            continue
                        }
                        if (x == "") {
                            x = core.toNumber(record[j])
                            continue
                        }
                        if (y == "") {
                            y = core.toNumber(record[j])
                            continue
                        }
                        if (size == "") {
                            if (core.type(record[j]) == "number") {
                                size = core.toNumber(record[j])
                            }
                        }
                    }
                    if (x == "") {
                        x = math.random()
                    }
                    if (y == "") {
                        y = math.random()
                    }
                    if (size == "") {
                        size = 1
                    }
                    node = {
                        "id": "n" + id,
                        "label": label,
                        "x": x,
                        "y": y,
                        "size": size
                    }
                    network.nodes.push(node)
                    i = i + 1
                } elseif (fileSection == "*edges") {
                    source = ""
                    target = ""
                    weight = ""
                    for (j = 0; j < record.length; j = j + 1) {
                        if (record[j] == "") {
                            continue
                        }
                        if (source == "") {
                            source = core.toNumber(record[j])
                            continue
                        }
                        if (target == "") {
                            target = core.toNumber(record[j])
                            continue
                        }
                        if (weight == "") {
                            if (core.type(record[j]) == "number") {
                                weight = core.toNumber(record[j])
                            }
                            continue
                        }
                    }
                    if (weight == "") {
                        weight = 1
                    }
                    edge = {
                        "id": "e" + core.toString(i),
                        "source": "n" + core.toString(source),
                        "target": "n" + core.toString(target),
                        "size": weight
                    }
                    network.edges.push(edge)
                    i = i + 1
                    properties.m = properties.m + 1
                } elseif (fileSection == "*arcs") {
                    source = ""
                    target = ""
                    weight = ""
                    for (j = 0; j < record.length; j = j + 1) {
                        if (record[j] == "") {
                            continue
                        }
                        if (source == "") {
                            source = core.toNumber(record[j])
                            continue
                        }
                        if (target == "") {
                            target = core.toNumber(record[j])
                            continue
                        }
                        if (weight == "") {
                            weight = core.toNumber(record[j])
                            continue
                        }
                    }
                    if (weight == "") {
                        weight = 1
                    }
                    edge = {
                        "id": "e" + core.toString(i),
                        "source": "n" + core.toString(source),
                        "target": "n" + core.toString(target),
                        "size": weight
                    }
                    network.edges.push(edge)
                    i = i + 1
                    properties.m = properties.m + 1
                }
            }
        }
        return(network)
    }

    ///
    /// Create a Pajek file from JSON.
    /// @method jsonToPajekFile
    /// @memberof cna
    /// @param {object}   network - JSON containing the network.
    /// @return {string}  A Pajek file.
    ///
    function jsonToPajekFile(network) {
        n = network.nodes.length
        m = network.edges.length
        fileContents = "*Vertices " + core.toString(n) + "\r\n"
        // Create a hash table to store each vertex id.
        hash = []
        for (i = 0; i < n; i = i + 1) {
            node = network.nodes[i]
            if (core.type(node) != "undefined") {
                id = core.toString(i + 1)
                hash[node.id] = id
                if (core.type(node.label) == "undefined") {
                    label = ""
                } else {
                    label = node.label
                }
                if (core.type(node.x) == "undefined") {
                    x = 0
                } else {
                    x = core.toString(node.x)
                }
                if (core.type(node.y) == "undefined") {
                    y = 0
                } else {
                    y = core.toString(node.y)
                }
                if (core.type(node.size) == "undefined") {
                    size = 0
                } else {
                    size = core.toString(node.size)
                }
                if (node.label != "") {
                    fileContents = fileContents + id + " \"" + label + "\" " + x + " " + y + " " + size + "\r\n"
                }
            }
        }
        fileContents = fileContents + "*Arcs\r\n"
        for (i = 0; i < m; i = i + 1) {
            edge = network.edges[i]
            if (core.type(edge) != "undefined") {
                if (core.type(edge.source) == "undefined") {
                    source = ""
                } else {
                    source = hash[edge.source]
                }
                if (core.type(edge.target) == "undefined") {
                    target = ""
                } else {
                    target = hash[edge.target]
                }
                if (core.type(edge.size) == "undefined") {
                    size = 0
                } else {
                    size = core.toString(edge.size)
                }
                if (!((edge.source == "") || (edge.target == ""))) {
                    fileContents = fileContents + source + " " + target + " " + size + "\r\n"
                }
            }
        }
        return(fileContents)
    }

    ///
    /// Transitive closure between two matrices (A and B).
    /// @method getTransitiveClosure
    /// @memberof cna
    /// @param {object}   a - Matrix A.
    /// @param {object}   b - Matrix B.
    /// @return {object}  Transitive closure between two matrices.
    ///
    function getTransitiveClosure(a, b) {
        dimA = core.dim(a)
        dimB = core.dim(b)

        if (dimA[1] == dimB[0]) {
            c = core.matrix(0, dimA[1], dimB[0])
            for (i = 1; i < dimA[0]; i = i + 1) {
                for (j = 1; j < dimB[1]; j = j + 1) {
                    s = 0
                    for (k = 1; k < dimB[0]; k = k + 1) {
                        s = s || a[i,k] && b[k,j]
                    }
                    c[i,j] = s
                }
            }
            return(c)
        } else {
            throw(Error("The number of columns in matrix A must equal the number of rows in matrix B"))
        }
    }
    
    ///
    /// Boolean OR between two matrices (A and B).
    /// @method getBooleanOr
    /// @memberof cna
    /// @param {object}   a - Matrix A.
    /// @param {object}   b - Matrix B.
    /// @return {object}  Boolean OR between two matrices.
    ///
    function getBooleanOr(a, b) {
        dimA = core.dim(a)
        dimB = core.dim(b)

        if (dimA[0] == dimB[0]) {
            c = core.matrix(0, dimA[0], dimA[1])
            for (i = 1; i < dimA[0]; i = i + 1) {
                for (j = 1; j < dimA[1]; j = j + 1) {    
                    c[i,j] = a[i, j] || b[i, j]
                }
            }
            return(c)
        } else {
            throw(Error("The dimensions of matrix A must be the same as that of matrix B"))
        }
    }
    
    ///
    /// Calculates the network density.
    /// @method getDensity
    /// @memberof cna
    /// @param {object}   adj - Adjascence matrix.
    /// @param {boolean}  directed - True if the network is directed.
    /// @return {number}  The network density.
    ///
    function getDensity(adj, directed) {
        if (core.type(directed) == "undefined") {
            directed = false
        }
        dimAdj = core.dim(adj)
        dimI = dimAdj[0]
        n = dimI - 1
        // Remove vertex data from the matrix.
        for (i = 0; i < dimI; i = i + 1) {
            adj[0, i] = 0
            adj[i, 0] = 0
        }
        nedges = matrix.count(adj)
        if (directed) {
            density = nedges / (n * (n - 1))
        } else {
            density = (nedges / 2) / (n * (n - 1) / 2)
        }
        return(density)
    }
    
    ///
    /// Calculates the degrees of the vertices of the matrix.
    /// @method getDegrees
    /// @memberof cna
    /// @param {object}   adj - Adjascence matrix.
    /// @param {boolean}  directed - True if the network is directed.
    /// @return {object}  Returns a array containing the degrees of the vertices.
    ///
    function getDegrees(adj, directed) {
        if (core.type(directed) == "undefined") {
            directed = false
        }
        dimAdj = core.dim(adj)
        dimI = dimAdj[0]
        dimJ = dimAdj[1]
        degrees = core.matrix(0, dimI, 3)
        for (i = 1; i < dimI; i = i + 1) {
            degrees[i, 0] = matrix.count(adj, i, 1, i, dimJ - 1)
            degrees[i, 1] = matrix.count(adj, 1, i, dimI - 1, i)
            if (directed) {
                degrees[i, 2] = math.max(degrees[i, 0], degrees[i, 1])
            } else {
                if (degrees[i, 0] != 0) {
                    degrees[i, 2] = degrees[i, 0]
                } else {
                    degrees[i, 2] = degrees[i, 1]
                }
            }
        }
        return(degrees)
    }
    
    ///
    /// Calculates the degrees distribution in the network.
    /// @method getDegreeDistribution
    /// @memberof cna
    /// @param {object}   degrees - Array containing degrees of the vertices.
    /// @return {object}  Returns an array containing the degrees distribution in the network.
    ///
    function getDegreeDistribution(degrees) {
        dimDegrees = core.dim(degrees)
        dimI = dimDegrees[0]
        degDist = []
        for (i = 1; i < dimI; i = i + 1) {
            degree = degrees[i, 2]
            if (core.type(degDist[degree]) == "undefined") {
                degDist[degree] = 1
            } else {
                degDist[degree] = degDist[degree] + 1
			}
        }
        hist = core.matrix(0, core.length(degDist), 3)
        i = 0
        foreach(degDist; deg; dist) {
            hist[i, 0] = deg
            hist[i, 1] = dist
    		i = i + 1
        }
        dimHist = core.dim(hist)
        sumDegs = matrix.sum(hist, 0, 1, dimHist[0] - 1, 1)
        for (i = 0; i < dimHist[0]; i = i + 1) {
            hist[i, 2] = (hist[i, 1] / sumDegs) * 100
        }
        // Sort data.
        for (i = 0; i < dimHist[0] - 1; i = i + 1) {
            for (j = i; j < dimHist[0]; j = j + 1) {
                if (hist[i, 0] > hist[j, 0]) {
                    degree = hist[i, 0]
                    hist[i, 0] = hist[j, 0]
                    hist[j, 0] = degree
                    
                    frequency = hist[i, 1]
                    hist[i, 1] = hist[j, 1]
                    hist[j, 1] = frequency

                    percentual = hist[i, 2]
                    hist[i, 2] = hist[j, 2]
                    hist[j, 2] = percentual
                }
            }
        }
    	return(hist)
    }
    
    ///
    /// Calculates average degree of the network.
    /// @method getAverageDegree
    /// @memberof cna
    /// @param {object}   degrees - Array containing degrees of the vertices.
    /// @return {number}  Returns the average degree of the network.
    ///
    function getAverageDegree(degrees) {
        dimDegrees = core.dim(degrees)
        avgDeg = matrix.avg(degrees, 1, 2, (dimI - 1), 2)
        return(avgDeg.avg)
    }
    
    ///
    /// Calculates the clustering coefficients of the vertices.
    /// @method getClustering
    /// @memberof cna
    /// @param {object}   adj - Adjascence matrix.
    /// @param {boolean}  directed - True if the network is directed.
    /// @return {object}  Returns an array containing the clustering coefficients of the vertices.
    ///
    function getClustering(adj, directed) {
        if (core.type(directed) == "undefined") {
            directed = false
        }
        dimAdj = core.dim(adj)
        dimI = dimAdj[0]
        dimJ = dimAdj[1]
        // To calculate the clustering coefficient we need an undirected graph.
        if (directed) {
            for (i = 1; i < dimI; i = i + 1) {
                for (j = 1; j < dimJ; j = j + 1) {
                    if (adj[i, j] == 1) {
                        adj[j, i] = 1
                    }
                }
            }
        }
        clustering = core.matrix(0, dimI, 1)
        // Calculate the clustering coefficient for each vertex.
        for (i = 1; i < dimI; i = i + 1) {
            neighbor = []
            ki = matrix.count(adj, i, 1, i, (dimJ - 1))
            // If the vertex has any neighbor.
            if (ki > 0) {
                // Get all neighbor indexes.
                for (j = 1; j < dimJ; j = j + 1) {
                    if (adj[i, j] == 1) {
                        neighbor.push(j)
                    }
                }
                if (core.length(neighbor) > 0) {
                    // Calculate the number of neighbors connected one to other.
                    n = 0
                    for (n1 = 0; n1 < core.length(neighbor); n1 = n1 + 1) {
                        for (n2 = 0; n2 < core.length(neighbor); n2 = n2 + 1) {
                            if (n1 != n2) {
                                if (adj[neighbor[n1], neighbor[n2]] == 1) {
                                    n = n + 1
                                }
                            }
                        }
                    }
                }
                // Calculate the clustering coefficient to this vertex.
                if ((ki == 0) || (ki == 1)) {
                    clustering[i, 0] = 0
                } else {
                    clustering[i, 0] = n / (ki * (ki - 1))
                }
            }
        }
        return(clustering)
    }
    
    ///
    /// Calculates average clustering coefficient of the network.
    /// @method getAverageClustering
    /// @memberof cna
    /// @param {object}   clustering - Array containing the clustering coefficients of the vertices.
    /// @return {number}  Returns the average clustering coefficient of the network.
    ///
    function getAverageClustering(clustering) {
        dimClustering = core.dim(clustering)
        n = dimClustering[0] - 1
        avgClustering = matrix.sum(clustering) / n
        return(avgClustering)
    }
    
    ///
    /// Calculates the shortest parh between each two vetices in the matrix.
    /// @method getShortestPath
    /// @memberof cna
    /// @param {object}   adj - Adjascence matrix.
    /// @param {boolean}  useGPU - Uses the GPU to speed up calculations.
    /// @return {object}  Returns an array containing the shortest parh between each two vetice in the matrix.
    ///
    function getShortestPath(adj, useGPU) {
        dimAdj = core.dim(adj)
        dimI = dimAdj[0]
        dimJ = dimAdj[1]
        matrixOne = core.one(dimI, dimJ)
        matrixZero = core.zero(dimI, dimJ)

        if (core.type(useGPU) == "undefined") {
            useGPU = false
        }

        //
        // Compute shaders for GPU calculations.
        //

        // This function performs binary multiplication between two matrices a and b.
        kernel mulShader(a, b, n) {
            local c = false
            for (local i = 1; i < n; i = i + 1) {
                c = c || (a[this.thread.y, i] != 0) && (b[i, this.thread.x] != 0)
            }
            if (c) {
                return(1)
            } else {
                return(0)
            }
        }

        // This function performs binary OR between two matrices a and b.
        kernel orShader(a, b) {
            local c = false
            c = (a[this.thread.x, this.thread.y] != 0) || (b[this.thread.x, this.thread.y] != 0)
            if (c) {
                return(1)
            } else {
                return(0)
            }
        }

        if (useGPU) {
            device = gpu.new()

            mulMatrices = device.createKernel(mulShader)
            mulMatrices.setOutput([dimI, dimI])

            orMatrices = device.createKernel(orShader)
            orMatrices.setOutput([dimI, dimI])
        }

        // Remove vertex data from the matrix.
        for (i = 0; i < dimI; i = i + 1) {
            adj[0, i] = 0
            adj[i, 0] = 0
        } 
        geodesic = core.copyMatrix(adj)
        if ((adj == matrixZero) || (adj == matrixOne)) {
            return(geodesic)
        }
        old = core.copyMatrix(adj)
        prod = core.copyMatrix(adj)
        order = 1
        while (true) {
            if (useGPU) {
                prod = mulMatrices(adj, old, dimI)
                path = orMatrices(old, prod)
            } else {
                prod = this.getTransitiveClosure(adj, old)
                path = this.getBooleanOr(old, prod)
            }
            order = order + 1      
            for (i = 1; i < dimI; i = i + 1) {
                for (j = 1; j < dimJ; j = j + 1) {
                    if ((prod[i, j] == 1) && (geodesic[i, j] == 0) && (i != j)) {
                        geodesic[i, j] = order
                    }
                }
            } 
            if (path == matrixOne) {
                break
            } elseif (path == old) {
                break
            } elseif (order == dimI) {
                break
            }
            old = prod
        }
        return(geodesic)
    }
    
    ///
    /// Calculates average shortest path of the network.
    /// @method getAverageShortestPath
    /// @memberof cna
    /// @param {object}   geodesic - Array the shortest parh between each two vetices in the matrix.
    /// @return {number}  Returns the average shortest path of the network.
    ///
    function getAverageShortestPath(geodesic) {
        dimGeodesic = core.dim(geodesic)
        dimI = dimGeodesic[0]
        dimJ = dimGeodesic[1]
        n = matrix.count(geodesic, 1, 1, dimI - 1, dimJ - 1)
        if (n > 0) {
            avgshortestpath = matrix.sum(geodesic, 1, 1, dimI - 1, dimJ - 1) / n
        } else {
            avgshortestpath = 0
        }
        return(avgshortestpath)
    }
    
    ///
    /// Calculates the network diameter.
    /// @method getDiameter
    /// @memberof cna
    /// @param {object}   geodesic - Array the shortest parh between each two vetices in the matrix.
    /// @return {number}  Returns the network diameter.
    ///
    function getDiameter(geodesic) {
        diameter = matrix.max(geodesic)
        return(diameter)
    }
    
    ///
    /// Calculates the shortest parh between each two vetices in the matrix using Floyd Warshall method.
    /// @method getFloydWarshallShortestPath
    /// @memberof cna
    /// @param {object}   adj - Adjascence matrix.
    /// @param {object}   path - Matrix to store paths.
    /// @return {object}  Returns an array containing the shortest parh between each two vetice in the matrix.
    ///
    function getFloydWarshallShortestPath(adj, path) {
        dimAdj = core.dim(adj)
        dimI = dimAdj[0]
        dimJ = dimAdj[1]
        geodesic = core.matrix(Number.MAX_VALUE, dimI, dimJ)
        for (i = 1; i < dimI; i = i + 1) {
            for (j = 1; j < dimJ; j = j + 1) {
                if (adj[i, j] != 0) {
                    geodesic[i, j] = adj[i,j]
                    if (core.type(path) != "undefined") {
                        path[i, j] = j
                    }
                }
            }
        }
        for (i = 0; i < dimI; i = i + 1) {
            geodesic[i, i] = 0
            if (core.type(path) != "undefined") {
                path[i, i] = i
            }
        }
        for (k = 1; k < dimI; k = k + 1) {
            for (i = 1; i < dimI; i = i + 1) {
                for (j = 1; j < dimJ; j = j + 1) {
                    if ((geodesic[i, k] == Number.MAX_VALUE) || (geodesic[k, j] == Number.MAX_VALUE)) {
                        continue
                    }
                    if (geodesic[i, j] > (geodesic[i, k] + geodesic[k, j])) {
                        geodesic[i, j] = geodesic[i, k] + geodesic[k, j]
                        if (core.type(path) != "undefined") {
                            path[i, j] = path[i, k]
                        }
                    }
                }
            }
        }
        for (i = 0; i < dimI; i = i + 1) {
            geodesic[i, i] = 0
            geodesic[0, i] = 0
            geodesic[i, 0] = 0
        }
        # Fix geodesics.
        for (i = 1; i < dimI; i = i + 1) {
            for (j = 1; j < dimI; j = j + 1) {
                if (geodesic[i, j] == Infinity) {
                    geodesic[i, j] = 0
                }
            }
        }
        return(geodesic)
    }
    
    ///
    /// Do a shortest parh walk in the network and returns the path.
    /// @method doShortestPathWalk
    /// @memberof cna
    /// @param {object}   path - Paths matrix.
    /// @param {number}   i - Element index.
    /// @param {number}   j - Element index.
    /// @return {object}  Returns an array containing the shortest parh walk between two vetice in the matrix.
    ///
    function doShortestPathWalk(path, i, j) {
        if (path[i, j] == -1) {
            return([])
        }
        pij = [i]
        while (i != j) {
            i = path[i, j]
            pij.push(i)
        }
        return(pij)
    }
    
    ///
    /// Return the number os geodesics in the network.
    /// @method getNumberOfGeodesics
    /// @memberof cna
    /// @param {object}   adj - Adjascence matrix.
    /// @param {object}   geodesic - Vector including the geodesics of the network.
    /// @param {boolean}  directed - True if the network is directed. False otherwise.
    /// @return {number}  Returns the number os geodesics in the network.
    ///
    function getNumberOfGeodesics(adj, geodesic, directed) {
        dimGeodesic = core.dim(geodesic)
        dimI = dimGeodesic[0]
        dimJ = dimGeodesic[1]
        gjk = core.copyMatrix(adj)
        for (i = 0; i < dimI; i = i + 1) {
            gjk[0, i] = 0
            gjk[i, 0] = 0
            gjk[i, i] = 1
        }
        diameter = this.getDiameter(geodesic)
        // Count how many geodesics are there between vertices i and j.
        for (p = 2; p <= diameter; p = p + 1) {
            npaths = adj ^ p
            for (i = 1; i < dimI; i = i + 1) {
                for (j = 1; j < dimJ; j = j + 1) {
                    if (i != j) {
                        if (gjk[i, j] == 0) {
                            gjk[i, j] = npaths[i, j]
                        }
                    }
                }
            }
        }
        return(gjk)
    }
    
    ///
    /// Returns an array containing the centralities of each pair of the network's vertices.
    /// @method getCentrality
    /// @memberof cna
    /// @param {object}   adj - Adjascence matrix.
    /// @param {object}   geodesic - Vector including the geodesics of the network.
    /// @param {boolean}  directed - True if the network is directed. False otherwise.
    /// @return {object}  Returns an array containing the centralities of each pair of the network's vertices
    ///
    function getCentrality(adj, geodesic, directed) {
        dimGeodesic = core.dim(geodesic)
        dimI = dimGeodesic[0]
        dimJ = dimGeodesic[1]
        // Column 0 contains the closeness centrality,
        // Column 1 contains the betweenness centrality.
        // Column 2 contains the normalized closeness centrality,
        // Column 3 contains the normalized betweenness centrality.
        centrality = core.matrix(0, dimI, 5)
        n = dimI - 1
        // Calculate the closeness centrality for each vertex.
        for (i = 1; i < dimI; i = i + 1) {
            s = matrix.sum(geodesic, i, 1, i, dimJ - 1)
            if (s > 0) {
                centrality[i, 0] = 1 / s
                centrality[i, 2] = (n - 1) / s
                centrality[i, 4] = s
            }
        }
        // Calculate the betweenness centrality for each vertex.
        gjk = this.getNumberOfGeodesics(adj, geodesic, directed)
        for (i = 1; i < dimI; i = i + 1) {
            bc = 0
            for (j = 1; j < dimI; j = j + 1) {
                for (k = 1; k < dimJ; k = k + 1) {
                    // Must insert an if to test if it is a directed graph.
                    if ((j < k) && (j != i) && (i != k)) {
                        spjk = geodesic[j, k]
                        if ((geodesic[j, i] + geodesic[i, k]) == spjk) {
                            bc = bc + math.max(gjk[j, i], gjk[i, k]) / gjk[j, k]
                        }
                    }
                }
            }
            centrality[i, 1] = bc
            // BC normalized: (N-1)(N-2)/2 in symmetric nets or (N-1)(N-2) otherwise.
            centrality[i, 3] = bc / (((n - 1) * (n - 2)) / 2)
        }
        return(centrality)
    }
    
    ///
    /// Returns an array containing the vertex efficiency of each pair of the network's vertices.
    /// @method getVertexEfficiency
    /// @memberof cna
    /// @param {object}   geodesic - Vector including the geodesics of the network.
    /// @return {object}  Returns an array containing the vertex efficiency of each pair of the network's vertices
    ///
    function getVertexEfficiency(geodesic) {
        dimGeodesic = core.dim(geodesic)
        dimI = dimGeodesic[0]
        dimJ = dimGeodesic[1]
        efficiency = core.matrix(0, dimI, dimJ)
        for (i = 1; i < dimI; i = i + 1) {
            for (j = 1; j < dimJ; j = j + 1) {
                if (geodesic[i, j] != 0) {
                    efficiency[i, j] = 1 / geodesic[i, j]
                }
            }
        }
        return(efficiency)
    }
    
    ///
    /// Returns the network's global efficiency.
    /// @method getGlobalEfficiency
    /// @memberof cna
    /// @param {object}   efficiency - Array containing the vertex efficiency of each vertex of the network.
    /// @return {number}  Returns the network's global efficiency.
    ///
    function getGlobalEfficiency(efficiency) {
        dimEfficiency = core.dim(efficiency)
        n = dimEfficiency[0] - 1
        avgeff = matrix.sum(efficiency) / (n * (n - 1))
        return(avgeff)
    }

    ///
    /// Returns the network vertices labels.
    /// @method getLabels
    /// @memberof cna
    /// @param {object}   adj - Adjascence matrix.
    /// @return {object}  Returns the network vertices labels.
    ///
    function getLabels(adj) {
        dimAdj = core.dim(adj)
        dimI = dimAdj[0]
        labels = []
        for (i = 1; i < dimI; i = i + 1) {
            labels[i] = adj[i, 0]
        }
        return(labels)
    }
    
    ///
    /// Sets the network vertices labels.
    /// @method setLabels
    /// @memberof cna
    /// @param {object}  adj - Adjascence matrix.
    /// @param {object}  labels - Array containing the vertices labels.
    /// @return {obj}    Matrix with vertex labels set.
    ///
    function setLabels(adj, labels) {
        dimAdj = core.dim(adj)
        dimI = dimAdj[0]
        for (i = 0; i < dimI; i = i + 1) {
            adj[i, 0] = labels[i]
            adj[0, i] = labels[i]
        }
        return(adj)
    }
    
    ///
    /// Removes the network vertices labels.
    /// @method removeLabels
    /// @memberof cna
    /// @param {object}   adj - Adjascence matrix.
    /// @return {number}  Removes the network vertices labels.
    ///
    function removeLabels(adj) {
        dimAdj = core.dim(adj)
        dimI = dimAdj[0]
        for (i = 0; i < dimI; i = i + 1) {
            adj[i, 0] = 0
            adj[0, i] = 0
        }
        return(adj)
    }
}