Skip to content

Segregation

Segregation: residential sorting of agents and its dissimilarity index.

BlockMovesInteraction(label, *, target, width=20, height=20, side=5, probability=1.0, phase=None)

Bases: MovementInteraction

Moves of the stochastic Schelling system, from an agent's cell to a random empty cell of another block.

A visited agent proposes to move to a random empty cell of the grid, and stays when the cell lies in its own block. With block densities rho_i before and rho_f' after the move, the proposal is accepted with probability 1 / (1 + exp(beta * dU)), where dU = (1 - altruism) * (u(rho_f') - u(rho_i)) + altruism * dU_global and dU_global is the change of M * sum_q rho_q u(rho_q) over the blocks of M cells. The densities are the counts of the block states of the group. In stochastic_schelling, a phase of visits draws as many agents with replacement as there are agents every step, and a MapInteraction sets the block of an agent after its move.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
target Annotated[str, RoleSpec()]

The path of the group of dwellers.

required
width int

The number of columns of the grid.

20
height int

The number of rows of the grid.

20
side int

The side of a square block in cells, which divides the width and the height.

5
probability float

The probability that a visited agent proposes a move.

1.0
phase str | None

The phase of the step the moves run in, or None for the default phase.

None

Examples:

>>> from skcomplex.systems.social.segregation import BlockMovesInteraction
>>> BlockMovesInteraction('moves', target='dwellers', side=4).schedule
'member'
Source code in src/skcomplex/systems/social/segregation/_blocks.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    width: int = 20,
    height: int = 20,
    side: int = 5,
    probability: float = 1.0,
    phase: str | None = None,
) -> None:
    super().__init__(
        label,
        target=target,
        step=self._propose,
        probability=probability,
        accept=self._accept,
        schedule='member',
        phase=phase,
    )
    self.width = width
    self.height = height
    self.side = side

get_block(agent, system)

Return the index of the block that holds the cell of an agent.

Parameters:

Name Type Description Default
agent AgentViewProtocol

The dweller.

required
system SystemViewProtocol

The system view.

required

Returns:

Name Type Description
block float

The index of the block.

Source code in src/skcomplex/systems/social/segregation/_blocks.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def get_block(self, agent: AgentViewProtocol, system: SystemViewProtocol) -> float:
    """Return the index of the block that holds the cell of an agent.

    Args:
        agent:
            The dweller.

        system:
            The system view.

    Returns:
        block:
            The index of the block.
    """
    return float(BlockMovesInteraction._block_of(agent.location, self.side, self.width // self.side))

DwellerElement(label, visual=None, *, block=0.0, beta=3.0, altruism=0.5, slope=-4.0 / 3.0, intercept=1.0)

Bases: Element

Agent of the stochastic Schelling system, whose utility a * rho + b depends on the density of its block.

Parameters:

Name Type Description Default
label str

The label of the agent or group.

required
visual VisualSpec | None

How the agent is drawn, or None for default_visual_.

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

The index of the agent's block, which stochastic_schelling sets after every move.

0.0
beta Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The inverse temperature of the moves.

3.0
altruism Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The weight of the change of the global utility against the agent's own utility.

0.5
slope Annotated[float, ParameterSpec()]

The slope a of the utility in the block density.

-4.0 / 3.0
intercept Annotated[float, ParameterSpec()]

The intercept b of the utility.

1.0

Examples:

>>> from skcomplex.core.components.elements import GroupElement
>>> from skcomplex.systems.social.segregation import DwellerElement
>>> group = GroupElement('dwellers', element=DwellerElement, size=10, values={'beta': 3.0})
>>> group.label, group.element.__name__, group.size
('dwellers', 'DwellerElement', 10)
Source code in src/skcomplex/systems/social/segregation/_blocks.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    block: Annotated[float, StateSpec(domain=NN)] = 0.0,
    beta: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 3.0,
    altruism: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    slope: Annotated[float, ParameterSpec()] = -4.0 / 3.0,
    intercept: Annotated[float, ParameterSpec()] = 1.0,
) -> None:
    super().__init__(label, visual=visual)
    self.block = block
    self.beta = beta
    self.altruism = altruism
    self.slope = slope
    self.intercept = intercept

DwellerView(agent, stepper)

Bases: AgentView

View of a dweller of the stochastic Schelling system, with its states and parameters 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

RelocationInteraction(label, *, target, probability=1.0, choices=None, phase=None)

Bases: MovementInteraction

Relocation of the Schelling system, where every resident with too few similar neighbours moves to an empty cell.

Every step every resident, in random order, moves to a random empty cell when the share of its neighbours of its own kind is below its threshold. With choices, the Big Sort of Bishop's hypothesis, every resident moves with the probability probability whatever its neighbours, to the cell with the highest share of neighbours of its own kind among choices random empty cells, the first of them on ties.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
target Annotated[str, RoleSpec()]

The path of the group of residents.

required
probability Annotated[float, ParameterSpec(domain=closed(0, 1))]

The probability that an unhappy resident moves in a step, or that any resident moves under the Big Sort.

1.0
choices int | None

The number of random empty cells a moving resident compares under the Big Sort, or None for Schelling's rule.

None
phase str | None

The phase of the step the relocation runs in, or None for the default phase.

None

Examples:

>>> from skcomplex.systems.social.segregation import RelocationInteraction
>>> RelocationInteraction('relocation', target='residents').target
'residents'
Source code in src/skcomplex/systems/social/segregation/_schelling.py
102
103
104
105
106
107
108
109
110
111
112
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    probability: Annotated[float, ParameterSpec(domain=RealSet.closed(0, 1))] = 1.0,
    choices: int | None = None,
    phase: str | None = None,
) -> None:
    super().__init__(label, target=target, probability=probability, phase=phase)
    self.choices = choices

ResidentElement(label, visual=VisualSpec('agents', {'color_by': 'kind', 'color_range': (0.0, 1.0)}), *, kind=0.0, threshold=0.5)

Bases: Element

Resident of the Schelling system, who moves to an empty cell while too few neighbours share its kind.

Parameters:

Name Type Description Default
label str

The label of the agent or group.

required
visual VisualSpec | None

How the agent is drawn, or None for default_visual_.

VisualSpec('agents', {'color_by': 'kind', 'color_range': (0.0, 1.0)})
kind Annotated[float, StateSpec(domain=NN)]

The kind of the resident, 0 or 1.

0.0
threshold Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The smallest share of neighbours of the same kind that keeps a resident in place.

0.5

Examples:

>>> from skcomplex.core.components.elements import GroupElement
>>> from skcomplex.systems.social.segregation import ResidentElement
>>> group = GroupElement('residents', element=ResidentElement, size=10, values={'kind': 1.0})
>>> group.label, group.element.__name__, group.size
('residents', 'ResidentElement', 10)
Source code in src/skcomplex/systems/social/segregation/_schelling.py
53
54
55
56
57
58
59
60
61
62
63
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'kind', 'color_range': (0.0, 1.0)}),
    *,
    kind: Annotated[float, StateSpec(domain=NN)] = 0.0,
    threshold: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
) -> None:
    super().__init__(label, visual=visual)
    self.kind = kind
    self.threshold = threshold

ResidentView(agent, stepper)

Bases: AgentView

View of a resident of the Schelling system, with its states and parameters 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

schelling(width=20, height=20, *, density=0.8, minority=0.3, threshold=0.5, choices=None, moving=0.1, random_state=0)

Return a Schelling segregation system on a grid with at most one resident per cell.

Every step every resident whose share of neighbours of its own kind is below the threshold moves to a random empty cell. With choices, the Big Sort of Downey's Exercise 9-1 after Bishop's hypothesis, residents move for any reason, a share moving of them every step, and choose among choices random empty cells the one with the highest share of neighbours of their own kind, so the threshold plays no part.

Parameters:

Name Type Description Default
width int

The number of columns.

20
height int

The number of rows.

20
density float

The share of cells with a resident.

0.8
minority float

The share of residents of kind 1.

0.3
threshold float

The smallest share of similar neighbours that keeps a resident in place.

0.5
choices int | None

The number of random empty cells a moving resident compares under the Big Sort, or None for Schelling's rule.

None
moving float

The probability that a resident moves in a step under the Big Sort.

0.1
random_state int | Generator | None

The seed or generator of the kinds.

0

Returns:

Name Type Description
system System

The system, with the group residents.

Examples:

>>> from skcomplex.systems.social.segregation import schelling
>>> schelling(10, 10, density=0.5).elements[0].prepare().size_
50
Source code in src/skcomplex/systems/social/segregation/_schelling.py
158
159
160
161
162
163
164
165
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
@register_system(
    field='social/segregation',
    tags=('segregation', 'agents', 'grid'),
    references=('Schelling 1971', 'Downey 2018'),
    defaults={'until': 50},
)
def schelling(
    width: int = 20,
    height: int = 20,
    *,
    density: float = 0.8,
    minority: float = 0.3,
    threshold: float = 0.5,
    choices: int | None = None,
    moving: float = 0.1,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return a Schelling segregation system on a grid with at most one resident per cell.

    Every step every resident whose share of neighbours of its own kind is below the threshold moves to a random empty
    cell. With `choices`, the Big Sort of Downey's Exercise 9-1 after Bishop's hypothesis, residents move for any
    reason, a share `moving` of them every step, and choose among `choices` random empty cells the one with the
    highest share of neighbours of their own kind, so the threshold plays no part.

    Args:
        width:
            The number of columns.

        height:
            The number of rows.

        density:
            The share of cells with a resident.

        minority:
            The share of residents of kind 1.

        threshold:
            The smallest share of similar neighbours that keeps a resident in place.

        choices:
            The number of random empty cells a moving resident compares under the Big Sort, or `None` for Schelling's
            rule.

        moving:
            The probability that a resident moves in a step under the Big Sort.

        random_state:
            The seed or generator of the kinds.

    Returns:
        system:
            The system, with the group `residents`.

    Examples:
        >>> from skcomplex.systems.social.segregation import schelling
        >>> schelling(10, 10, density=0.5).elements[0].prepare().size_
        50
    """
    rng = np.random.default_rng(random_state)
    size = int(width * height * density)
    kinds = (rng.random(size) < minority).astype(float)
    residents = GroupElement(
        'residents',
        element=ResidentElement,
        size=size,
        values={'kind': kinds, 'threshold': threshold},
    )
    return System(
        elements=[residents],
        interactions=[
            (
                RelocationInteraction('relocation', target='residents')
                if choices is None
                else RelocationInteraction('relocation', target='residents', probability=moving, choices=choices)
            ),
        ],
        space=LatticeSpace((width, height), boundary='fixed', capacity=1),
    )

stochastic_schelling(width=20, height=20, *, side=5, density=0.5, beta=3.0, altruism=0.5, slope=-4.0 / 3.0, intercept=1.0, random_state=0)

Return the stochastic Schelling system of agents moving between blocks, which segregates above beta = 2.

Parameters:

Name Type Description Default
width int

The number of columns, a multiple of the block side.

20
height int

The number of rows, a multiple of the block side.

20
side int

The side of a square block in cells.

5
density float

The share of cells with an agent.

0.5
beta float

The inverse temperature of the moves.

3.0
altruism float

The weight of the change of the global utility.

0.5
slope float

The slope of the utility in the block density.

-4.0 / 3.0
intercept float

The intercept of the utility.

1.0
random_state int | Generator | None

The seed or generator of the initial cells.

0

Returns:

Name Type Description
system System

The system, with the group dwellers.

Examples:

>>> from skcomplex.systems.social.segregation import stochastic_schelling
>>> stochastic_schelling(8, 8, side=4, random_state=0).elements[0].prepare().size_
32
Source code in src/skcomplex/systems/social/segregation/_blocks.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
@register_system(
    field='social/segregation',
    tags=('segregation', 'agents', 'grid', 'stochastic'),
    references=('Grauwin et al. 2009', 'Jensen 2023'),
    defaults={'until': 50},
)
def stochastic_schelling(
    width: int = 20,
    height: int = 20,
    *,
    side: int = 5,
    density: float = 0.5,
    beta: float = 3.0,
    altruism: float = 0.5,
    slope: float = -4.0 / 3.0,
    intercept: float = 1.0,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the stochastic Schelling system of agents moving between blocks, which segregates above `beta = 2`.

    Args:
        width:
            The number of columns, a multiple of the block side.

        height:
            The number of rows, a multiple of the block side.

        side:
            The side of a square block in cells.

        density:
            The share of cells with an agent.

        beta:
            The inverse temperature of the moves.

        altruism:
            The weight of the change of the global utility.

        slope:
            The slope of the utility in the block density.

        intercept:
            The intercept of the utility.

        random_state:
            The seed or generator of the initial cells.

    Returns:
        system:
            The system, with the group `dwellers`.

    Examples:
        >>> from skcomplex.systems.social.segregation import stochastic_schelling
        >>> stochastic_schelling(8, 8, side=4, random_state=0).elements[0].prepare().size_
        32
    """
    rng = np.random.default_rng(random_state)
    size = int(width * height * density)
    cells = rng.permutation(width * height)[:size]
    locations = [(int(cell % width), int(cell // width)) for cell in cells]
    blocks = [BlockMovesInteraction._block_of(location, side, width // side) for location in locations]
    dwellers = GroupElement(
        'dwellers',
        element=DwellerElement,
        size=size,
        values={
            'block': np.array(blocks, dtype=float),
            'beta': beta,
            'altruism': altruism,
            'slope': slope,
            'intercept': intercept,
        },
        location=locations,
    )
    moves = BlockMovesInteraction('moves', target='dwellers', width=width, height=height, side=side, phase=PHASE)
    block = MapInteraction(
        'block',
        target='dwellers',
        state='block',
        function=moves.get_block,
        schedule='member',
        form='agent',
        phase=PHASE,
    )
    return System(
        elements=[dwellers],
        interactions=[moves, block],
        space=LatticeSpace((width, height), boundary='fixed', capacity=1),
        time=DiscreteTime(schedule=PhasesScheduleSpec([PhaseSpec(PHASE, activation='visits', groups=('dwellers',))])),
    )