Skip to content

Galam majority

The Galam majority model, where random groups adopt their majority until one opinion wins.

  • Name: galam_majority
  • Field: social/opinion_dynamics
  • Formalism: discrete map
  • References: Galam 2002; Castellano, Fortunato, and Loreto 2009

Factory

@register_model(
    field='social/opinion_dynamics',
    tags=('opinion', 'agents', 'majority', 'consensus'),
    references=('Galam 2002', 'Castellano, Fortunato, and Loreto 2009'),
    defaults={'until': 20},
)
def galam_majority(
    n_agents: int = 300,
    *,
    group_size: int = 3,
    share: float = 0.4,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the Galam majority model, where random groups adopt their majority until one opinion wins.

    Args:
        n_agents:
            The number of agents.

        group_size:
            The number of agents in each discussion group.

        share:
            The share of agents with opinion 1.

        random_state:
            The seed or generator of the opinions.

    Returns:
        system:
            The model, with the group `citizens`.

    Examples:
        >>> from skcomplex.models.social.opinion_dynamics import galam_majority
        >>> galam_majority(30, random_state=0).elements[0].check().prepare().size_
        30
    """
    citizens = GroupElement(
        'citizens',
        element=CitizenElement,
        size=n_agents,
        values={'opinion': _shares(n_agents, share, random_state)},
    )
    return System(
        elements=[citizens],
        interactions=[DiscussionsInteraction('talks', target='citizens', size=group_size)],
    )

Components

class CitizenElement(Element):
    """Agent of the Galam majority model, who holds a binary opinion.

    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 CitizenElement
        >>> group = GroupElement('citizens', element=CitizenElement, size=10, values={'opinion': 1.0})
        >>> group.label, group.element.__name__, group.size
        ('citizens', 'CitizenElement', 10)
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        opinion: Annotated[float, StateSpec(domain=NN)] = 0.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.opinion = opinion

    @classmethod
    def _get_view(cls) -> type[CitizenView]:
        """Return the class of the views of the agents, whose attributes are the states and parameters."""
        return CitizenView

class DiscussionsInteraction(GroupingInteraction):
    """Discussions of the Galam model, which split the citizens into random groups that adopt their majority opinion.

    It is a symbolic `GroupingInteraction` of random partitions, drawn again at every step. A tie in a group of even
    size goes to opinion 0, the status quo.

    Args:
        label:
            The label of the interaction.

        target:
            The path of the group of citizens.

        size:
            The number of citizens in each discussion group, the last group holding the remainder.

    Examples:
        >>> from skcomplex.models.social.opinion_dynamics import DiscussionsInteraction
        >>> DiscussionsInteraction('talks', target='citizens', size=4).check().write
        'opinion'
    """

    def __init__(self, label: str, *, target: Annotated[str, RoleSpec()], size: int = 3) -> None:
        super().__init__(
            label,
            target=target,
            groups='partition',
            size=size,
            form='symbolic',
            write='opinion',
            function=self._get_majority,
        )

    def _get_majority(self, group: SimpleNamespace) -> Expression:
        """Return 1 when more than half of the members of a group hold opinion 1, and 0 otherwise."""
        return where(mean(group.members.opinion, over=group.members) > 1 / 2, 1, 0)

Equations

citizens.opinion[i]: opinion_citizens(t + 1) == where(1/3*opinion_citizens__member + 1/3*opinion_citizens__other0 + 1/3*opinion_citizens__other1 > 0.5, 1, 0)