Skip to content

Commit

Permalink
add Hegselmann-Krause model
Browse files Browse the repository at this point in the history
  • Loading branch information
letiziam committed Feb 16, 2021
1 parent 1216547 commit 3c0e171
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 7 deletions.
3 changes: 1 addition & 2 deletions docs/reference/models/opinion/ARWHK.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ Example
-------

In the code below is shown an example of instantiation and execution of an ARWHK model simulation on a
random graph: we set the initial set of infected nodes as 1% of the overall population,
assign an epsilon value of 0.32, the percentage of stubborness equal 0.2, the distribution of stubborness equal 0
random graph: we assign an epsilon value of 0.32, the percentage of stubborness equal 0.2, the distribution of stubborness equal 0
and a weight equal 0.2 to all the edges.


Expand Down
95 changes: 95 additions & 0 deletions docs/reference/models/opinion/HK.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
************************************************
Attraction-Repulsion Weighted Hegselmann-Krause
************************************************

The Hegselmann-Krause model was introduced in 2002 by Hegselmann, Krause et al [#]_.

During each interaction a random agenti is selected and the set :math:`\Gamma_{\epsilon}` of its neighbors whose
opinions differ at most :math:`\epsilon` (:math:`d_{i,j}=|x_i(t)-x_j(t)|\leq \epsilon`) is identified.
The selected agent i changes its opinion based on the following update rule:

.. math::
x_i(t+1)= \frac{\sum_{j \in \Gamma_{\epsilon}} x_j(t)}{\#\Gamma_{\epsilon}}
The idea behind the WHK formulation is that the opinion of agent :math:`i` at time :math:`t+1`, will be given by the average
opinion by its, selected, :math:`\epsilon`-neighbor.

--------
Statuses
--------

Node statuses are continuous values in [-1,1].

----------
Parameters
----------

=========================== ===== ========================= ======= ========= ==============================================
Name Type Value Type Default Mandatory Description
=========================== ===== ========================= ======= ========= ==============================================
epsilon Model float in [0, 1] --- True Bounded confidence threshold
=========================== ===== ========================= ======= ========= ==============================================

-------
Methods
-------

The following class methods are made available to configure, describe and execute the simulation:

^^^^^^^^^
Configure
^^^^^^^^^

.. autoclass:: ndlib.models.opinions.HKModel.HKModel
.. automethod:: ndlib.models.opinions.HKModel.HKModel.__init__(graph)

.. automethod:: ndlib.models.opinions.HKModel.HKModel.set_initial_status(self, configuration)
.. automethod:: ndlib.models.opinions.HKModel.HKModel.reset(self)

^^^^^^^^
Describe
^^^^^^^^

.. automethod:: ndlib.models.opinions.HKModel.HKModel.get_info(self)
.. automethod:: ndlib.models.opinions.HKModel.HKModel.get_status_map(self)

^^^^^^^^^^^^^^^^^^
Execute Simulation
^^^^^^^^^^^^^^^^^^
.. automethod:: ndlib.models.opinions.HKModel.HKModel.iteration(self)
.. automethod:: ndlib.models.opinions.HKModel.HKModel.iteration_bunch(self, bunch_size)


-------
Example
-------

In the code below is shown an example of instantiation and execution of an HK model simulation on a random graph:
we an epsilon value of 0.32 .


.. code-block:: python
import networkx as nx
import ndlib.models.ModelConfig as mc
import ndlib.models.opinions as opn
# Network topology
g = nx.erdos_renyi_graph(1000, 0.1)
# Model selection
model = opn.HKModel(g)
# Model Configuration
config = mc.Configuration()
config.add_model_parameter("epsilon", 0.32)
model.set_initial_status(config)
# Simulation execution
iterations = model.iteration_bunch(20)
.. [#] R. Hegselmann, U. Krause, et al.: “Opinion dynamics and bounded confidence models, analysis, and simulation." in Journal of artificial societies and social simulation, 2002
10 changes: 5 additions & 5 deletions docs/reference/models/opinion/WHK.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ Weighted Hegselmann-Krause
The Weighted Hegselmann-Krause was introduced by Milli et al. in 2021 [#]_.

This model is a variation of the well-known Hegselmann-Krause (HK).
Conversely from the HK model, during each iteration WHK consider a random pair-wise interaction involving agents at distance :math:`\epsilon`.
During each interaction a random agenti is selected and the set :math:`\Gamma_{\epsilon}` of its neighbors whose
opinions differ at most :math:`\epsilon` (:math:`d_{i,j}=|x_i(t)-x_j(t)|\leq \epsilon`) is identified.
Moreover, to account for the heterogeneity of interaction frequency among agent pairs, WHK leverages edge weights, thus capturing the effect of different social bonds' strength/trust as it happens in reality.
To such extent, each edge :math:`(i,j) \in E`, carries a value :math:`w_{i,j}\in [0,1]`.
The update rule then becomes:

.. math::
x_i(t+1)= \left\{ \begin{array}{ll}
x_i(t) + \frac{x_i(t) + x_j(t)w_{i,j}}{2} (1-x_i(t)) & \quad \quad \mbox{if } x_i(t) \geq 0\\
x_i(t) + \frac{x_i(t) + x_j(t)w_{i,j}}{2} (1+x_i(t)) & \quad \quad \mbox{if } x_i(t) < 0
x_i(t) + \frac{\sum_{j \in \Gamma_{\epsilon}} x_j(t)w_{ij}}{\#\Gamma_{\epsilon}} (1-x_i(t)) \quad \quad \text{\quad if x_i(t) \geq 0}\\
x_i(t) + \frac{\sum_{j \in \Gamma_{\epsilon}} x_j(t)w_{ij}}{\#\Gamma_{\epsilon}} (1+x_i(t)) \quad \text{if x_i(t) < 0 }
\end{array}
\right.
Expand Down Expand Up @@ -78,8 +79,7 @@ Example
-------

In the code below is shown an example of instantiation and execution of an WHK model simulation on a random graph:
we set the initial set of infected nodes as 1% of the overall population,
assign an epsilon value of 0.32 and a weight equal 0.2 to all the edges.
we an epsilon value of 0.32 and a weight equal 0.2 to all the edges.


.. code-block:: python
Expand Down
1 change: 1 addition & 0 deletions docs/reference/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ In ``NDlib`` are implemented the following **Opinion Dynamics** models:
models/opinion/AlgorithmicBias.rst
models/opinion/ARWHK.rst
models/opinion/WHK.rst
models/opinion/HK.rst



Expand Down
119 changes: 119 additions & 0 deletions ndlib/models/opinions/HKModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from ndlib.models.DiffusionModel import DiffusionModel
import future.utils
import numpy as np
import random
from sklearn.metrics import jaccard_score

__author__ = ['Letizia Milli']
__license__ = "BSD-2-Clause"

class HKModel(DiffusionModel):
"""
Model Parameters to be specified via ModelConfig
:param epsilon: bounded confidence threshold from the HK model (float in [0,1])
"""

def __init__(self, graph):
"""
Model Constructor
:param graph: A networkx graph object
"""
super(self.__class__, self).__init__(graph)
self.discrete_state = False

self.available_statuses = {
"Infected": 0
}

self.parameters = {
"model": {
"epsilon": {
"descr": "Bounded confidence threshold",
"range": [0, 1],
"optional": False,
}
},
"edges": {},
"nodes": {},
}
self.name = "Hegselmann-Krause"

def set_initial_status(self, configuration=None):
"""
Override behaviour of methods in class DiffusionModel.
Overwrites initial status using random real values.
"""
super(HKModel, self).set_initial_status(configuration)

# set node status
for node in self.status:
self.status[node] = random.uniform(-1, 1)
self.initial_status = self.status.copy()


def clean_initial_status(self, valid_status=None):
for n, s in future.utils.iteritems(self.status):
if s > 1 or s < -1:
self.status[n] = 0.0

def iteration(self, node_status=True):

'''
Execute a single model iteration
:return: Iteration_id, Incremental node status (dictionary code -> status)
'''
# An iteration changes the opinion of the selected agent 'i' .

self.clean_initial_status(None)

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(self.status)
if node_status:
return {"iteration": 0, "status": self.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 i in range(0, self.graph.number_of_nodes()):
# select a random node
n1 = list(self.graph.nodes)[np.random.randint(0, self.graph.number_of_nodes())]

# select neighbors of n1
neighbours = list(self.graph.neighbors(n1))
sum_op = 0
count_in_eps = 0

if len(neighbours) == 0:
continue

for neigh in neighbours:
# compute the difference between opinions
diff_opinion = np.abs((actual_status[n1]) - (actual_status[neigh]))
if diff_opinion < self.params['model']['epsilon']:
sum_op += actual_status[neigh]
# count_in_eps is the number of neighbors in epsilon
count_in_eps += 1

if (count_in_eps > 0):
new_op = sum_op / float(count_in_eps)
else:
# if there aren't neighbors in epsilon, the status of n1 doesn't change
new_op = actual_status[n1]

actual_status[n1] = new_op

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()}
2 changes: 2 additions & 0 deletions ndlib/models/opinions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .VoterModel import VoterModel
from .WHKModel import WHKModel
from .ARWHKModel import ARWHKModel
from .HKModel import HKModel


__all__ = [
Expand All @@ -25,4 +26,5 @@
'VoterModel',
'WHKModel',
'ARWHKModel',
'HKModel'
]
13 changes: 13 additions & 0 deletions ndlib/test/test_ndlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,19 @@ def test_whk_model(self):
iterations = model.iteration_bunch(3)
self.assertEqual(len(iterations), 3)

def test_hk_model(self):
for g in get_graph():
model = opn.HKModel(g)
config = mc.Configuration()
config.add_model_parameter("epsilon", 0.32)

model.set_initial_status(config)

iterations = model.iteration_bunch(10)
self.assertEqual(len(iterations), 10)
iterations = model.iteration_bunch(10, node_status=False)
self.assertEqual(len(iterations), 10)

def test_arwhk_model(self):
for g in get_graph():
model = opn.ARWHKModel(g)
Expand Down

0 comments on commit 3c0e171

Please sign in to comment.