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)