/ndlib/models/opinions/CognitiveOpDynModel.py

https://github.com/GiulioRossetti/ndlib · Python · 221 lines · 145 code · 28 blank · 48 comment · 27 complexity · efe3c14270ff04413f7ef0df7a594cc7 MD5 · raw file

  1. from ..DiffusionModel import DiffusionModel
  2. import numpy as np
  3. import future.utils
  4. __author__ = "Alina Sirbu"
  5. __email__ = "alina.sirbu@unipi.it"
  6. class CognitiveOpDynModel(DiffusionModel):
  7. """
  8. Model Parameters to be specified via ModelConfig
  9. :param I: external information value in [0,1]
  10. :param T_range_min: the minimum of the range of initial values for T. Range [0,1].
  11. :param T_range_max: the maximum of the range of initial values for T. Range [0,1].
  12. :param B_range_min: the minimum of the range of initial values for B. Range [0,1]
  13. :param B_range_max: the maximum of the range of initial values for B. Range [0,1].
  14. :param R_fraction_negative: fraction of individuals having the node parameter R=-1.
  15. :param R_fraction_positive: fraction of individuals having the node parameter R=1
  16. :param R_fraction_neutral: fraction of individuals having the node parameter R=0
  17. The following relation should hold: R_fraction_negative+R_fraction_neutral+R_fraction_positive=1.
  18. To achieve this, the fractions selected will be normalised to sum 1.
  19. Node states are continuous values in [0,1].
  20. The initial state is generated randomly uniformly from the domain defined by model parameters.
  21. """
  22. def __init__(self, graph, seed=None):
  23. """
  24. Model Constructor
  25. :param graph: A networkx graph object
  26. """
  27. super(self.__class__, self).__init__(graph, seed)
  28. self.discrete_state = False
  29. self.available_statuses = {
  30. "Infected": 0
  31. }
  32. self.parameters = {
  33. "model": {
  34. "I": {
  35. "descr": "External information",
  36. "range": [0, 1],
  37. "optional": False
  38. },
  39. "T_range_min": {
  40. "descr": "Minimum of the range of initial values for T",
  41. "range": [0, 1],
  42. "optional": False
  43. },
  44. "T_range_max": {
  45. "descr": "Maximum of the range of initial values for T",
  46. "range": [0, 1],
  47. "optional": False
  48. },
  49. "B_range_min": {
  50. "descr": "Minimum of the range of initial values for B",
  51. "range": [0, 1],
  52. "optional": False
  53. },
  54. "B_range_max": {
  55. "descr": "Maximum of the range of initial values for B",
  56. "range": [0, 1],
  57. "optional": False
  58. },
  59. "R_fraction_negative": {
  60. "descr": "Fraction of nodes having R=-1",
  61. "range": [0, 1],
  62. "optional": False
  63. },
  64. "R_fraction_neutral": {
  65. "descr": "Fraction of nodes having R=0",
  66. "range": [0, 1],
  67. "optional": False
  68. },
  69. "R_fraction_positive": {
  70. "descr": "Fraction of nodes having R=1",
  71. "range": [0, 1],
  72. "optional": False
  73. }
  74. },
  75. "nodes": {},
  76. "edges": {}
  77. }
  78. self.name = "Cognitive Opinion Dynamics"
  79. def set_initial_status(self, configuration=None):
  80. """
  81. Override behaviour of methods in class DiffusionModel.
  82. Overwrites initial status using random real values.
  83. Generates random node profiles.
  84. """
  85. super(CognitiveOpDynModel, self).set_initial_status(configuration)
  86. # set node status
  87. for node in self.status:
  88. self.status[node] = np.random.random_sample()
  89. self.initial_status = self.status.copy()
  90. # set new node parameters
  91. self.params['nodes']['cognitive'] = {}
  92. # first correct the input model parameters and retreive T_range, B_range and R_distribution
  93. T_range = (self.params['model']['T_range_min'], self.params['model']['T_range_max'])
  94. if self.params['model']['T_range_min'] > self.params['model']['T_range_max']:
  95. T_range = (self.params['model']['T_range_max'], self.params['model']['T_range_min'])
  96. B_range = (self.params['model']['B_range_min'], self.params['model']['B_range_max'])
  97. if self.params['model']['B_range_min'] > self.params['model']['B_range_max']:
  98. B_range = (self.params['model']['B_range_max'], self.params['model']['B_range_min'])
  99. s = float(self.params['model']['R_fraction_negative'] + self.params['model']['R_fraction_neutral'] +
  100. self.params['model']['R_fraction_positive'])
  101. R_distribution = (self.params['model']['R_fraction_negative']/s, self.params['model']['R_fraction_neutral']/s,
  102. self.params['model']['R_fraction_positive']/s)
  103. # then sample parameters from the ranges and distribution
  104. for node in self.graph.nodes:
  105. R_prob = np.random.random_sample()
  106. if R_prob < R_distribution[0]:
  107. R = -1
  108. elif R_prob < (R_distribution[0] + R_distribution[1]):
  109. R = 0
  110. else:
  111. R = 1
  112. # R, B and T parameters in a tuple
  113. self.params['nodes']['cognitive'][node] = (R,
  114. B_range[0] + (B_range[1] - B_range[0])*np.random.random_sample(),
  115. T_range[0] + (T_range[1] - T_range[0])*np.random.random_sample())
  116. def clean_initial_status(self, valid_status=None):
  117. for n, s in future.utils.iteritems(self.status):
  118. if s > 1 or s < 0:
  119. self.status[n] = 0
  120. def iteration(self, node_status=True):
  121. """
  122. Execute a single model iteration
  123. :return: Iteration_id, Incremental node status (dictionary node->status)
  124. """
  125. # One iteration changes the opinion of all agents using the following procedure:
  126. # - first all agents communicate with institutional information I using a deffuant like rule
  127. # - then random pairs of agents are selected to interact (N pairs)
  128. # - interaction depends on state of agents but also internal cognitive structure
  129. self.clean_initial_status(None)
  130. actual_status = {node: nstatus for node, nstatus in future.utils.iteritems(self.status)}
  131. if self.actual_iteration == 0:
  132. self.actual_iteration += 1
  133. delta, node_count, status_delta = self.status_delta(self.status)
  134. if node_status:
  135. return {"iteration": 0, "status": self.status.copy(),
  136. "node_count": node_count.copy(), "status_delta": status_delta.copy()}
  137. else:
  138. return {"iteration": 0, "status": {},
  139. "node_count": node_count.copy(), "status_delta": status_delta.copy()}
  140. # first interact with I
  141. I = self.params['model']['I']
  142. for node in self.graph.nodes:
  143. T = self.params['nodes']['cognitive'][node][2]
  144. R = self.params['nodes']['cognitive'][node][0]
  145. actual_status[node] = actual_status[node] + T * (I - actual_status[node])
  146. if R == 1:
  147. actual_status[node] = 0.5 * (1 + actual_status[node])
  148. if R == -1:
  149. actual_status[node] *= 0.5
  150. # then interact with peers
  151. for i in range(0, self.graph.number_of_nodes()):
  152. # select a random node
  153. n1 = list(self.graph.nodes)[np.random.randint(0, self.graph.number_of_nodes())]
  154. # select all of the nodes neighbours (no digraph possible)
  155. neighbours = list(self.graph.neighbors(n1))
  156. if len(neighbours) == 0:
  157. continue
  158. # select second node - a random neighbour
  159. n2 = neighbours[np.random.randint(0, len(neighbours))]
  160. # update status of n1 and n2
  161. p1 = pow(actual_status[n1], 1.0 / self.params['nodes']['cognitive'][n1][1])
  162. p2 = pow(actual_status[n2], 1.0 / self.params['nodes']['cognitive'][n2][1])
  163. oldn1 = self.status[n1]
  164. if np.random.random_sample() < p2: # if node 2 talks, node 1 gets changed
  165. T1 = self.params['nodes']['cognitive'][n1][2]
  166. R1 = self.params['nodes']['cognitive'][n1][0]
  167. actual_status[n1] += (1 - T1) * (actual_status[n2] - actual_status[n1])
  168. if R1 == 1:
  169. actual_status[n1] = 0.5 * (1 + actual_status[n1])
  170. if R1 == -1:
  171. actual_status[n1] *= 0.5
  172. if np.random.random_sample() < p1: # if node 1 talks, node 2 gets changed
  173. T2 = self.params['nodes']['cognitive'][n2][2]
  174. R2 = self.params['nodes']['cognitive'][n2][0]
  175. actual_status[n2] += (1 - T2) * (oldn1 - actual_status[n2])
  176. if R2 == 1:
  177. actual_status[n2] = 0.5 * (1 + actual_status[n2])
  178. if R2 == -1:
  179. actual_status[n2] *= 0.5
  180. delta, node_count, status_delta = self.status_delta(actual_status)
  181. self.status = actual_status
  182. self.actual_iteration += 1
  183. if node_status:
  184. return {"iteration": self.actual_iteration - 1, "status": delta.copy(),
  185. "node_count": node_count.copy(), "status_delta": status_delta.copy()}
  186. else:
  187. return {"iteration": self.actual_iteration - 1, "status": {},
  188. "node_count": node_count.copy(), "status_delta": status_delta.copy()}