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 model, 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.models.social.segregation import BlockMovesInteraction
>>> BlockMovesInteraction('moves', target='dwellers', side=4).check().schedule
'member'
Source code in src/skcomplex/models/social/segregation/_block_moves.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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, model)

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

Parameters:

Name Type Description Default
agent AgentViewProtocol

The dweller.

required
model ModelViewProtocol

The model view.

required

Returns:

Name Type Description
block float

The index of the block.

Source code in src/skcomplex/models/social/segregation/_block_moves.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def get_block(self, agent: AgentViewProtocol, model: ModelViewProtocol) -> float:
    """Return the index of the block that holds the cell of an agent.

    Args:
        agent:
            The dweller.

        model:
            The model 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 model, 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.elements import GroupElement
>>> from skcomplex.models.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/models/social/segregation/_dweller.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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 model, with its states and parameters as attributes.

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

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

Bases: MovementInteraction

Relocation of the Schelling model, 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.

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.

1.0
phase str | None

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

None

Examples:

>>> from skcomplex.models.social.segregation import RelocationInteraction
>>> RelocationInteraction('relocation', target='residents').target
'residents'
Source code in src/skcomplex/models/social/segregation/_relocation.py
42
43
44
45
46
47
48
49
50
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    probability: Annotated[float, ParameterSpec(domain=RealSet.closed(0, 1))] = 1.0,
    phase: str | None = None,
) -> None:
    super().__init__(label, target=target, probability=probability, phase=phase)

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 model, 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.elements import GroupElement
>>> from skcomplex.models.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/models/social/segregation/_resident.py
40
41
42
43
44
45
46
47
48
49
50
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 model, with its states and parameters as attributes.

Source code in src/skcomplex/engine/solvers/_mesa.py
513
514
515
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, random_state=0)

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

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
random_state int | Generator | None

The seed or generator of the kinds.

0

Returns:

Name Type Description
system System

The model, with the group residents.

Examples:

>>> from skcomplex.models.social.segregation import schelling
>>> schelling(10, 10, density=0.5).elements[0].check().prepare().size_
50
Source code in src/skcomplex/models/social/segregation/_schelling.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@register_model(
    field='social/segregation',
    tags=('segregation', 'agents', 'grid'),
    references=('Schelling 1971',),
    defaults={'until': 50},
)
def schelling(
    width: int = 20,
    height: int = 20,
    *,
    density: float = 0.8,
    minority: float = 0.3,
    threshold: float = 0.5,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return a Schelling segregation model on a grid with at most one resident per cell.

    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.

        random_state:
            The seed or generator of the kinds.

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

    Examples:
        >>> from skcomplex.models.social.segregation import schelling
        >>> schelling(10, 10, density=0.5).elements[0].check().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')],
        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 model 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 model, with the group dwellers.

Examples:

>>> from skcomplex.models.social.segregation import stochastic_schelling
>>> stochastic_schelling(8, 8, side=4, random_state=0).elements[0].check().prepare().size_
32
Source code in src/skcomplex/models/social/segregation/_blocks.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
@register_model(
    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 model 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 model, with the group `dwellers`.

    Examples:
        >>> from skcomplex.models.social.segregation import stochastic_schelling
        >>> stochastic_schelling(8, 8, side=4, random_state=0).elements[0].check().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',))])),
    )