예제 #1
0
    def __init__(self, graph, seed=None):
        """
            Model Constructor

            :param graph: A networkx graph object
        """

        np.random.seed(seed)

        self.discrete_state = True

        self.params = {'nodes': {}, 'edges': {}, 'model': {}, 'status': {}}

        self.available_statuses = {
            "Susceptible": 0,
            "Infected": 1,
            "Recovered": 2,
            "Blocked": -1
        }

        self.name = ""

        self.parameters = {"model": {}, "nodes": {}, "edges": {}}

        self.actual_iteration = 0
        self.graph = AGraph(graph)
        self.status = {n: 0 for n in self.graph.nodes}
        self.initial_status = {}
예제 #2
0
    def __init__(self, graph, seed=None):
        """
                    Model Constructor
                    :param graph: A networkx graph object
                """

        np.random.seed(seed)

        self.discrete_state = True

        self.params = {
            'nodes': {},
            'edges': {
                "threshold_1": {
                    "descr": "Infection rate for contagion 1.",
                    "range": [0, 1],
                    "optional": False
                },
                "threshold_2": {
                    "descr": "Infection rate for contagion 2.",
                    "range": [0, 1],
                    "optional": False
                },
                "infection_1_given_2": {
                    "descr":
                    "Infection rate for contagion 1 given infected with contagion 2.",
                    "range": [0, 1],
                    "optional": False
                },
                "infection_2_given_1": {
                    "descr":
                    "Infection rate for contagion 2 given infected with contagion 1.",
                    "range": [0, 1],
                    "optional": False
                },
            },
            'model': {},
            'status': {}
        }
        self.available_statuses = {}
        cnt = 0
        for i in range(3):
            for j in range(3):
                self.available_statuses[(i, j)] = cnt
                cnt += 1
        self.name = "multiple_IC"

        self.actual_iteration = 0
        self.graph = AGraph(graph)
        self.status = {n: 0 for n in self.graph.nodes}
        self.initial_status = {}
예제 #3
0
    def test_has_edge(self):
        g = nx.karate_club_graph()
        ag = AGraph(g)
        n1 = ag.has_edge(0, 1)

        g = from_nx_to_igraph(g)
        ag = AGraph(g)
        n2 = ag.has_edge(0, 1)
        self.assertEqual(n1, n2)
예제 #4
0
    def test_neighbors(self):
        g = nx.karate_club_graph()
        ag = AGraph(g)
        n1 = ag.neighbors(1)

        g = from_nx_to_igraph(g)
        ag = AGraph(g)
        n2 = ag.neighbors(1)
        self.assertListEqual(n1, n2)
예제 #5
0
    def test_number_of_edges(self):
        g = nx.karate_club_graph()
        ag = AGraph(g)
        n1 = ag.number_of_edges()

        g = from_nx_to_igraph(g)
        ag = AGraph(g)
        n2 = ag.number_of_edges()
        self.assertEqual(n1, n2)
예제 #6
0
    def test_successors(self):
        g = nx.karate_club_graph()
        g1 = nx.to_directed(g)
        ag = AGraph(g1)
        n1 = ag.successors(1)

        g = from_nx_to_igraph(g1, directed=True)
        ag = AGraph(g)
        n2 = ag.successors(1)
        self.assertListEqual(n1, n2)
예제 #7
0
class DiffusionModel(object):
    """
        Partial Abstract Class that defines Diffusion Models
    """
    # __metaclass__ = abc.ABCMeta

    def __init__(self, graph, seed=None):
        """
            Model Constructor

            :param graph: A networkx graph object
        """

        np.random.seed(seed)

        self.discrete_state = True

        self.params = {
            'nodes': {},
            'edges': {},
            'model': {},
            'status': {}
        }

        self.available_statuses = {
            "Susceptible": 0,
            "Infected": 1,
            "Recovered": 2, #remove
            "Blocked": -1 #remove
        }

        self.name = ""

        self.parameters = {
            "model": {},
            "nodes": {},
            "edges": {}
        }

        self.actual_iteration = 0
        self.graph = AGraph(graph)
        self.status = {n: 0 for n in self.graph.nodes}
        self.attempt = {n: 0 for n in self.graph.nodes}
        self.initial_status = {}
        self.stop = False

    def __validate_configuration(self, configuration):
        """
        Validate the consistency of a Configuration object for the specific model

        :param configuration: a Configuration object instance
        """
        if "Infected" not in self.available_statuses:
            raise ConfigurationException("'Infected' status not defined.")

        # Checking mandatory parameters
        omp = set([k for k in list(self.parameters['model'].keys()) if not self.parameters['model'][k]['optional']])
        onp = set([k for k in list(self.parameters['nodes'].keys()) if not self.parameters['nodes'][k]['optional']])
        oep = set([k for k in list(self.parameters['edges'].keys()) if not self.parameters['edges'][k]['optional']])
        # print(onp)
        # print(oep)

        mdp = set(configuration.get_model_parameters().keys())
        ndp = set(configuration.get_nodes_configuration().keys())
        edp = set(configuration.get_edges_configuration().keys())
        # print(ndp) #somehow in second influencer selection onwards the param attempt comes up even tho i reset the model and config
        # print(edp)

        if len(omp) > 0:
            if len(omp & mdp) != len(omp):
                raise ConfigurationException({"message": "Missing mandatory model parameter(s)", "parameters": omp-mdp})

        if len(onp) > 0:
            if len(onp & ndp) != len(onp):
                raise ConfigurationException({"message": "Missing mandatory node parameter(s)", "parameters": onp-ndp})

        if len(oep) > 0:
            if len(oep & edp) != len(oep):
                raise ConfigurationException({"message": "Missing mandatory edge parameter(s)", "parameters": oep-edp})

        # Checking optional parameters
        omp = set([k for k in list(self.parameters['model'].keys()) if self.parameters['model'][k]['optional']])
        onp = set([k for k in list(self.parameters['nodes'].keys()) if self.parameters['nodes'][k]['optional']])
        oep = set([k for k in list(self.parameters['edges'].keys()) if self.parameters['edges'][k]['optional']])
        # print(onp)
        # print(oep)

        if len(omp) > 0:
            for param in omp:
                if param not in mdp:
                    configuration.add_model_parameter(param, self.parameters['model'][param]['default'])

        if len(onp) > 0:
            for param in onp:
                # print(param)
                # print(ndp)
                if param not in ndp:
                    
                    for nid in self.graph.nodes:
                        configuration.add_node_configuration(param, nid, self.parameters['nodes'][param]['default'])
                        # print(nid)
                        # print(self.parameters['nodes'][param]['default'])
                        # print('node done')

        if len(oep) > 0:
            for param in oep:
                if param not in edp:
                    for eid in self.graph.edges:
                        configuration.add_edge_configuration(param, eid, self.parameters['edges'][param]['default'])
                        # print(eid)
                        # print(self.parameters['edges'][param]['default'])
                        # print('edge done')
                        

        # Checking initial simulation status
        sts = set(configuration.get_model_configuration().keys())
        if self.discrete_state and "Infected" not in sts and "fraction_infected" not in mdp \
                and "percentage_infected" not in mdp:
            warnings.warn('Initial infection missing: a random sample of 5% of graph nodes will be set as infected')
            self.params['model']["fraction_infected"] = 0.05

    def set_initial_status(self, configuration):
        """
        Set the initial model configuration

        :param configuration: a ```ndlib.models.ModelConfig.Configuration``` object
        """

        self.__validate_configuration(configuration)

        nodes_cfg = configuration.get_nodes_configuration()
        # Set additional node information

        for param, node_to_value in future.utils.iteritems(nodes_cfg):
            if len(node_to_value) < len(self.graph.nodes):
                raise ConfigurationException({"message": "Not all nodes have a configuration specified"})

            self.params['nodes'][param] = node_to_value

        edges_cfg = configuration.get_edges_configuration()
        # Set additional edges information
        for param, edge_to_values in future.utils.iteritems(edges_cfg):
            if len(edge_to_values) == len(self.graph.edges):
                self.params['edges'][param] = {}
                for e in edge_to_values:
                    self.params['edges'][param][e] = edge_to_values[e]

        # Set initial status
        model_status = configuration.get_model_configuration()

        for param, nodes in future.utils.iteritems(model_status):
            self.params['status'][param] = nodes
            for node in nodes:
                self.status[node] = self.available_statuses[param]

        # Set model additional information
        model_params = configuration.get_model_parameters()
        for param, val in future.utils.iteritems(model_params):
            self.params['model'][param] = val

        # Handle initial infection
        if 'Infected' not in self.params['status']:
            if 'percentage_infected' in self.params['model']:
                self.params['model']['fraction_infected'] = self.params['model']['percentage_infected']
            if 'fraction_infected' in self.params['model']:
                number_of_initial_infected = self.graph.number_of_nodes() * float(self.params['model']['fraction_infected'])
                if number_of_initial_infected < 1:
                    warnings.warn(
                        "The fraction_infected value is too low given the number of nodes of the selected graph: a "
                        "single node will be set as infected")
                    number_of_initial_infected = 1

                available_nodes = [n for n in self.status if self.status[n] == 0]
                sampled_nodes = np.random.choice(available_nodes, int(number_of_initial_infected), replace=False)
                for k in sampled_nodes:
                    self.status[k] = self.available_statuses['Infected']

        self.initial_status = self.status

    def clean_initial_status(self, valid_status=None):
        """
        Check the consistency of initial status
        :param valid_status: valid node configurations
        """
        for n, s in future.utils.iteritems(self.status):
            if s not in valid_status:
                self.status[n] = 0

    # def iteration_bunch(self, bunch_size, node_status=True):
    def iteration_bunch(self, node_status=True):
        """
        Execute a bunch of model iterations

        :param bunch_size: the number of iterations to execute
        :param node_status: if the incremental node status has to be returned.

        :return: a list containing for each iteration a dictionary {"iteration": iteration_id, "status": dictionary_node_to_status}
        """
        system_status = []
        all_activated_nodes = []
        step_activated_nodes = []
        while not self.stop:
            its = self.iteration(node_status)
            all_activated_nodes.extend(list(its[1].keys()))
            step_activated_nodes.append(list(its[1].keys()))
            active_set_size = its[0]
        return active_set_size, all_activated_nodes, step_activated_nodes
            
    def random_deactivation(self, activated_nodes):
        """
        Randomly deactivate nodes from the set of activated nodes
        """
        if activated_nodes != []:
            deactivated_nodes = []
            k = random.randint(0, math.ceil(len(activated_nodes) * 0.05))
            deactivated_nodes = random.sample(activated_nodes, k = k)
            print('Number of deactivations: ' + str(k))
            print('Deactivated Nodes: ' + str(deactivated_nodes))
            for node in deactivated_nodes: 
                self.status[node] = 0
                activated_nodes.remove(node)
        return len(activated_nodes), activated_nodes, deactivated_nodes

    def mg_reset(self, temp_activated_nodes):
        # mg reset for mg testing
        #reset nodes that were activated during marginal gain testing
        # need to reset attempted to 0 as well
        # reset self.stop to false so can keep on testing
        for n in temp_activated_nodes:
            self.status[n] = 0
            self.params['nodes']['attempt'][n] = 0
        self.stop = False #so can run again
        # return     
        # return self.status

    def is_reset(self):
        # is reset for influence spread
        # reset self.stop so can proceed with subsequent mg testing and is 
        self.stop = False

    def get_info(self):
        """
        Describes the current model parameters (nodes, edges, status)

        :return: a dictionary containing for each parameter class the values specified during model configuration
        """
        info = {k: v for k, v in future.utils.iteritems(self.params) if k not in ['nodes', 'edges', 'status']}
        if 'infected_nodes' in self.params['status']:
            info['selected_initial_infected'] = True
        return info['model']

    def reset(self, infected_nodes=None):
        """
        Reset the simulation setting the actual status to the initial configuration.
        """
        self.actual_iteration = 0

        if infected_nodes is not None:
            for n in self.status:
                self.status[n] = 0
            for n in infected_nodes:
                self.status[n] = self.available_statuses['Infected']
            self.initial_status = self.status
            self.stop = Falses

        else:
            if 'percentage_infected' in self.params['model']:
                self.params['model']['fraction_infected'] = self.params['model']['percentage_infected']
            if 'fraction_infected' in self.params['model']:
                for n in self.status:
                    self.status[n] = 0
                number_of_initial_infected = self.graph.number_of_nodes() * float(self.params['model']['fraction_infected'])
                available_nodes = [n for n in self.status if self.status[n] == 0]
                sampled_nodes = np.random.choice(available_nodes, int(number_of_initial_infected), replace=False)

                for k in sampled_nodes:
                    self.status[k] = self.available_statuses['Infected']

                self.initial_status = self.status
            else:
                self.status = self.initial_status

        return self

    def get_model_parameters(self):
        return self.parameters

    def get_name(self):
        return self.name

    def get_status_map(self):
        """
        Specify the statuses allowed by the model and their numeric code

        :return: a dictionary (status->code)
        """
        return self.available_statuses

    @abc.abstractmethod
    def iteration(self, node_status=True):
        """
        Execute a single model iteration

        :param node_status: if the incremental node status has to be returned.

        :return: Iteration_id,
                 (optional) Incremental node status (dictionary node->status),
                 Status count (dictionary status->node count),
                 Status delta (dictionary status->node delta)
        """
        pass

    @staticmethod
    def check_status_similarity(actual, previous):
        """
        Evaluate similarity among statuses

        :param actual: actual status
        :param previous: previous status
        :return: True if the two statuses are the same, False otherwise
        """
        for n, v in future.utils.iteritems(actual):
            if n not in previous:
                return False
            if previous[n] != actual[n]:
                return False
        return True

    def status_delta(self, actual_status):
        """
        Compute the point-to-point variations for each status w.r.t. the previous system configuration

        :param actual_status: the actual simulation status
        :return: node that have changed their statuses (dictionary status->nodes),
                 count of actual nodes per status (dictionary status->node count),
                 delta of nodes per status w.r.t the previous configuration (dictionary status->delta)
        """
        actual_status_count = {}
        old_status_count = {}
        delta = {}
        for n, v in future.utils.iteritems(self.status):
            if v != actual_status[n]:
                delta[n] = actual_status[n]

        for st in list(self.available_statuses.values()):
            actual_status_count[st] = len([x for x in actual_status if actual_status[x] == st])
            old_status_count[st] = len([x for x in self.status if self.status[x] == st])

        status_delta = {st: actual_status_count[st] - old_status_count[st] for st in actual_status_count}

        return delta, actual_status_count, status_delta

    def status_delta_continuous(self, actual_status):
        """
        Compute the point-to-point variations for each status w.r.t. the previous system configuration

        Should be used for continuous statuses instead of discrete values

        :param actual_status: the actual simulation status
        :return: nodes that have changed their statuses (dictionary status->nodes),
                    count of actual nodes per status (dictionary status->node count),
                    delta of nodes per status w.r.t the previous configuration (dictionary status->delta)
        """
        delta = {}
        status_delta = {}
        for n, v in future.utils.iteritems(self.status):
            # print(n)
            # print(v)
            delta[n] = {}
            status_delta[n] = {}
            for var, val in list(v.items()):
                if val != actual_status[n][var]:
                    delta[n][var] = actual_status[n][var]
                    status_delta[n][var] = actual_status[n][var] - val
            if len(list(delta[n].values())) == 0:
                del delta[n]
            if len(list(status_delta[n].values())) == 0:
                del status_delta[n]

        return delta, status_delta


    def build_trends(self, iterations):
        """
        Build node status and node delta trends from model iteration bunch

        :param iterations: a set of iterations
        :return: a trend description
        """
        status_delta = {status: [] for status in list(self.available_statuses.values())}
        node_count = {status: [] for status in list(self.available_statuses.values())}

        for it in iterations:
            for st in list(self.available_statuses.values()):
                try:
                    status_delta[st].append(it['status_delta'][st])
                    node_count[st].append(it['node_count'][st])
                except:
                    status_delta[st].append(it['status_delta'][str(st)])
                    node_count[st].append(it['node_count'][str(st)])

        return [{"trends": {"node_count": node_count, "status_delta": status_delta}}]
예제 #8
0
    def test_add_edges(self):
        g = nx.karate_club_graph()
        ag = AGraph(g)
        n1 = ag.neighbors(1)
        ag.add_edges(1, [5, 6])
        n2 = ag.neighbors(1)
        n1.extend([5, 6])
        self.assertListEqual(n2, n1)

        g = nx.karate_club_graph()
        g = from_nx_to_igraph(g)
        ag = AGraph(g)
        n3 = ag.neighbors(1)
        ag.add_edges(1, [5, 6])
        n4 = ag.neighbors(1)
        n3.extend([5, 6])
        self.assertListEqual(sorted(n3), sorted(n4))

        self.assertListEqual(sorted(n2), sorted(n4))
예제 #9
0
    def test_remove_edges(self):
        g = nx.karate_club_graph()
        ag = AGraph(g)
        n1 = ag.neighbors(1)
        ag.remove_edges(1, [0, 2])
        n2 = ag.neighbors(1)
        n1 = [n for n in n1 if n not in [0, 2]]
        self.assertListEqual(n2, n1)

        g = nx.karate_club_graph()
        g = from_nx_to_igraph(g)
        ag = AGraph(g)
        n3 = ag.neighbors(1)
        ag.remove_edges(1, [0, 2])
        n4 = ag.neighbors(1)
        n3 = [n for n in n3 if n not in [0, 2]]
        self.assertListEqual(sorted(n3), sorted(n4))

        self.assertListEqual(sorted(n2), sorted(n4))
예제 #10
0
class multiple_independent_cascade(DiffusionModel):
    def __init__(self, graph, seed=None):
        """
                    Model Constructor
                    :param graph: A networkx graph object
                """

        np.random.seed(seed)

        self.discrete_state = True

        self.params = {
            'nodes': {},
            'edges': {
                "threshold_1": {
                    "descr": "Infection rate for contagion 1.",
                    "range": [0, 1],
                    "optional": False
                },
                "threshold_2": {
                    "descr": "Infection rate for contagion 2.",
                    "range": [0, 1],
                    "optional": False
                },
                "infection_1_given_2": {
                    "descr":
                    "Infection rate for contagion 1 given infected with contagion 2.",
                    "range": [0, 1],
                    "optional": False
                },
                "infection_2_given_1": {
                    "descr":
                    "Infection rate for contagion 2 given infected with contagion 1.",
                    "range": [0, 1],
                    "optional": False
                },
            },
            'model': {},
            'status': {}
        }
        self.available_statuses = {}
        cnt = 0
        for i in range(3):
            for j in range(3):
                self.available_statuses[(i, j)] = cnt
                cnt += 1
        self.name = "multiple_IC"

        self.actual_iteration = 0
        self.graph = AGraph(graph)
        self.status = {n: 0 for n in self.graph.nodes}
        self.initial_status = {}

    def iteration(self, node_status=True):
        """
        Execute a single model iteration
        :return: Iteration_id, Incremental node status (dictionary node->status)
        """
        self.clean_initial_status(self.available_statuses.values())
        actual_status = {
            node: nstatus
            for node, nstatus in future.utils.iteritems(self.status)
        }

        if self.actual_iteration == 0:
            self.actual_iteration += 1
            delta, node_count, status_delta = self.status_delta(actual_status)
            if node_status:
                return {
                    "iteration": 0,
                    "status": actual_status.copy(),
                    "node_count": node_count.copy(),
                    "status_delta": status_delta.copy()
                }
            else:
                return {
                    "iteration": 0,
                    "status": {},
                    "node_count": node_count.copy(),
                    "status_delta": status_delta.copy()
                }

        for u in self.graph.nodes:
            for i in range(2):
                if self.status[u][i] != 1:
                    continue

                neighbors = list(
                    self.graph.neighbors(u)
                )  # neighbors and successors (in DiGraph) produce the same result

                # Standard threshold
                if len(neighbors) > 0:
                    threshold = 1.0 / len(neighbors)

                    for v in neighbors:
                        if actual_status[v][i] == 0:
                            key = (u, v)

                            if self.params['nodes']['com'][u] == self.params[
                                    'nodes']['com'][
                                        v]:  # within same community
                                # Individual specified thresholds
                                if 'threshold_' + str(
                                        i) in self.params['edges']:
                                    if key in self.params['edges']['threshold_'
                                                                   + str(i)]:
                                        threshold = self.params['edges'][
                                            'threshold_' + str(i)][key]
                                    elif (v, u) in self.params['edges'][
                                            'threshold_' +
                                            str(i
                                                )] and not self.graph.directed:
                                        threshold = self.params['edges'][
                                            'threshold_' + str(i)][(v, u)]

                            else:  # across communities
                                p = self.params['model']['permeability']
                                if 'threshold_' + str(
                                        i) in self.params['edges']:
                                    if key in self.params['edges']['threshold_'
                                                                   + str(i)]:
                                        threshold = self.params['edges'][
                                            'threshold_' + str(i)][key] * p
                                    elif (v, u) in self.params['edges'][
                                            'threshold_' +
                                            str(i
                                                )] and not self.graph.directed:
                                        threshold = self.params['edges'][
                                            'threshold_' + str(i)][(v, u)] * p

                            flip = np.random.random_sample()
                            if flip <= threshold:
                                actual_status[v][i] = 1

                actual_status[u][i] = 2

            delta, node_count, status_delta = self.status_delta(actual_status)
            self.status = actual_status
            self.actual_iteration += 1

            if node_status:
                return {
                    "iteration": self.actual_iteration - 1,
                    "status": delta.copy(),
                    "node_count": node_count.copy(),
                    "status_delta": status_delta.copy()
                }
            else:
                return {
                    "iteration": self.actual_iteration - 1,
                    "status": {},
                    "node_count": node_count.copy(),
                    "status_delta": status_delta.copy()
                }