Voter
A voter system with one voter per node of a network.
- Class:
VoterSystem - Import:
from skcomplex.systems.social.opinion_dynamics import VoterSystem - Field:
social/opinion_dynamics - Topics:
opinion,agents,network - References: Holley and Liggett 1975
Use this system
from skcomplex.core.engine.runtime import simulate
from skcomplex.systems.social.opinion_dynamics import VoterSystem
trajectory = simulate(VoterSystem())
Parameters
| Parameter | Default |
|---|---|
graph |
None |
share |
0.5 |
random_state |
0 |
Tags
| Tag | Values |
|---|---|
| Time | discrete |
| Space | network |
| Laws | update |
| Randomness | stochastic |
| Domains | integer |
| Structure | fixed |
Class
class VoterSystem(BaseSystem):
"""A voter system with one voter per node of a network.
The system has the group `voters`.
Args:
graph:
The NetworkX graph, or `None` for a ring of 50 nodes each linked to its four nearest neighbours.
share:
The share of voters with opinion 1.
random_state:
The seed or generator of the opinions.
label:
The label of the system, which names it when it is nested in another one.
Examples:
>>> import networkx as nx
>>> from skcomplex.systems.social.opinion_dynamics import VoterSystem
>>> VoterSystem(nx.cycle_graph(8), random_state=0).prepare().system_.elements[0].prepare().size_
8
"""
field = 'social/opinion_dynamics'
topics = ('opinion', 'agents', 'network')
references = ('Holley and Liggett 1975',)
until = 100
def __init__(
self,
graph: object = None,
*,
share: float = 0.5,
random_state: int | np.random.Generator | None = 0,
label: str = 'system',
) -> None:
super().__init__(label)
self.graph = graph
self.share = share
self.random_state = random_state
def _build_system(self) -> System:
"""Return the parts of the system built from its model parameters."""
graph = self.graph
rng = np.random.default_rng(self.random_state)
graph = nx.watts_strogatz_graph(50, 4, 0.0) if graph is None else graph
size = nx.number_of_nodes(graph)
opinions = (rng.random(size) < self.share).astype(float)
return System(
self.label,
elements=[GroupElement('voters', element=VoterElement, size=size, values={'opinion': opinions})],
interactions=[VoterImitationInteraction('imitation', target='voters')],
space=NetworkSpace(graph),
)
Components
Elements:
VoterElement, fromElement
Interactions:
VoterImitationInteraction, fromMapInteraction
class VoterElement(Element):
"""Agent of the voter system on a network.
The imitation sets its opinion to that of a random neighbour.
Args:
label:
The label of the agent or group.
visual:
How the agent is drawn, or `None` for `default_visual_`.
opinion:
The initial opinion, 0 or 1.
Examples:
>>> from skcomplex.core.components.elements import GroupElement
>>> from skcomplex.systems.social.opinion_dynamics import VoterElement
>>> group = GroupElement('voters', element=VoterElement, size=20, values={'opinion': 1.0})
>>> group.label, group.element.__name__, group.size
('voters', 'VoterElement', 20)
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'opinion', 'color_range': (0.0, 1.0)}),
*,
opinion: Annotated[float, StateSpec(domain=NN)] = 0.0,
) -> None:
super().__init__(label, visual=visual)
self.opinion = opinion
@classmethod
def _get_view(cls) -> type[VoterView]:
"""Return the class of the views of the agents.
Their attributes are the states and parameters.
"""
return VoterView
class VoterImitationInteraction(MapInteraction):
"""Imitation of the voter system.
Every voter in turn adopts the opinion of a random neighbour. The voters update one after the other in a new random
order every step, each reading the opinions the voters before it set. A voter without a neighbouring voter keeps its
opinion.
Args:
label:
The label of the interaction.
target:
The path of the group of voters.
Examples:
>>> from skcomplex.systems.social.opinion_dynamics import VoterImitationInteraction
>>> VoterImitationInteraction('imitation', target='voters').schedule
'random-sequential'
"""
def __init__(self, label: str, *, target: Annotated[str, RoleSpec()]) -> None:
super().__init__(
label,
target=target,
state='opinion',
function=self._get_opinion,
schedule='random-sequential',
form='agent',
)
def _get_opinion(self, agent: VoterView, system: SystemViewProtocol) -> float:
"""Return the opinion of a random neighbouring voter.
Without one, it is the voter's own.
"""
neighbors = [item for item in agent.neighbors() if isinstance(item, VoterView)]
if not neighbors:
return float(agent.opinion)
return float(neighbors[int(system.rng.integers(len(neighbors)))].opinion)