Skip to content

Simon-Yule growth

The Simon model of a text that grows from one word, whose word counts follow a power law.

  • Name: simon_yule
  • Field: social/spreading
  • Formalism: agent-based model
  • References: Simon 1955; Yule 1925; Thurner, Hanel, and Klimek 2018

Factory

@register_model(
    field='social/spreading',
    tags=('agents', 'growth', 'power law', 'language'),
    references=('Simon 1955', 'Yule 1925', 'Thurner, Hanel, and Klimek 2018'),
    defaults={'until': 20},
)
def simon_yule(*, innovation: float = 0.1, words_per_step: int = 100) -> System:
    """Return the Simon model of a text that grows from one word, whose word counts follow a power law.

    The rank-frequency exponent is `1 / (1 - innovation)`, and the number of distinct words grows as `innovation`
    times the length of the text.

    Args:
        innovation:
            The probability that the next word is new.

        words_per_step:
            The number of words written per step.

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

    Examples:
        >>> from skcomplex.models.social.spreading import simon_yule
        >>> simon_yule().elements[0].check().prepare().size_
        1
    """
    words = GroupElement(
        'words',
        element=GrowingWordElement,
        size=1,
        values={'count': 1.0, 'innovation': innovation},
        variable=True,
    )
    return System(
        elements=[words],
        interactions=[WritingInteraction('writing', target='words', words_per_step=words_per_step)],
    )

Components

class GrowingWordElement(Element):
    """Distinct word of the Simon model, with the number of times it occurs in the text.

    Args:
        label:
            The label of the agent or group.

        visual:
            How the agent is drawn, or `None` for `default_visual_`.

        count:
            The number of occurrences.

        innovation:
            The probability that the next word of the text is new.

    Examples:
        >>> from skcomplex.core.elements import GroupElement
        >>> from skcomplex.models.social.spreading import GrowingWordElement
        >>> group = GroupElement('words', element=GrowingWordElement, size=1, values={'count': 1.0})
        >>> group.label, group.element.__name__, group.size
        ('words', 'GrowingWordElement', 1)
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        count: Annotated[float, StateSpec(domain=NN)] = 1.0,
        innovation: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
    ) -> None:
        super().__init__(label, visual=visual)
        self.count = count
        self.innovation = innovation

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

class WritingInteraction(Interaction):
    """Writing of the Simon model, a number of words per step.

    Every next word is new with probability `innovation`, and otherwise repeats a random earlier occurrence, so a word
    is repeated with probability proportional to its count.

    Args:
        label:
            The label of the interaction.

        target:
            The path of the group of words.

        words_per_step:
            The number of words written per step.

    Examples:
        >>> from skcomplex.models.social.spreading import WritingInteraction
        >>> WritingInteraction('writing', target='words').words_per_step
        100
    """

    def __init__(
        self,
        label: str,
        *,
        target: Annotated[str, RoleSpec()],
        words_per_step: int = 100,
    ) -> None:
        super().__init__(label)
        self.target = target
        self.words_per_step = words_per_step

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the writing rule.

        Args:
            s:
                The namespace of the symbols the laws can use.

        Returns:
            laws:
                The laws of the component.
        """
        return [RuleLaw(self.write)]

    def write(self, model: ModelView) -> None:
        """Write the words of one step.

        Args:
            model:
                The model.
        """
        words = model.agents(self.target, GrowingWordView)
        if not words:
            return
        innovation = words[0].innovation
        counts = [int(word.count) for word in words]
        tokens = np.repeat(np.arange(len(counts)), counts).tolist()
        for _ in range(self.words_per_step):
            index = len(counts) if model.rng.random() < innovation else tokens[int(model.rng.integers(len(tokens)))]
            if index == len(counts):
                counts.append(0)
            counts[index] += 1
            tokens.append(index)
        for word, count in zip(words, counts, strict=False):
            word.count = float(count)
        for count in counts[len(words) :]:
            model.create(self.target, count=float(count))