/* -*- mode: C -*-  */
/*
   IGraph library.
   Copyright (C) 2009-2012  Gabor Csardi <csardi.gabor@gmail.com>
   334 Harvard street, Cambridge, MA 02139 USA

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301 USA

*/

#include "igraph_mixing.h"

#include "igraph_interface.h"

/**
 * \function igraph_assortativity_nominal
 * Assortativity of a graph based on vertex categories
 *
 * Assuming the vertices of the input graph belong to different
 * categories, this function calculates the assortativity coefficient of
 * the graph. The assortativity coefficient is between minus one and one
 * and it is one if all connections stay within categories, it is
 * minus one, if the network is perfectly disassortative. For a
 * randomly connected network it is (asymptotically) zero.
 *
 * </para><para>See equation (2) in M. E. J. Newman: Mixing patterns
 * in networks, Phys. Rev. E 67, 026126 (2003)
 * (http://arxiv.org/abs/cond-mat/0209450) for the proper
 * definition.
 *
 * \param graph The input graph, it can be directed or undirected.
 * \param types Vector giving the vertex types. They are assumed to be
 *    integer numbers, starting with zero.
 * \param res Pointer to a real variable, the result is stored here.
 * \param directed Boolean, it gives whether to consider edge
 *    directions in a directed graph. It is ignored for undirected
 *    graphs.
 * \return Error code.
 *
 * Time complexity: O(|E|+t), |E| is the number of edges, t is the
 * number of vertex types.
 *
 * \sa \ref igraph_assortativity if the vertex types are defines by
 * numeric values (e.g. vertex degree), instead of categories.
 *
 * \example examples/simple/assortativity.c
 */

int igraph_assortativity_nominal(const igraph_t *graph,
                                 const igraph_vector_t *types,
                                 igraph_real_t *res,
                                 igraph_bool_t directed) {

    long int no_of_nodes = igraph_vcount(graph);
    long int no_of_edges = igraph_ecount(graph);
    long int no_of_types;
    igraph_vector_t ai, bi, eii;
    long int e, i;
    igraph_real_t sumaibi = 0.0, sumeii = 0.0;

    if (igraph_vector_size(types) != no_of_nodes) {
        IGRAPH_ERROR("Invalid `types' vector length", IGRAPH_EINVAL);
    }

    if (no_of_nodes == 0) {
        *res = IGRAPH_NAN;
        return IGRAPH_SUCCESS;
    }

    /* 'types' length > 0 here, safe to call vector_min() */
    if (igraph_vector_min(types) < 0) {
        IGRAPH_ERROR("Invalid `types' vector", IGRAPH_EINVAL);
    }

    directed = directed && igraph_is_directed(graph);

    no_of_types = (long int) igraph_vector_max(types) + 1;
    IGRAPH_VECTOR_INIT_FINALLY(&ai, no_of_types);
    IGRAPH_VECTOR_INIT_FINALLY(&bi, no_of_types);
    IGRAPH_VECTOR_INIT_FINALLY(&eii, no_of_types);

    for (e = 0; e < no_of_edges; e++) {
        long int from = IGRAPH_FROM(graph, e);
        long int to = IGRAPH_TO(graph, e);
        long int from_type = (long int) VECTOR(*types)[from];
        long int to_type = (long int) VECTOR(*types)[to];

        VECTOR(ai)[from_type] += 1;
        VECTOR(bi)[to_type] += 1;
        if (from_type == to_type) {
            VECTOR(eii)[from_type] += 1;
        }
        if (!directed) {
            if (from_type == to_type) {
                VECTOR(eii)[from_type] += 1;
            }
            VECTOR(ai)[to_type] += 1;
            VECTOR(bi)[from_type] += 1;
        }
    }

    for (i = 0; i < no_of_types; i++) {
        sumaibi += (VECTOR(ai)[i] / no_of_edges) * (VECTOR(bi)[i] / no_of_edges);
        sumeii  += (VECTOR(eii)[i] / no_of_edges);
    }

    if (!directed) {
        sumaibi /= 4.0;
        sumeii  /= 2.0;
    }

    *res = (sumeii - sumaibi) / (1.0 - sumaibi);

    igraph_vector_destroy(&eii);
    igraph_vector_destroy(&bi);
    igraph_vector_destroy(&ai);
    IGRAPH_FINALLY_CLEAN(3);

    return 0;
}

/**
 * \function igraph_assortativity
 * Assortativity based on numeric properties of vertices
 *
 * This function calculates the assortativity coefficient of the input
 * graph. This coefficient is basically the correlation between the
 * actual connectivity patterns of the vertices and the pattern
 * expected from the distribution of the vertex types.
 *
 * </para><para>See equation (21) in M. E. J. Newman: Mixing patterns
 * in networks, Phys. Rev. E 67, 026126 (2003)
 * (http://arxiv.org/abs/cond-mat/0209450) for the proper
 * definition. The actual calculation is performed using equation (26)
 * in the same paper for directed graphs, and equation (4) in
 * M. E. J. Newman: Assortative mixing in networks,
 * Phys. Rev. Lett. 89, 208701 (2002)
 * (http://arxiv.org/abs/cond-mat/0205405/) for undirected graphs.
 *
 * \param graph The input graph, it can be directed or undirected.
 * \param types1 The vertex values, these can be arbitrary numeric
 *     values.
 * \param types2 A second value vector to be using for the incoming
 *     edges when calculating assortativity for a directed graph.
 *     Supply a null pointer here if you want to use the same values
 *     for outgoing and incoming edges. This argument is ignored
 *     (with a warning) if it is not a null pointer and undirected
 *     assortativity coefficient is being calculated.
 * \param res Pointer to a real variable, the result is stored here.
 * \param directed Boolean, whether to consider edge directions for
 *     directed graphs. It is ignored for undirected graphs.
 * \return Error code.
 *
 * Time complexity: O(|E|), linear in the number of edges of the
 * graph.
 *
 * \sa \ref igraph_assortativity_nominal() if you have discrete vertex
 * categories instead of numeric labels, and \ref
 * igraph_assortativity_degree() for the special case of assortativity
 * based on vertex degree.
 *
 * \example examples/simple/assortativity.c
 */

int igraph_assortativity(const igraph_t *graph,
                         const igraph_vector_t *types1,
                         const igraph_vector_t *types2,
                         igraph_real_t *res,
                         igraph_bool_t directed) {

    long int no_of_nodes = igraph_vcount(graph);
    long int no_of_edges = igraph_ecount(graph);
    long int e;

    directed = directed && igraph_is_directed(graph);

    if (!directed && types2) {
        IGRAPH_WARNING("Only `types1' is used for undirected case");
    }

    if (igraph_vector_size(types1) != no_of_nodes) {
        IGRAPH_ERROR("Invalid `types1' vector length", IGRAPH_EINVAL);
    }

    if (types2 && igraph_vector_size(types2) != no_of_nodes) {
        IGRAPH_ERROR("Invalid `types2' vector length", IGRAPH_EINVAL);
    }

    if (!directed) {
        igraph_real_t num1 = 0.0, num2 = 0.0, den1 = 0.0;

        for (e = 0; e < no_of_edges; e++) {
            long int from = IGRAPH_FROM(graph, e);
            long int to = IGRAPH_TO(graph, e);
            igraph_real_t from_type = VECTOR(*types1)[from];
            igraph_real_t to_type = VECTOR(*types1)[to];

            num1 += from_type * to_type;
            num2 += from_type + to_type;
            den1 += from_type * from_type + to_type * to_type;
        }

        num1 /= no_of_edges;
        den1 /= no_of_edges * 2;
        num2 /= no_of_edges * 2;
        num2 = num2 * num2;

        *res = (num1 - num2) / (den1 - num2);

    } else {
        igraph_real_t num1 = 0.0, num2 = 0.0, num3 = 0.0,
                      den1 = 0.0, den2 = 0.0;
        igraph_real_t num, den;

        if (!types2) {
            types2 = types1;
        }

        for (e = 0; e < no_of_edges; e++) {
            long int from = IGRAPH_FROM(graph, e);
            long int to = IGRAPH_TO(graph, e);
            igraph_real_t from_type = VECTOR(*types1)[from];
            igraph_real_t to_type = VECTOR(*types2)[to];

            num1 += from_type * to_type;
            num2 += from_type;
            num3 += to_type;
            den1 += from_type * from_type;
            den2 += to_type * to_type;
        }

        num = num1 - num2 * num3 / no_of_edges;
        den = sqrt(den1 - num2 * num2 / no_of_edges) *
              sqrt(den2 - num3 * num3 / no_of_edges);

        *res = num / den;
    }

    return 0;
}

/**
 * \function igraph_assortativity_degree
 * Assortativity of a graph based on vertex degree
 *
 * Assortativity based on vertex degree, please see the discussion at
 * the documentation of \ref igraph_assortativity() for details.
 *
 * \param graph The input graph, it can be directed or undirected.
 * \param res Pointer to a real variable, the result is stored here.
 * \param directed Boolean, whether to consider edge directions for
 *     directed graphs. This argument is ignored for undirected
 *     graphs. Supply 1 (=TRUE) here to do the natural thing, i.e. use
 *     directed version of the measure for directed graphs and the
 *     undirected version for undirected graphs.
 * \return Error code.
 *
 * Time complexity: O(|E|+|V|), |E| is the number of edges, |V| is
 * the number of vertices.
 *
 * \sa \ref igraph_assortativity() for the general function
 * calculating assortativity for any kind of numeric vertex values.
 *
 * \example examples/simple/assortativity.c
 */

int igraph_assortativity_degree(const igraph_t *graph,
                                igraph_real_t *res,
                                igraph_bool_t directed) {

    directed = directed && igraph_is_directed(graph);

    if (directed) {
        igraph_vector_t indegree, outdegree;
        igraph_vector_init(&indegree, 0);
        igraph_vector_init(&outdegree, 0);
        igraph_degree(graph, &indegree, igraph_vss_all(), IGRAPH_IN, /*loops=*/ 1);
        igraph_degree(graph, &outdegree, igraph_vss_all(), IGRAPH_OUT, /*loops=*/ 1);
        igraph_vector_add_constant(&indegree, -1);
        igraph_vector_add_constant(&outdegree, -1);
        igraph_assortativity(graph, &outdegree, &indegree, res, /*directed=*/ 1);
        igraph_vector_destroy(&indegree);
        igraph_vector_destroy(&outdegree);
    } else {
        igraph_vector_t degree;
        igraph_vector_init(&degree, 0);
        igraph_degree(graph, &degree, igraph_vss_all(), IGRAPH_ALL, /*loops=*/ 1);
        igraph_vector_add_constant(&degree, -1);
        igraph_assortativity(graph, &degree, 0, res, /*directed=*/ 0);
        igraph_vector_destroy(&degree);
    }

    return 0;
}
