Sznajd
The Sznajd model, where agreeing pairs of agents convince their neighbours.
- Name:
sznajd - Field:
social/opinion_dynamics - Formalism: agent-based model
- References: Sznajd-Weron and Sznajd 2000; Castellano, Fortunato, and Loreto 2009
Factory
@register_model(
field='social/opinion_dynamics',
tags=('opinion', 'agents', 'network', 'consensus'),
references=('Sznajd-Weron and Sznajd 2000', 'Castellano, Fortunato, and Loreto 2009'),
defaults={'until': 100},
)
def sznajd(
graph: object = None,
*,
share: float = 0.5,
random_state: int | np.random.Generator | None = 0,
) -> System:
"""Return the Sznajd model, where agreeing pairs of agents convince their neighbours.
Args:
graph:
The NetworkX graph, or `None` for a ring of 50 agents.
share:
The share of agents with opinion 1.
random_state:
The seed or generator of the opinions.
Returns:
system:
The model, with the group `people` and the persuasion `persuasion`.
Examples:
>>> import networkx as nx
>>> from skcomplex.models.social.opinion_dynamics import sznajd
>>> sznajd(nx.cycle_graph(8), random_state=0).elements[0].check().prepare().size_
8
"""
graph = nx.cycle_graph(50) if graph is None else graph
size = nx.number_of_nodes(graph)
people = GroupElement(
'people',
element=PersuaderElement,
size=size,
values={'opinion': _shares(size, share, random_state)},
)
return System(
elements=[people],
interactions=[PersuasionInteraction('persuasion', target='people')],
space=NetworkSpace(graph),
)
Components
class PersuaderElement(Element):
"""Agent of the Sznajd model, who convinces the neighbours of an agreeing pair.
A step of an agent picks a random neighbour, and when both share an opinion every neighbour of either adopts it.
On a ring this is the original one-dimensional rule, where the pair convinces the two agents beside it.
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.elements import GroupElement
>>> from skcomplex.models.social.opinion_dynamics import PersuaderElement
>>> group = GroupElement('people', element=PersuaderElement, size=10, values={'opinion': 1.0})
>>> group.label, group.element.__name__, group.size
('people', 'PersuaderElement', 10)
"""
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[PersuaderView]:
"""Return the class of the views of the agents, whose attributes are the states and parameters."""
return PersuaderView
class PersuasionInteraction(GroupingInteraction):
"""Persuasion of the Sznajd model, where an agreeing pair of neighbours convinces the neighbours of both.
Every step every agent, in random order, picks a random neighbour, and when both share an opinion every neighbour
of either adopts it. On a ring this is the original one-dimensional rule, where the pair convinces the two agents
beside it.
Args:
label:
The label of the interaction.
target:
The path of the group of persuaders.
phase:
The phase of the step the persuasion runs in, or `None` for the default phase.
Examples:
>>> from skcomplex.models.social.opinion_dynamics import PersuasionInteraction
>>> PersuasionInteraction('persuasion', target='people').groups
'neighborhood'
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
phase: str | None = None,
) -> None:
super().__init__(label, target=target, groups='neighborhood', phase=phase)
def act_on(self, members: list[AgentViewProtocol], model: ModelViewProtocol) -> None:
"""Convince the neighbours of the first member and a random neighbour of it when the two agree.
Args:
members:
The persuader, followed by its neighbours.
model:
The model view of the rule.
"""
agent, *neighbors = [member for member in members if isinstance(member, PersuaderView)]
if not neighbors:
return
partner = neighbors[int(model.rng.integers(len(neighbors)))]
if partner.opinion != agent.opinion:
return
for other in [*neighbors, *partner.neighbors()]:
if isinstance(other, PersuaderView):
other.opinion = agent.opinion