Skip to content

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