Skip to content

Language

Language: the statistics of words from the economy of their use.

LanguageElement(label, exponent=1.0)

Bases: Element

Language with the exponent lambda of its word frequencies.

Parameters:

Name Type Description Default
label str

The label of the language.

required
exponent Annotated[float, StateSpec()]

The initial guess of the exponent, from which it is solved.

1.0

Examples:

>>> from skcomplex.systems.social.language import LanguageElement
>>> LanguageElement('language').exponent
1.0
Source code in src/skcomplex/systems/social/language/_word_frequencies.py
40
41
42
def __init__(self, label: str, exponent: Annotated[float, StateSpec()] = 1.0) -> None:
    super().__init__(label)
    self.exponent = exponent

LeastEffortInteraction(label, *, target, words)

Bases: Interaction

Probabilities of words that minimise the ratio of the mean cost C to the entropy H.

The probabilities that are stationary for C / H on the simplex are p_i = exp(-lambda c_i) / Z, for which H = lambda C + ln Z. The ratio is stationary in lambda where H = lambda C, that is where ln Z = 0, which solves the exponent. The sum reduces in log space.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
target Annotated[str, RoleSpec()]

The path of the element that holds exponent.

required
words Annotated[str, RoleSpec()]

The path of the group of words, which hold cost and probability.

required

Examples:

>>> from skcomplex.systems.social.language import LeastEffortInteraction
>>> LeastEffortInteraction('effort', target='language', words='words').words
'words'
Source code in src/skcomplex/systems/social/language/_word_frequencies.py
 98
 99
100
101
102
103
104
105
106
107
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    words: Annotated[str, RoleSpec()],
) -> None:
    super().__init__(label)
    self.target = target
    self.words = words

TypedWordElement(label, visual=None, *, count=1.0, length=1.0, code=1.0)

Bases: Element

Distinct word of a randomly typed text, with its number of occurrences and its number of letters.

Parameters:

Name Type Description Default
label str

The label of the word or the group.

required
visual VisualSpec | None

How the agent is drawn, or None for default_visual_.

None
count Annotated[float, StateSpec(domain=NN)]

The number of occurrences of the word.

1.0
length Annotated[float, StateSpec(domain=NN)]

The number of letters of the word.

1.0
code Annotated[float, StateSpec(domain=NN)]

The word as a number, its letters k_1, ..., k_L from 0 read as the digits k_i + 1 of base letters.

1.0

Examples:

>>> from skcomplex.systems.social.language import TypedWordElement
>>> TypedWordElement('word', length=3.0).length
3.0
Source code in src/skcomplex/systems/social/language/_typewriting.py
55
56
57
58
59
60
61
62
63
64
65
66
67
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    count: Annotated[float, StateSpec(domain=NN)] = 1.0,
    length: Annotated[float, StateSpec(domain=NN)] = 1.0,
    code: Annotated[float, StateSpec(domain=NN)] = 1.0,
) -> None:
    super().__init__(label, visual=visual)
    self.count = count
    self.length = length
    self.code = code

TypedWordView(agent, stepper)

Bases: AgentView

View of a typed word of the random typewriting system, with its states as attributes.

Source code in src/skcomplex/core/engine/solvers/_mesa.py
559
560
561
def __init__(self, agent: mesa.Agent, stepper: 'MesaSolver') -> None:
    self._agent = agent
    self._stepper = stepper

TypingInteraction(label, *, target, letters=5, space_bar=0.2, keystrokes=1000)

Bases: Interaction

Typing of Miller's monkeys, random keystrokes on a typewriter with a number of letters and a space bar.

Every keystroke hits the space bar with the probability space_bar, and otherwise one of the letters letters, uniformly. The space bar ends the word typed since the last space, a new distinct word or one more occurrence of an earlier one, and a space after a space types no word. A word is held as its code, its letters k_1, ..., k_L from 0 read as the digits k_i + 1 of base letters, so distinct words have distinct codes.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
target Annotated[str, RoleSpec()]

The path of the variable group of distinct words.

required
letters Annotated[int, ParameterSpec(domain=closed_open(1, oo))]

The number of letters of the typewriter.

5
space_bar Annotated[float, ParameterSpec(domain=open(0, 1))]

The probability that a keystroke hits the space bar.

0.2
keystrokes Annotated[int, ParameterSpec(domain=NN)]

The number of keystrokes per step.

1000

Examples:

>>> from skcomplex.systems.social.language import TypingInteraction
>>> TypingInteraction('typing', target='words').letters
5
Source code in src/skcomplex/systems/social/language/_typewriting.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    letters: Annotated[int, ParameterSpec(domain=RealSet.closed_open(1, oo))] = 5,
    space_bar: Annotated[float, ParameterSpec(domain=RealSet.open(0, 1))] = 0.2,
    keystrokes: Annotated[int, ParameterSpec(domain=NN)] = 1000,
) -> None:
    super().__init__(label)
    self.target = target
    self.letters = letters
    self.space_bar = space_bar
    self.keystrokes = keystrokes

type_keys(system)

Type the keystrokes of one step and count the words they end.

Parameters:

Name Type Description Default
system SystemView

The system.

required
Source code in src/skcomplex/systems/social/language/_typewriting.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
def type_keys(self, system: SystemView) -> None:
    """Type the keystrokes of one step and count the words they end.

    Args:
        system:
            The system.
    """
    storage = system.storage
    if 'words' not in storage:
        storage['words'] = {int(word.code): word for word in system.agents(self.target, TypedWordView)}
    words, code, length = storage['words'], storage.get('code', 0), storage.get('length', 0)
    if not isinstance(words, dict) or not isinstance(code, int) or not isinstance(length, int):
        return
    spaces = system.rng.random(int(self.keystrokes)) < self.space_bar
    keys = system.rng.integers(int(self.letters), size=int(self.keystrokes))
    new: dict[tuple[int, int], int] = {}
    for is_space, key in zip(spaces.tolist(), keys.tolist(), strict=True):
        if not is_space:
            code, length = code * int(self.letters) + key + 1, length + 1
            continue
        if length:
            new[code, length] = new.get((code, length), 0) + 1
        code, length = 0, 0
    storage['code'], storage['length'] = code, length
    for (word_code, word_length), count in new.items():
        word = words.get(word_code)
        if isinstance(word, TypedWordView):
            word.count += count
            continue
        created = system.create(self.target, count=float(count), length=float(word_length), code=float(word_code))
        words[word_code] = created

WordElement(label, cost=1.0, probability=0.0)

Bases: Element

Word with a quenched cost and the probability of its use.

Parameters:

Name Type Description Default
label str

The label of the word or the group.

required
cost Annotated[float, StateSpec()]

The cost of the word.

1.0
probability Annotated[float, StateSpec()]

The initial guess of the probability, which the equation replaces.

0.0

Examples:

>>> from skcomplex.systems.social.language import WordElement
>>> WordElement('word', cost=1.0).cost
1.0
Source code in src/skcomplex/systems/social/language/_word_frequencies.py
64
65
66
67
68
69
70
71
72
def __init__(
    self,
    label: str,
    cost: Annotated[float, StateSpec()] = 1.0,
    probability: Annotated[float, StateSpec()] = 0.0,
) -> None:
    super().__init__(label)
    self.cost = cost
    self.probability = probability

mandelbrot_least_effort(n_words=200)

Return the word frequencies that minimise the mean cost of a language over its entropy, a power law of rank.

A word of rank i costs log(i + 1), the length of its code, so the probabilities are (i + 1)^-lambda, where lambda makes them sum to 1 (Giordano 2024 §6, after Mandelbrot 1953).

Parameters:

Name Type Description Default
n_words int

The number of words.

200

Returns:

Name Type Description
system System

The system, with the states language.exponent, words.cost, and words.probability.

Examples:

>>> from skcomplex.core.engine.runtime import Simulation
>>> from skcomplex.systems.social.language import mandelbrot_least_effort
>>> trajectory = Simulation(mandelbrot_least_effort(50)).prepare().run(1.0).trajectory_
>>> round(float(trajectory.values('words.probability')[0].sum()), 6)
1.0
Source code in src/skcomplex/systems/social/language/_word_frequencies.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
@register_system(
    field='social/language',
    tags=('power law', 'least effort', 'entropy', 'static'),
    references=('Giordano 2024, arXiv 2407.02181', 'Mandelbrot 1953'),
)
def mandelbrot_least_effort(n_words: int = 200) -> System:
    """Return the word frequencies that minimise the mean cost of a language over its entropy, a power law of rank.

    A word of rank `i` costs `log(i + 1)`, the length of its code, so the probabilities are `(i + 1)^-lambda`, where
    `lambda` makes them sum to 1 (Giordano 2024 §6, after Mandelbrot 1953).

    Args:
        n_words:
            The number of words.

    Returns:
        system:
            The system, with the states `language.exponent`, `words.cost`, and `words.probability`.

    Examples:
        >>> from skcomplex.core.engine.runtime import Simulation
        >>> from skcomplex.systems.social.language import mandelbrot_least_effort
        >>> trajectory = Simulation(mandelbrot_least_effort(50)).prepare().run(1.0).trajectory_
        >>> round(float(trajectory.values('words.probability')[0].sum()), 6)
        1.0
    """
    costs = np.log(np.arange(1, n_words + 1) + 1.0)
    visual = VisualSpec('histograms', {'color_by': 'probability'})
    words = GroupElement('words', element=WordElement, size=n_words, values={'cost': costs}, visual=visual)
    effort = LeastEffortInteraction('effort', target='language', words='words')
    return System(elements=[words, LanguageElement('language')], interactions=[effort], time=StaticTime())

random_typewriting(letters=5, *, space=0.2, keystrokes=1000, random_state=0)

Return Miller's random typewriting, monkeys whose random keystrokes give Zipf's law of word frequencies.

A typewriter has letters letters and a space bar hit with the probability q = space, and every space ends a word. A word of L letters has the probability q ((1 - q) / letters)^L, so its frequency falls exponentially with its length, while the number of distinct words of length L grows exponentially as letters^L. The two exponentials give a power law of the frequency of the word of rank r, r^(-alpha) with alpha = 1 - log(1 - q) / log(letters) (Thurner et al. 2018 section 3.3.6.2).

Parameters:

Name Type Description Default
letters int

The number of letters of the typewriter.

5
space float

The probability that a keystroke hits the space bar.

0.2
keystrokes int

The number of keystrokes per step.

1000
random_state int | Generator | None

The seed or generator of the first word of the text.

0

Returns:

Name Type Description
system System

The system, with the variable group words, which starts with the first word of the text.

Examples:

>>> from skcomplex.systems.social.language import random_typewriting
>>> random_typewriting().elements[0].prepare().size_
1
Source code in src/skcomplex/systems/social/language/_typewriting.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
@register_system(
    field='social/language',
    tags=('agents', 'power law', 'zipf', 'language'),
    references=('Miller 1957', 'Thurner, Hanel, and Klimek 2018'),
    defaults={'until': 50},
)
def random_typewriting(
    letters: int = 5,
    *,
    space: float = 0.2,
    keystrokes: int = 1000,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return Miller's random typewriting, monkeys whose random keystrokes give Zipf's law of word frequencies.

    A typewriter has `letters` letters and a space bar hit with the probability `q = space`, and every space ends a
    word. A word of `L` letters has the probability `q ((1 - q) / letters)^L`, so its frequency falls exponentially
    with its length, while the number of distinct words of length `L` grows exponentially as `letters^L`. The two
    exponentials give a power law of the frequency of the word of rank `r`, `r^(-alpha)` with
    `alpha = 1 - log(1 - q) / log(letters)` (Thurner et al. 2018 section 3.3.6.2).

    Args:
        letters:
            The number of letters of the typewriter.

        space:
            The probability that a keystroke hits the space bar.

        keystrokes:
            The number of keystrokes per step.

        random_state:
            The seed or generator of the first word of the text.

    Returns:
        system:
            The system, with the variable group `words`, which starts with the first word of the text.

    Examples:
        >>> from skcomplex.systems.social.language import random_typewriting
        >>> random_typewriting().elements[0].prepare().size_
        1
    """
    rng = np.random.default_rng(random_state)
    code, length = 0, 0
    while length == 0 or rng.random() >= space:
        code, length = code * letters + int(rng.integers(letters)) + 1, length + 1
    words = GroupElement(
        'words',
        element=TypedWordElement,
        size=1,
        values={'count': 1.0, 'length': float(length), 'code': float(code)},
        variable=True,
    )
    typing = TypingInteraction('typing', target='words', letters=letters, space_bar=space, keystrokes=keystrokes)
    return System(elements=[words], interactions=[typing])