# Source code for netrd.dynamics.voter

"""
voter.py
--------

Implementation of voter model dynamics on a network.

author: Stefan McCabe

Submitted as part of the 2019 NetSI Collabathon.

"""

from netrd.dynamics import BaseDynamics
import numpy as np
import networkx as nx
from ..utilities import unweighted

[docs]class VoterModel(BaseDynamics):
"""Voter dynamics."""

[docs]    @unweighted
def simulate(self, G, L, noise=None):
r"""Simulate voter-model-style dynamics on a network.

Nodes are randomly assigned a state in :math:\{-1, 1\}; at each
time step all nodes asynchronously update by choosing their new
state uniformly from their neighbors. Generates an :math:N \times
L time series.

The results dictionary also stores the ground truth network as
'ground_truth'.

Parameters
----------
G (nx.Graph)
the input (ground-truth) graph with N nodes.

L (int)
the length of the desired time series.

noise (float, str or None)
if noise is present, with this probability a node's state will
be randomly redrawn from :math:\{-1, 1\} independent of its
neighbors' states. If 'automatic', set noise to :math:1/N.

Returns
-------
TS (np.ndarray)
an :math:N \times L array of synthetic time series data.

"""

N = G.number_of_nodes()

if noise is None:
noise = 0
elif noise == 'automatic' or noise == 'auto':
noise = 1 / N
elif not isinstance(noise, (int, float)):
raise ValueError("noise must be a number, 'automatic', or None")

transitions = nx.to_numpy_array(G)
transitions = transitions / np.sum(transitions, axis=0)

TS = np.zeros((N, L))
TS[:, 0] = [1 if x < 0.5 else -1 for x in np.random.rand(N)]
indices = np.arange(N)

for t in range(1, L):
np.random.shuffle(indices)
TS[:, t] = TS[:, t - 1]
for i in indices:
TS[i, t] = np.random.choice(TS[:, t], p=transitions[:, i])
if np.random.rand() < noise:
TS[i, t] = 1 if np.random.rand() < 0.5 else -1

self.results['ground_truth'] = G
self.results['TS'] = TS
return TS