Skip to content

Organizations

Organizations: hierarchies, queues, workplaces, and institutions.

ArmsRaceElement(label, visual=None, *, a=10.0, b=10.5, margin=0.1, adjustment_time=2.0, perception_delay=1.0)

Bases: Element

Arms race element.

Source code in src/skcomplex/models/social/organizations/_arms_race.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    a: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='arms')] = 10.0,
    b: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='arms')] = 10.5,
    margin: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
    adjustment_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 2.0,
    perception_delay: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
) -> None:
    super().__init__(label, visual=visual)
    self.a = a
    self.b = b
    self.margin = margin
    self.adjustment_time = adjustment_time
    self.perception_delay = perception_delay

BurdenElement(label, visual=None, *, capability=0.5, intervention=0.0, desired=1.0, fix_rate=1.0, build_time=5.0, solution_delay=2.0, erosion=0.5)

Bases: Element

Burden element.

Source code in src/skcomplex/models/social/organizations/_burden.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    capability: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='capability')] = 0.5,
    intervention: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='intervention')] = 0.0,
    desired: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    fix_rate: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    build_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 5.0,
    solution_delay: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 2.0,
    erosion: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
) -> None:
    super().__init__(label, visual=visual)
    self.capability = capability
    self.intervention = intervention
    self.desired = desired
    self.fix_rate = fix_rate
    self.build_time = build_time
    self.solution_delay = solution_delay
    self.erosion = erosion

CityResidentElement(label, x=0.0, contacts=0.0)

Bases: Element

Resident of a city, who stays at a position.

Parameters:

Name Type Description Default
label str

The label of the resident or the group.

required
x Annotated[ArrayLike, StateSpec(vector=True, role=position)]

The position.

0.0
contacts Annotated[float, StateSpec()]

The initial guess of the sum of the interactions with the others, which the equation replaces.

0.0

Examples:

>>> from skcomplex.models.social.organizations import CityResidentElement
>>> CityResidentElement('resident', x=(1.0, 2.0)).x
(1.0, 2.0)
Source code in src/skcomplex/models/social/organizations/_city_resident.py
33
34
35
36
37
38
39
40
41
def __init__(
    self,
    label: str,
    x: Annotated[npt.ArrayLike, StateSpec(vector=True, role='position')] = 0.0,
    contacts: Annotated[float, StateSpec()] = 0.0,
) -> None:
    super().__init__(label)
    self.x = x
    self.contacts = contacts

ContenderElement(label, visual=VisualSpec('agents', {'color_by': 'strength'}), *, strength=0.0, eta=1.0, relaxation=0.1)

Bases: Element

Agent of the Bonabeau model, who wanders on a grid and fights the agents it runs into.

Every step an agent's strength relaxes by the factor 1 - relaxation, and the agent tries to step into a random neighbouring cell. An attacker beats the occupant with probability 1 / (1 + exp(eta * (h_occupant - h_attacker))), the winner gains 1 and the loser loses 1, and a winning attacker swaps places with the occupant. Agents differ only by their strength, so the swap exchanges their strengths.

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': 'strength'})
strength Annotated[float, StateSpec()]

The initial strength h.

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

The sensitivity of the outcome of a fight to the strength difference.

1.0
relaxation Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The fraction of its strength an agent loses every step.

0.1

Examples:

>>> from skcomplex.models.social.organizations import ContenderElement
>>> group = GroupElement('contenders', element=ContenderElement, size=10)
>>> group.label, group.element.__name__, group.size
('contenders', 'ContenderElement', 10)
Source code in src/skcomplex/models/social/organizations/_hierarchies.py
55
56
57
58
59
60
61
62
63
64
65
66
67
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'strength'}),
    *,
    strength: Annotated[float, StateSpec()] = 0.0,
    eta: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    relaxation: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
) -> None:
    super().__init__(label, visual=visual)
    self.strength = strength
    self.eta = eta
    self.relaxation = relaxation

wander(agent, model)

Relax the strength, then step into a random neighbouring cell or fight its occupant.

Parameters:

Name Type Description Default
agent ContenderView

The contender.

required
model ModelView

The model.

required
Source code in src/skcomplex/models/social/organizations/_hierarchies.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def wander(self, agent: ContenderView, model: ModelView) -> None:
    """Relax the strength, then step into a random neighbouring cell or fight its occupant.

    Args:
        agent:
            The contender.

        model:
            The model.
    """
    agent.strength *= 1 - agent.relaxation
    cells = agent.neighbor_cells()
    cell = cells[int(model.rng.integers(len(cells)))]
    occupants = [
        other
        for other in agent.neighbors()
        if isinstance(other, ContenderView)
        and tuple(np.ravel(np.asarray(other.location))) == tuple(np.ravel(np.asarray(cell)))
    ]
    if occupants:
        self._fight(agent, occupants[0], model)
        return
    agent.move_to(cell)

ContenderView(agent, stepper)

Bases: AgentView

View of an agent of the Bonabeau hierarchy 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

ContestElement(label, visual=None, *, a=55.0, b=45.0, budget=10.0, lifetime=10.0)

Bases: Element

Contest element.

Source code in src/skcomplex/models/social/organizations/_contest.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    a: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='resources')] = 55.0,
    b: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='resources')] = 45.0,
    budget: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 10.0,
    lifetime: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 10.0,
) -> None:
    super().__init__(label, visual=visual)
    self.a = a
    self.b = b
    self.budget = budget
    self.lifetime = lifetime

DailyMovementsInteraction(label, *, women, premises, n_cities=3, phase=None, victims_phase=None)

Bases: Interaction

Movements of the women of the brothel movement model.

Every day one random free woman moves to a random cell of a random city, and in the steps of the phase victims_phase the traffickers move a random number of the victims of every brothel to random brothels. A move between cities counts as a train trip. The cities are vertical bands of equal width of the grid.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
women Annotated[str, RoleSpec()]

The path of the group of travellers.

required
premises Annotated[str, RoleSpec()]

The path of the group of premises, whose brothels the victims move between.

required
n_cities int

The number of cities, vertical bands of equal width.

3
phase str | None

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

None
victims_phase str | None

The phase of the step the victims move in, or None for the default phase.

None

Examples:

>>> from skcomplex.models.social.organizations import DailyMovementsInteraction
>>> DailyMovementsInteraction('day', women='women', premises='premises').n_cities
3
Source code in src/skcomplex/models/social/organizations/_daily_movements.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def __init__(
    self,
    label: str,
    *,
    women: Annotated[str, RoleSpec()],
    premises: Annotated[str, RoleSpec()],
    n_cities: int = 3,
    phase: str | None = None,
    victims_phase: str | None = None,
) -> None:
    super().__init__(label)
    self.women = women
    self.premises = premises
    self.n_cities = n_cities
    self.phase = phase
    self.victims_phase = victims_phase

move_free(model)

Move a random free woman to a random cell of a random city.

Parameters:

Name Type Description Default
model ModelView

The model.

required
Source code in src/skcomplex/models/social/organizations/_daily_movements.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def move_free(self, model: ModelView) -> None:
    """Move a random free woman to a random cell of a random city.

    Args:
        model:
            The model.
    """
    free = [woman for woman in model.agents('women', TravellerView) if not woman.victim]
    if not free:
        return
    width, height = self._shape(model)
    city = int(model.rng.integers(self.n_cities))
    start, stop = city * width // self.n_cities, (city + 1) * width // self.n_cities
    cell = (int(model.rng.integers(start, stop)), int(model.rng.integers(height)))
    self._travel(free[int(model.rng.integers(len(free)))], cell, width)

move_victims(model)

Move a random number of the victims of every brothel to random brothels.

Parameters:

Name Type Description Default
model ModelView

The model.

required
Source code in src/skcomplex/models/social/organizations/_daily_movements.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def move_victims(self, model: ModelView) -> None:
    """Move a random number of the victims of every brothel to random brothels.

    Args:
        model:
            The model.
    """
    women = model.agents('women', TravellerView)
    brothels = [
        DailyMovementsInteraction._cell(place.location)
        for place in model.agents('premises', PremisesView)
        if place.brothel
    ]
    width = self._shape(model)[0]
    for brothel in brothels:
        victims = [
            woman for woman in women if woman.victim and DailyMovementsInteraction._cell(woman.location) == brothel
        ]
        moved = model.rng.permutation(len(victims))[: int(model.rng.integers(len(victims) + 1))]
        for index in moved:
            self._travel(victims[index], brothels[int(model.rng.integers(len(brothels)))], width)

EaterElement(label, visual=VisualSpec('agents', {'color_by': 'bmi', 'color_range': (18.5, 35.0)}), *, home=0.0, work=0.0, weight=70.0, height=170.0, age=40.0, male=1.0, habit=1.0, calories=0.0, bmi=24.0, day=0.0, reach=30.0, activity=1.55)

Bases: Element

Person of the obesity model, who goes from home to work and back every day and eats near each place.

The moves and the meals of the day are movements and transfers of the model, in the phases home, work, and evening. The calories of a meal are drawn from the bottom, middle, or top half of the restaurant's range by the eating habit. In the phase week, every seventh step, the weight changes by the calories eaten beyond seven days of the expenditure, the basal metabolic rate times the activity factor of the Harris-Benedict equation, at 7700 kilocalories per kilogram.

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': 'bmi', 'color_range': (18.5, 35.0)})
home Annotated[ArrayLike, StateSpec(vector=True)]

The position of the person's home.

0.0
work Annotated[ArrayLike, StateSpec(vector=True)]

The position of the person's workplace.

0.0
weight Annotated[float, StateSpec(domain=open(0, oo))]

The weight in kilograms.

70.0
height Annotated[float, StateSpec(domain=open(0, oo))]

The height in centimetres.

170.0
age Annotated[float, StateSpec(domain=open(0, oo))]

The age in years.

40.0
male Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))]

1 for a man and 0 for a woman.

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

The eating habit: low at 0, medium at 1, or high at 2.

1.0
calories Annotated[float, StateSpec(domain=closed_open(0, oo))]

The calories eaten so far this week.

0.0
bmi Annotated[float, StateSpec(domain=open(0, oo))]

The body mass index.

24.0
day Annotated[float, StateSpec(domain=NN)]

The number of days lived in the model.

0.0
reach Annotated[float, ParameterSpec(domain=open(0, oo))]

The distance within which a person looks for a restaurant.

30.0
activity Annotated[float, ParameterSpec(domain=open(0, oo))]

The factor of the basal metabolic rate a person spends a day, 1.2 when sedentary and 1.9 when very active.

1.55

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.social.organizations import EaterElement
>>> group = GroupElement(
...     'people', element=EaterElement, size=5, values={'home': [1.0, 2.0], 'work': [3.0, 4.0]}
... )
>>> group.label, group.element.__name__, group.size
('people', 'EaterElement', 5)
Source code in src/skcomplex/models/social/organizations/_eater.py
127
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
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'bmi', 'color_range': (18.5, 35.0)}),
    *,
    home: Annotated[npt.ArrayLike, StateSpec(vector=True)] = 0.0,
    work: Annotated[npt.ArrayLike, StateSpec(vector=True)] = 0.0,
    weight: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 70.0,
    height: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 170.0,
    age: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 40.0,
    male: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 1.0,
    habit: Annotated[float, StateSpec(domain=NN)] = 1.0,
    calories: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    bmi: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 24.0,
    day: Annotated[float, StateSpec(domain=NN)] = 0.0,
    reach: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 30.0,
    activity: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.55,
) -> None:
    super().__init__(label, visual=visual)
    self.home = home
    self.work = work
    self.weight = weight
    self.height = height
    self.age = age
    self.male = male
    self.habit = habit
    self.calories = calories
    self.bmi = bmi
    self.day = day
    self.reach = reach
    self.activity = activity

count_day(agent, model)

Count one more day lived in the model.

Parameters:

Name Type Description Default
agent EaterView

The person.

required
model ModelView

The model.

required
Source code in src/skcomplex/models/social/organizations/_eater.py
177
178
179
180
181
182
183
184
185
186
187
def count_day(self, agent: EaterView, model: ModelView) -> None:
    """Count one more day lived in the model.

    Args:
        agent:
            The person.

        model:
            The model.
    """
    agent.day += 1

draw_meal(agent, restaurant, model) staticmethod

Return the calories of a meal, drawn from the part of the range of a restaurant that a habit eats.

Parameters:

Name Type Description Default
agent EaterView

The person.

required
restaurant RestaurantView

The restaurant.

required
model ModelView

The model.

required

Returns:

Name Type Description
calories float

The calories of the meal.

Source code in src/skcomplex/models/social/organizations/_eater.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
@staticmethod
def draw_meal(agent: EaterView, restaurant: RestaurantView, model: ModelView) -> float:
    """Return the calories of a meal, drawn from the part of the range of a restaurant that a habit eats.

    Args:
        agent:
            The person.

        restaurant:
            The restaurant.

        model:
            The model.

    Returns:
        calories:
            The calories of the meal.
    """
    low, high = MEAL_RANGES[int(restaurant.kind)]
    start, stop = HABIT_RANGES[int(agent.habit)]
    return float(model.rng.uniform(low + start * (high - low), low + stop * (high - low)))

gain_weight(agent, model)

Change the weight by the calories of the week beyond the basal needs, and start a new week.

Parameters:

Name Type Description Default
agent EaterView

The person.

required
model ModelView

The model.

required
Source code in src/skcomplex/models/social/organizations/_eater.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def gain_weight(self, agent: EaterView, model: ModelView) -> None:
    """Change the weight by the calories of the week beyond the basal needs, and start a new week.

    Args:
        agent:
            The person.

        model:
            The model.
    """
    rate = basal_metabolic_rate(agent.weight, agent.height, agent.age, agent.male)
    needs = DAYS_PER_WEEK * agent.activity * rate
    agent.weight += (agent.calories - needs) / KCAL_PER_KG
    agent.calories = 0.0
    agent.bmi = agent.weight / (agent.height / 100) ** 2

go_home(agent, model) staticmethod

Return the position of the home of a person.

Parameters:

Name Type Description Default
agent EaterView

The person.

required
model ModelView

The model.

required

Returns:

Name Type Description
position NDArray[float64]

The position of the home.

Source code in src/skcomplex/models/social/organizations/_eater.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
@staticmethod
def go_home(agent: EaterView, model: ModelView) -> npt.NDArray[np.float64]:
    """Return the position of the home of a person.

    Args:
        agent:
            The person.

        model:
            The model.

    Returns:
        position:
            The position of the home.
    """
    return np.asarray(agent.home, dtype=float)

go_to_work(agent, model) staticmethod

Return the position of the workplace of a person.

Parameters:

Name Type Description Default
agent EaterView

The person.

required
model ModelView

The model.

required

Returns:

Name Type Description
position NDArray[float64]

The position of the workplace.

Source code in src/skcomplex/models/social/organizations/_eater.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
@staticmethod
def go_to_work(agent: EaterView, model: ModelView) -> npt.NDArray[np.float64]:
    """Return the position of the workplace of a person.

    Args:
        agent:
            The person.

        model:
            The model.

    Returns:
        position:
            The position of the workplace.
    """
    return np.asarray(agent.work, dtype=float)

settle_meal(agent, restaurant, amounts, model) staticmethod

Credit a person with the calories of a meal, and count the sale on the restaurant.

Parameters:

Name Type Description Default
agent EaterView

The person.

required
restaurant RestaurantView

The restaurant.

required
amounts Sequence[float]

The calories of the meal, the only amount.

required
model ModelView

The model.

required
Source code in src/skcomplex/models/social/organizations/_eater.py
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
@staticmethod
def settle_meal(agent: EaterView, restaurant: RestaurantView, amounts: Sequence[float], model: ModelView) -> None:
    """Credit a person with the calories of a meal, and count the sale on the restaurant.

    Args:
        agent:
            The person.

        restaurant:
            The restaurant.

        amounts:
            The calories of the meal, the only amount.

        model:
            The model.
    """
    calories = amounts[0]
    low, high = MEAL_RANGES[int(restaurant.kind)]
    agent.calories += calories
    restaurant.sold += 1
    restaurant.sold_high += float(calories > (low + high) / 2)

EaterView(agent, stepper)

Bases: AgentView

View of a person of the obesity 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

EmployeeElement(label, visual=VisualSpec('agents', {'color_by': 'risk', 'color_range': (0.0, 5.0)}), *, predisposed=0.0, disgruntlement=0.0, stress=0.0, skill=0.0, affect=0.5, risk=0.5, days=0.0, first=0.0, mean_feeling=0.0, expectation=0.0, feeling=0.0, turnover=0.15, culture=0.8, change=0.05, primacy=1.0, consistency=1.0, recency=1.0, affect_weight=1.0, alpha_seen=0.5, lambda_seen=1.0, alpha_unseen=0.25, lambda_unseen=0.0, skill_growth=0.001, skill_variance=0.0005)

Bases: Element

Employee of the insider sabotage model, whose risk is the sum of five factors.

The risk is R = P + D + S + K + A: the predisposition P, the disgruntlement D of predisposed employees whose daily feeling about the organization falls short of their expectation, the stress S raised by seeing a supervisor, the skill K that grows every day, and the fixed affect A. The expectation weighs the first, the mean, and the latest feeling. Every day an employee wanders to an adjacent cell and leaves with probability turnover / 365, replaced by a new hire. In insider_sabotage, a movement, a map of every state, and a life cycle that renews the employees who leave run these steps in turn in the activation of every employee.

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': 'risk', 'color_range': (0.0, 5.0)})
predisposed Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))]

Whether the employee is predisposed to sabotage, 1, or not, 0.

0.0
disgruntlement Annotated[float, StateSpec()]

The initial disgruntlement D.

0.0
stress Annotated[float, StateSpec(domain=closed_open(0, oo))]

The initial stress S.

0.0
skill Annotated[float, StateSpec()]

The initial skill K.

0.0
affect Annotated[float, StateSpec(domain=closed_open(0, oo))]

The affect A.

0.5
risk Annotated[float, StateSpec()]

The initial risk R.

0.5
days Annotated[float, StateSpec(domain=NN)]

The number of days the employee has felt about the organization.

0.0
first Annotated[float, StateSpec()]

The first feeling.

0.0
mean_feeling Annotated[float, StateSpec()]

The mean feeling.

0.0
expectation Annotated[float, StateSpec()]

The expected feeling.

0.0
feeling Annotated[float, StateSpec()]

The latest feeling.

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

The yearly share of employees who leave.

0.15
culture Annotated[float, ParameterSpec()]

The mean daily feeling about the organization.

0.8
change Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The variance of the daily feeling.

0.05
primacy Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The weight of the first feeling in the expectation.

1.0
consistency Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The weight of the mean feeling in the expectation.

1.0
recency Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The weight of the latest feeling in the expectation.

1.0
affect_weight Annotated[float, ParameterSpec()]

The factor of the shortfall of a feeling in the disgruntlement.

1.0
alpha_seen Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The learning rate of stress when a supervisor is in sight.

0.5
lambda_seen Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The level stress approaches when a supervisor is in sight.

1.0
alpha_unseen Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The learning rate of stress otherwise.

0.25
lambda_unseen Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The level stress approaches otherwise.

0.0
skill_growth Annotated[float, ParameterSpec()]

The mean daily growth of skill.

0.001
skill_variance Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The variance of the daily growth of skill.

0.0005

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.social.organizations import EmployeeElement
>>> group = GroupElement('employees', element=EmployeeElement, size=10, values={'affect': 0.5})
>>> group.label, group.element.__name__, group.size
('employees', 'EmployeeElement', 10)
Source code in src/skcomplex/models/social/organizations/_employee.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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
159
160
161
162
163
164
165
166
167
168
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'risk', 'color_range': (0.0, 5.0)}),
    *,
    predisposed: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 0.0,
    disgruntlement: Annotated[float, StateSpec()] = 0.0,
    stress: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    skill: Annotated[float, StateSpec()] = 0.0,
    affect: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    risk: Annotated[float, StateSpec()] = 0.5,
    days: Annotated[float, StateSpec(domain=NN)] = 0.0,
    first: Annotated[float, StateSpec()] = 0.0,
    mean_feeling: Annotated[float, StateSpec()] = 0.0,
    expectation: Annotated[float, StateSpec()] = 0.0,
    feeling: Annotated[float, StateSpec()] = 0.0,
    turnover: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.15,
    culture: Annotated[float, ParameterSpec()] = 0.8,
    change: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.05,
    primacy: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    consistency: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    recency: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    affect_weight: Annotated[float, ParameterSpec()] = 1.0,
    alpha_seen: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    lambda_seen: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    alpha_unseen: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.25,
    lambda_unseen: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    skill_growth: Annotated[float, ParameterSpec()] = 0.001,
    skill_variance: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0005,
) -> None:
    super().__init__(label, visual=visual)
    self.predisposed = predisposed
    self.disgruntlement = disgruntlement
    self.stress = stress
    self.skill = skill
    self.affect = affect
    self.risk = risk
    self.days = days
    self.first = first
    self.mean_feeling = mean_feeling
    self.expectation = expectation
    self.feeling = feeling
    self.turnover = turnover
    self.culture = culture
    self.change = change
    self.primacy = primacy
    self.consistency = consistency
    self.recency = recency
    self.affect_weight = affect_weight
    self.alpha_seen = alpha_seen
    self.lambda_seen = lambda_seen
    self.alpha_unseen = alpha_unseen
    self.lambda_unseen = lambda_unseen
    self.skill_growth = skill_growth
    self.skill_variance = skill_variance

EmployeeView(agent, stepper)

Bases: AgentView

View of an employee of the insider sabotage 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

ExecutionInteraction(label, *, target)

Bases: Interaction

Execution of one task per step from the list of the Barabási model.

The highest priority task is executed with probability greed and a random task otherwise. The executed task is replaced by a new task with a uniform random priority, and every other task ages by one step. The waiting time of the executed task is reported as wait, so the run records one waiting time per step.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
target Annotated[str, RoleSpec()]

The path of the group of tasks.

required

Examples:

>>> from skcomplex.models.social.organizations import ExecutionInteraction
>>> ExecutionInteraction('execution', target='tasks').target
'tasks'
Source code in src/skcomplex/models/social/organizations/_execution.py
40
41
42
43
44
45
46
47
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
) -> None:
    super().__init__(label)
    self.target = target

execute(model)

Execute one task and replace it with a new one.

Parameters:

Name Type Description Default
model ModelView

The model.

required
Source code in src/skcomplex/models/social/organizations/_execution.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def execute(self, model: ModelView) -> None:
    """Execute one task and replace it with a new one.

    Args:
        model:
            The model.
    """
    tasks = model.agents(self.target, TaskView)
    if not tasks:
        return
    priorities = [task.priority for task in tasks]
    greedy = model.rng.random() < tasks[0].greed
    chosen = int(np.argmax(priorities)) if greedy else int(model.rng.integers(len(tasks)))
    for index, task in enumerate(tasks):
        task.age += 1
        if index == chosen:
            task.wait, task.age, task.priority = task.age, 0.0, float(model.rng.random())
            model.report('wait', task.wait)

GravityContactInteraction(label, *, target, reach, gamma=1.0)

Bases: Interaction

Contacts of every resident, the sum of r^-gamma over the others within a reach.

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
reach float

The largest distance of a contact, which the diameter of a city covers.

required
gamma Annotated[float, ParameterSpec(domain=open(0, oo))]

The decay exponent of an interaction with the distance.

1.0

Examples:

>>> from skcomplex.models.social.organizations import GravityContactInteraction
>>> GravityContactInteraction('contact', target='residents', reach=30.0, gamma=1.5).gamma
1.5
Source code in src/skcomplex/models/social/organizations/_gravity_contact.py
43
44
45
46
47
48
49
50
51
52
53
54
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    reach: float,
    gamma: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
) -> None:
    super().__init__(label)
    self.target = target
    self.reach = reach
    self.gamma = gamma

PremisesElement(label, visual=VisualSpec('agents', {'color_by': 'brothel', 'color_range': (0.0, 1.0)}), *, brothel=0.0, bills=0.0, last_bills=0.0, efficiency=1.0, day=0.0)

Bases: Element

House or brothel of the brothel movement model, whose energy bills grow with the people inside.

Every day the bills grow by 1 / (1 + exp(x0 - n)) for the n people in the premises' cell, and every quarter they are recorded and reset. In trafficking, maps of the bills read the counts of the women by cell every day, and maps in a phase of a period of 90 days record and reset them.

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': 'brothel', 'color_range': (0.0, 1.0)})
brothel Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))]

Whether the premises are a brothel, 1, or a house, 0.

0.0
bills Annotated[float, StateSpec(domain=closed_open(0, oo))]

The bills of the current quarter.

0.0
last_bills Annotated[float, StateSpec(domain=closed_open(0, oo))]

The bills of the last full quarter.

0.0
efficiency Annotated[float, StateSpec()]

The midpoint x0 of the sigmoid of the bills.

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

The number of days run.

0.0

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.social.organizations import PremisesElement
>>> group = GroupElement('premises', element=PremisesElement, size=3, values={'brothel': 1.0})
>>> group.label, group.element.__name__, group.size
('premises', 'PremisesElement', 3)
Source code in src/skcomplex/models/social/organizations/_premises.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'brothel', 'color_range': (0.0, 1.0)}),
    *,
    brothel: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 0.0,
    bills: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    last_bills: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    efficiency: Annotated[float, StateSpec()] = 1.0,
    day: Annotated[float, StateSpec(domain=NN)] = 0.0,
) -> None:
    super().__init__(label, visual=visual)
    self.brothel = brothel
    self.bills = bills
    self.last_bills = last_bills
    self.efficiency = efficiency
    self.day = day

PremisesView(agent, stepper)

Bases: AgentView

View of a premises of the trafficking 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

RestaurantElement(label, visual=None, *, kind=0.0, sold=0.0, sold_high=0.0)

Bases: Element

Restaurant of the obesity model: a market at 0, a restaurant at 1, or a fast food outlet at 2.

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
kind Annotated[float, StateSpec(domain=NN)]

The kind of restaurant.

0.0
sold Annotated[float, StateSpec(domain=NN)]

The number of meals sold.

0.0
sold_high Annotated[float, StateSpec(domain=NN)]

The number of meals sold from the top half of the restaurant's calorie range.

0.0

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.social.organizations import RestaurantElement
>>> group = GroupElement('restaurants', element=RestaurantElement, size=3, values={'kind': 2.0})
>>> group.label, group.element.__name__, group.size
('restaurants', 'RestaurantElement', 3)
Source code in src/skcomplex/models/social/organizations/_restaurant.py
42
43
44
45
46
47
48
49
50
51
52
53
54
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    kind: Annotated[float, StateSpec(domain=NN)] = 0.0,
    sold: Annotated[float, StateSpec(domain=NN)] = 0.0,
    sold_high: Annotated[float, StateSpec(domain=NN)] = 0.0,
) -> None:
    super().__init__(label, visual=visual)
    self.kind = kind
    self.sold = sold
    self.sold_high = sold_high

RestaurantView(agent, stepper)

Bases: AgentView

View of a restaurant of the obesity 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

SupervisorElement(label, visual=None)

Bases: Element

Supervisor of the insider sabotage model, who wanders between adjacent cells.

The wandering is a MovementInteraction to a random adjacent cell, which insider_sabotage adds.

Parameters:

Name Type Description Default
label str

The label of the agent or group.

required

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.social.organizations import SupervisorElement
>>> group = GroupElement('supervisors', element=SupervisorElement, size=3)
>>> group.label, group.element.__name__, group.size
('supervisors', 'SupervisorElement', 3)
Source code in src/skcomplex/core/elements/_base.py
70
71
72
def __init__(self, label: str, visual: VisualSpec | None = None) -> None:
    super().__init__(label)
    self.visual = visual

TaskElement(label, visual=VisualSpec('histograms', {'color_by': 'age', 'shown': ('agents',)}), *, priority=0.5, age=0.0, wait=0.0, greed=0.99999)

Bases: Element

Task of the Barabási priority queue, with a priority, an age, and the waiting time when it was last executed.

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('histograms', {'color_by': 'age', 'shown': ('agents',)})
priority Annotated[float, StateSpec(domain=closed_open(0, oo))]

The priority, uniform on 0 to 1 for a new task.

0.5
age Annotated[float, StateSpec(domain=NN)]

The number of steps the task has waited.

0.0
wait Annotated[float, StateSpec(domain=NN)]

The waiting time of the last task executed from this slot of the list.

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

The probability that the highest priority task is executed rather than a random one.

0.99999

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.social.organizations import TaskElement
>>> group = GroupElement('tasks', element=TaskElement, size=2, values={'priority': [0.2, 0.7]})
>>> group.label, group.element.__name__, group.size
('tasks', 'TaskElement', 2)
Source code in src/skcomplex/models/social/organizations/_task.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('histograms', {'color_by': 'age', 'shown': ('agents',)}),
    *,
    priority: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    age: Annotated[float, StateSpec(domain=NN)] = 0.0,
    wait: Annotated[float, StateSpec(domain=NN)] = 0.0,
    greed: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.99999,
) -> None:
    super().__init__(label, visual=visual)
    self.priority = priority
    self.age = age
    self.wait = wait
    self.greed = greed

TaskView(agent, stepper)

Bases: AgentView

View of a task of the priority queue 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

TravellerElement(label, visual=VisualSpec('agents', {'color_by': 'victim', 'color_range': (0.0, 1.0)}), *, victim=0.0, trips=0.0)

Bases: Element

Woman of the brothel movement model: a trafficking victim, or a woman who travels freely.

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': 'victim', 'color_range': (0.0, 1.0)})
victim Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))]

Whether the woman is a trafficking victim, 1, or not, 0.

0.0
trips Annotated[float, StateSpec(domain=NN)]

The number of train trips between cities.

0.0

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.social.organizations import TravellerElement
>>> group = GroupElement('women', element=TravellerElement, size=4, values={'victim': 1.0})
>>> group.label, group.element.__name__, group.size
('women', 'TravellerElement', 4)
Source code in src/skcomplex/models/social/organizations/_traveller.py
40
41
42
43
44
45
46
47
48
49
50
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'victim', 'color_range': (0.0, 1.0)}),
    *,
    victim: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 0.0,
    trips: Annotated[float, StateSpec(domain=NN)] = 0.0,
) -> None:
    super().__init__(label, visual=visual)
    self.victim = victim
    self.trips = trips

TravellerView(agent, stepper)

Bases: AgentView

View of a woman of the trafficking 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

WorkplaceElement(label, visual=None, *, colour=0, employer=0)

Bases: Element

Cell of the employment takeover model: an employer, white at 0 or black at 1, or a worker, red at 0 or blue at 1.

Employers and workers alternate like the squares of a checkerboard. Every step each worker first takes the colour of the employer to its right, at x + 1. Then each employer turns black when at least three of its four workers are blue, stays as it is when two are, and turns white otherwise. The two phases are local rule interactions of the lattice, which employment_takeover adds.

Parameters:

Name Type Description Default
label str

The label of the cell or the lattice group.

required
visual VisualSpec | None

How the agent is drawn, or None for default_visual_.

None
colour Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))]

The initial colour, 0 or 1.

0
employer Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))]

Whether the cell is an employer, 1, or a worker, 0.

0

Examples:

>>> from skcomplex.models.social.organizations import WorkplaceElement
>>> WorkplaceElement('cell', employer=1).employer
1
Source code in src/skcomplex/models/social/organizations/_employment.py
65
66
67
68
69
70
71
72
73
74
75
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    colour: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 0,
    employer: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 0,
) -> None:
    super().__init__(label, visual=visual)
    self.colour = colour
    self.employer = employer

basal_metabolic_rate(weight, height, age, male)

Return the daily basal metabolic rate of the revised Harris-Benedict equation, in kilocalories.

Parameters:

Name Type Description Default
weight float

The weight in kilograms.

required
height float

The height in centimetres.

required
age float

The age in years.

required
male float

1 for a man and 0 for a woman.

required

Returns:

Name Type Description
rate float

The basal metabolic rate per day.

Examples:

>>> from skcomplex.models.social.organizations import basal_metabolic_rate
>>> round(basal_metabolic_rate(70.0, 175.0, 30.0, 1.0))
1696
Source code in src/skcomplex/models/social/organizations/_eater.py
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
def basal_metabolic_rate(weight: float, height: float, age: float, male: float) -> float:
    """Return the daily basal metabolic rate of the revised Harris-Benedict equation, in kilocalories.

    Args:
        weight:
            The weight in kilograms.

        height:
            The height in centimetres.

        age:
            The age in years.

        male:
            1 for a man and 0 for a woman.

    Returns:
        rate:
            The basal metabolic rate per day.

    Examples:
        >>> from skcomplex.models.social.organizations import basal_metabolic_rate
        >>> round(basal_metabolic_rate(70.0, 175.0, 30.0, 1.0))
        1696
    """
    if male:
        return 88.362 + 13.397 * weight + 4.799 * height - 5.677 * age
    return 447.593 + 9.247 * weight + 3.098 * height - 4.330 * age

bonabeau_hierarchies(width=20, height=20, *, density=0.5, eta=1.0, relaxation=0.1, random_state=0)

Return the Bonabeau model, where a hierarchy of strengths emerges above a critical density of agents.

Parameters:

Name Type Description Default
width int

The number of columns of the torus.

20
height int

The number of rows of the torus.

20
density float

The share of cells with an agent.

0.5
eta float

The sensitivity of the outcome of a fight to the strength difference.

1.0
relaxation float

The fraction of its strength an agent loses every step.

0.1
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 contenders.

Examples:

>>> from skcomplex.models.social.organizations import bonabeau_hierarchies
>>> bonabeau_hierarchies(10, 10, density=0.2).elements[0].check().prepare().size_
20
Source code in src/skcomplex/models/social/organizations/_hierarchies.py
120
121
122
123
124
125
126
127
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
@register_model(
    field='social/organizations',
    tags=('hierarchy', 'agents', 'grid', 'phase transition'),
    references=('Bonabeau, Theraulaz, and Deneubourg 1995', 'Castellano, Fortunato, and Loreto 2009'),
    defaults={'until': 50},
)
def bonabeau_hierarchies(
    width: int = 20,
    height: int = 20,
    *,
    density: float = 0.5,
    eta: float = 1.0,
    relaxation: float = 0.1,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the Bonabeau model, where a hierarchy of strengths emerges above a critical density of agents.

    Args:
        width:
            The number of columns of the torus.

        height:
            The number of rows of the torus.

        density:
            The share of cells with an agent.

        eta:
            The sensitivity of the outcome of a fight to the strength difference.

        relaxation:
            The fraction of its strength an agent loses every step.

        random_state:
            The seed or generator of the initial cells.

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

    Examples:
        >>> from skcomplex.models.social.organizations import bonabeau_hierarchies
        >>> bonabeau_hierarchies(10, 10, density=0.2).elements[0].check().prepare().size_
        20
    """
    size = int(width * height * density)
    cells = np.random.default_rng(random_state).permutation(width * height)[:size]
    contenders = GroupElement(
        'contenders',
        element=ContenderElement,
        size=size,
        values={'eta': eta, 'relaxation': relaxation},
        location=[(int(cell % width), int(cell // width)) for cell in cells],
    )
    return System(elements=[contenders], space=LatticeSpace((width, height), boundary='periodic', capacity=1))

employment_takeover(size=32, *, random_state=0)

Return the employment takeover model on a periodic checkerboard, with half of each kind coloured at random.

Parameters:

Name Type Description Default
size int

The even number of rows and columns.

32
random_state int | Generator | None

The seed or generator of the cells coloured 1.

0

Returns:

Name Type Description
system System

The model, with the colours cells.colour and the employers cells.employer.

Examples:

>>> from skcomplex.models.social.organizations import employment_takeover
>>> employment_takeover(4).elements[0].check().prepare().size_
16
Source code in src/skcomplex/models/social/organizations/_employment.py
 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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
@register_model(
    field='social/organizations',
    tags=('lattice', 'employment', 'takeover', 'deterministic'),
    references=('Chesney 2021',),
    defaults={'until': 50, 'dt': 1},
)
def employment_takeover(size: int = 32, *, random_state: int | np.random.Generator | None = 0) -> System:
    """Return the employment takeover model on a periodic checkerboard, with half of each kind coloured at random.

    Args:
        size:
            The even number of rows and columns.

        random_state:
            The seed or generator of the cells coloured 1.

    Returns:
        system:
            The model, with the colours `cells.colour` and the employers `cells.employer`.

    Examples:
        >>> from skcomplex.models.social.organizations import employment_takeover
        >>> employment_takeover(4).elements[0].check().prepare().size_
        16
    """
    rng = np.random.default_rng(random_state)
    rows, columns = np.indices((size, size))
    employers = ((rows + columns) % 2 == 0).astype(float)
    colours = np.zeros((size, size))
    for kind in (1.0, 0.0):
        cells = np.flatnonzero(employers.ravel() == kind)
        colours.ravel()[rng.choice(cells, len(cells) // 2, replace=False)] = 1.0
    cells = LatticeElement(
        'cells',
        element=WorkplaceElement,
        space=LatticeSpace((size, size)),
        values={'colour': colours, 'employer': employers},
    )
    phases = [
        LocalRuleInteraction(
            label,
            target='cells',
            state='colour',
            rule=rule,
            neighborhood=NEIGHBORS,
            reads=('colour', 'employer'),
        )
        for label, rule in (('workers', _follow_employer), ('employers', _follow_workers))
    ]
    return System(elements=[cells], interactions=phases)

escalation(a=10.0, b=10.5, *, margin=0.1, adjustment_time=2.0, perception_delay=1.0)

Return Meadows' escalation trap, two parties that each want to exceed the other's stock by a margin.

Each party sees the stock of the other through an information smoothing over the perception delay, wants to hold 1 + margin times that stock, and builds up the gap over the adjustment time. The loop through both stocks is reinforcing, so both stocks grow exponentially at the rate r that solves r * adjustment_time = (1 + margin) / (1 + r * perception_delay) - 1. The book gives the structure only, so the parameters are a documented choice.

Parameters:

Name Type Description Default
a float

The initial stock of the first party.

10.0
b float

The initial stock of the second party.

10.5
margin float

The fraction by which each party wants to exceed the other.

0.1
adjustment_time float

The time over which a party closes the gap to its desired stock.

2.0
perception_delay float

The time over which a party perceives the stock of the other.

1.0

Returns:

Name Type Description
system System

The model, with the stocks race.a and race.b in years, and the flows race.build_a and race.build_b. The observables of the model are race.desired_a and race.desired_b.

Examples:

>>> from skcomplex.models.social.organizations import escalation
>>> escalation(margin=0.2).elements[0].margin
0.2
Source code in src/skcomplex/models/social/organizations/_traps.py
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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
@register_model(
    field='social/organizations',
    tags=('system traps', 'arms race', 'stocks and flows', 'system dynamics', 'ode'),
    references=('Meadows 2008',),
    defaults={'until': 100.0, 'dt': 0.5},
    observables=lambda system: [
        ObservableSpec('desired_a', _desired_a, element='race'),
        ObservableSpec('desired_b', _desired_b, element='race'),
    ],
)
def escalation(
    a: float = 10.0,
    b: float = 10.5,
    *,
    margin: float = 0.1,
    adjustment_time: float = 2.0,
    perception_delay: float = 1.0,
) -> System:
    """Return Meadows' escalation trap, two parties that each want to exceed the other's stock by a margin.

    Each party sees the stock of the other through an information smoothing over the perception delay, wants to hold
    `1 + margin` times that stock, and builds up the gap over the adjustment time. The loop through both stocks is
    reinforcing, so both stocks grow exponentially at the rate `r` that solves
    `r * adjustment_time = (1 + margin) / (1 + r * perception_delay) - 1`. The book gives the structure only, so the
    parameters are a documented choice.

    Args:
        a:
            The initial stock of the first party.

        b:
            The initial stock of the second party.

        margin:
            The fraction by which each party wants to exceed the other.

        adjustment_time:
            The time over which a party closes the gap to its desired stock.

        perception_delay:
            The time over which a party perceives the stock of the other.

    Returns:
        system:
            The model, with the stocks `race.a` and `race.b` in years, and the flows `race.build_a` and
            `race.build_b`. The observables of the model are `race.desired_a` and `race.desired_b`.

    Examples:
        >>> from skcomplex.models.social.organizations import escalation
        >>> escalation(margin=0.2).elements[0].margin
        0.2
    """
    race = ArmsRaceElement(
        'race',
        a=a,
        b=b,
        margin=margin,
        adjustment_time=adjustment_time,
        perception_delay=perception_delay,
    )
    build = TransferInteraction(
        'build',
        source='race',
        target='race',
        state=Source('production'),
        into='a',
        amount=_build_a,
        flow='build_a',
        legs=[(Source('production'), 'b', _build_b, 'build_b')],
    )
    return System(elements=[race], interactions=[build])

gravity_urban_scaling(population=400, *, alpha=1.0, gamma=1.0, random_state=0)

Return the residents of a city at uniform positions in a disc, each interacting with the others as r^-gamma.

The disc has the area N^alpha, so the total interaction of a city grows as N^(2 - alpha gamma / 2) for gamma < 2, which with the population dimension D_P = 2 / alpha is the exponent 2 - gamma / D_P of Ribeiro and Netto (2024 §4). An Experiment sweeps population and fits the exponent with ScalingAnalysis(kind='power').

Parameters:

Name Type Description Default
population int

The number of residents N.

400
alpha float

The exponent of the area of the city with its population.

1.0
gamma float

The decay exponent of an interaction with the distance.

1.0
random_state int | Generator | None

The seed or generator of the positions.

0

Returns:

Name Type Description
system System

The model, with the states residents.x and residents.contacts and the observable total_contacts of the catalog, which a run passes in observables.

Examples:

>>> from skcomplex.models.social.organizations import gravity_urban_scaling
>>> gravity_urban_scaling(100).elements[0].size
100
Source code in src/skcomplex/models/social/organizations/_urban_scaling.py
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
@register_model(
    field='social/organizations',
    tags=('urban scaling', 'gravity model', 'static', 'allometry'),
    references=('Ribeiro and Netto 2024, arXiv 2404.02642', 'Ribeiro et al. 2017'),
    observables=_total_contacts,
)
def gravity_urban_scaling(
    population: int = 400,
    *,
    alpha: float = 1.0,
    gamma: float = 1.0,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the residents of a city at uniform positions in a disc, each interacting with the others as `r^-gamma`.

    The disc has the area `N^alpha`, so the total interaction of a city grows as `N^(2 - alpha gamma / 2)` for
    `gamma < 2`, which with the population dimension `D_P = 2 / alpha` is the exponent `2 - gamma / D_P` of Ribeiro and
    Netto (2024 §4). An `Experiment` sweeps `population` and fits the exponent with `ScalingAnalysis(kind='power')`.

    Args:
        population:
            The number of residents `N`.

        alpha:
            The exponent of the area of the city with its population.

        gamma:
            The decay exponent of an interaction with the distance.

        random_state:
            The seed or generator of the positions.

    Returns:
        system:
            The model, with the states `residents.x` and `residents.contacts` and the observable `total_contacts` of
            the catalog, which a run passes in `observables`.

    Examples:
        >>> from skcomplex.models.social.organizations import gravity_urban_scaling
        >>> gravity_urban_scaling(100).elements[0].size
        100
    """
    radius = np.sqrt(population**alpha / np.pi)
    rng = np.random.default_rng(random_state)
    distances = radius * np.sqrt(rng.uniform(size=population))
    angles = rng.uniform(0.0, 2 * np.pi, population)
    positions = radius + distances[:, None] * np.column_stack([np.cos(angles), np.sin(angles)])
    residents = GroupElement('residents', element=CityResidentElement, size=population, values={'x': positions})
    contact = GravityContactInteraction('contact', target='residents', reach=2.0 * radius, gamma=gamma)
    space = ContinuousSpace(extent=[2.0 * radius, 2.0 * radius])
    return System(elements=[residents], interactions=[contact], space=space, time=StaticTime())

insider_sabotage(size=20, *, n_employees=130, n_supervisors=3, turnover=0.15, random_state=0)

Return the insider sabotage model of employees whose risk of sabotage builds up over the days of a year.

Parameters:

Name Type Description Default
size int

The number of rows and columns of the torus.

20
n_employees int

The number of employees.

130
n_supervisors int

The number of supervisors.

3
turnover float

The yearly share of employees who leave.

0.15
random_state int | Generator | None

The seed or generator of the predispositions and affects.

0

Returns:

Name Type Description
system System

The model, with the groups employees and supervisors.

Examples:

>>> from skcomplex.models.social.organizations import insider_sabotage
>>> [group.size for group in insider_sabotage(n_employees=20, n_supervisors=2).elements]
[20, 2]
Source code in src/skcomplex/models/social/organizations/_sabotage.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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
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
@register_model(
    field='social/organizations',
    tags=('organizations', 'agents', 'grid', 'insider threat'),
    references=('Ardiles Cruz et al. 2019', 'Carmichael et al. 2019'),
    defaults={'until': 365},
)
def insider_sabotage(
    size: int = 20,
    *,
    n_employees: int = 130,
    n_supervisors: int = 3,
    turnover: float = 0.15,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the insider sabotage model of employees whose risk of sabotage builds up over the days of a year.

    Args:
        size:
            The number of rows and columns of the torus.

        n_employees:
            The number of employees.

        n_supervisors:
            The number of supervisors.

        turnover:
            The yearly share of employees who leave.

        random_state:
            The seed or generator of the predispositions and affects.

    Returns:
        system:
            The model, with the groups `employees` and `supervisors`.

    Examples:
        >>> from skcomplex.models.social.organizations import insider_sabotage
        >>> [group.size for group in insider_sabotage(n_employees=20, n_supervisors=2).elements]
        [20, 2]
    """
    rng = np.random.default_rng(random_state)
    predisposed = (rng.random(n_employees) < PREDISPOSED).astype(float)
    affect = rng.choice(AFFECTS, n_employees, p=AFFECT_SHARES)
    employees = GroupElement(
        'employees',
        element=EmployeeElement,
        size=n_employees,
        values={'predisposed': predisposed, 'affect': affect, 'risk': predisposed + affect, 'turnover': turnover},
    )
    supervisors = GroupElement('supervisors', element=SupervisorElement, size=n_supervisors)
    wandering = MovementInteraction('wandering', target='supervisors', schedule='member')
    maps = [
        ('feeling', _feel, 'replace'),
        ('first', _remember_first, 'replace'),
        ('disgruntlement', _disgruntle, 'replace'),
        ('days', _count_day, 'increment'),
        ('mean_feeling', _average, 'increment'),
        ('expectation', _expect, 'replace'),
        ('stress', _stress, 'increment'),
        ('skill', _learn, 'increment'),
    ]
    work = [
        MovementInteraction('working', target='employees', schedule='member'),
        *(
            MapInteraction(
                state,
                target='employees',
                state=state,
                function=function,
                mode=mode,
                schedule='member',
                form='agent',
            )
            for state, function, mode in maps
        ),
        LifeCycleInteraction('turnover', target='employees', death=_leave, renew=True, values=_hire, schedule='member'),
        MapInteraction('risk', target='employees', state='risk', function=_sum_risk, schedule='member', form='agent'),
    ]
    return System(
        elements=[employees, supervisors],
        interactions=[wandering, *work],
        space=LatticeSpace((size, size), boundary='periodic', neighborhood='von_neumann'),
    )

obesity(n_people=100, *, n_places=20, size=280.0, reach=30.0, activity=1.55, random_state=0)

Return the obesity model of adults who eat near home and work, one step per day.

Homes, workplaces, and restaurants of three kinds lie at random, and the eating habits are split equally. At home, at work, and at home again a person eats one meal at a random restaurant within reach, or at the nearest restaurant when none is within reach, so that access changes what is eaten but never skips a meal.

Parameters:

Name Type Description Default
n_people int

The number of people.

100
n_places int

The number of homes, of workplaces, and of restaurants.

20
size float

The side of the square the places lie in.

280.0
reach float

The distance within which a person looks for a restaurant.

30.0
activity float

The factor of the basal metabolic rate a person spends a day, 1.55 for moderate activity.

1.55
random_state int | Generator | None

The seed or generator of the places and the people.

0

Returns:

Name Type Description
system System

The model, with the groups people and restaurants.

Examples:

>>> from skcomplex.models.social.organizations import obesity
>>> [group.size for group in obesity(10, n_places=4, random_state=0).elements]
[10, 4]
Source code in src/skcomplex/models/social/organizations/_obesity.py
 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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
@register_model(
    field='social/organizations',
    tags=('health', 'agents', 'continuous space', 'obesity'),
    references=('Lynch et al. 2019', 'Carmichael et al. 2019'),
    defaults={'until': 70},
)
def obesity(
    n_people: int = 100,
    *,
    n_places: int = 20,
    size: float = 280.0,
    reach: float = 30.0,
    activity: float = 1.55,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the obesity model of adults who eat near home and work, one step per day.

    Homes, workplaces, and restaurants of three kinds lie at random, and the eating habits are split equally. At home,
    at work, and at home again a person eats one meal at a random restaurant within `reach`, or at the nearest
    restaurant when none is within reach, so that access changes what is eaten but never skips a meal.

    Args:
        n_people:
            The number of people.

        n_places:
            The number of homes, of workplaces, and of restaurants.

        size:
            The side of the square the places lie in.

        reach:
            The distance within which a person looks for a restaurant.

        activity:
            The factor of the basal metabolic rate a person spends a day, 1.55 for moderate activity.

        random_state:
            The seed or generator of the places and the people.

    Returns:
        system:
            The model, with the groups `people` and `restaurants`.

    Examples:
        >>> from skcomplex.models.social.organizations import obesity
        >>> [group.size for group in obesity(10, n_places=4, random_state=0).elements]
        [10, 4]
    """
    rng = np.random.default_rng(random_state)
    homes, works = rng.uniform(0, size, (2, n_places, 2))
    home, work = homes[rng.integers(n_places, size=n_people)], works[rng.integers(n_places, size=n_people)]
    male = (rng.random(n_people) < MALE_SHARE).astype(float)
    height = np.where(male > 0, rng.normal(176, 7, n_people), rng.normal(163, 7, n_people))
    weight = rng.normal(24, 3, n_people) * (height / 100) ** 2
    people = GroupElement(
        'people',
        element=EaterElement,
        size=n_people,
        values={
            'home': home,
            'work': work,
            'weight': weight,
            'height': height,
            'age': rng.uniform(18, 65, n_people),
            'male': male,
            'habit': np.arange(n_people) % 3,
            'bmi': weight / (height / 100) ** 2,
            'reach': reach,
            'activity': activity,
        },
        location=home,
    )
    restaurants = GroupElement(
        'restaurants',
        element=RestaurantElement,
        size=n_places,
        values={'kind': rng.integers(0, 3, n_places).astype(float)},
        location=rng.uniform(0, size, (n_places, 2)),
    )
    interactions: list[BaseInteraction] = []
    for phase, step in (
        ('home', EaterElement.go_home),
        ('work', EaterElement.go_to_work),
        ('evening', EaterElement.go_home),
    ):
        interactions += [
            MovementInteraction(f'go_{phase}', target='people', step=step, phase=phase),
            TransferInteraction(
                f'meal_{phase}',
                source='people',
                target='restaurants',
                state='calories',
                into='sold',
                amount=EaterElement.draw_meal,
                pairing=PartnerSpec('reach', fallback='nearest'),
                form='agent',
                phase=phase,
                settlement=EaterElement.settle_meal,
            ),
        ]
    phases = (PhaseSpec('home'), PhaseSpec('work'), PhaseSpec('evening'), PhaseSpec('week', period=DAYS_PER_WEEK))
    return System(
        elements=[people, restaurants],
        interactions=interactions,
        space=ContinuousSpace(extent=(size, size)),
        time=DiscreteTime(schedule=PhasesScheduleSpec(phases)),
    )

priority_queues(n_tasks=2, *, greed=0.99999, random_state=0)

Return the Barabási priority queue, whose waiting times follow a power law as the choice becomes greedy.

Parameters:

Name Type Description Default
n_tasks int

The length of the list of tasks.

2
greed float

The probability that the highest priority task is executed rather than a random one.

0.99999
random_state int | Generator | None

The seed or generator of the initial priorities.

0

Returns:

Name Type Description
system System

The model, with the group tasks and the execution execution, which reports the waiting time of the task it executes at every step.

Examples:

>>> from skcomplex.models.social.organizations import priority_queues
>>> priority_queues(3, random_state=0).elements[0].check().prepare().size_
3
Source code in src/skcomplex/models/social/organizations/_queues.py
16
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
@register_model(
    field='social/organizations',
    tags=('queues', 'agents', 'bursts', 'power law'),
    references=('Barabási 2005', 'Castellano, Fortunato, and Loreto 2009'),
    defaults={'until': 200},
)
def priority_queues(
    n_tasks: int = 2,
    *,
    greed: float = 0.99999,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the Barabási priority queue, whose waiting times follow a power law as the choice becomes greedy.

    Args:
        n_tasks:
            The length of the list of tasks.

        greed:
            The probability that the highest priority task is executed rather than a random one.

        random_state:
            The seed or generator of the initial priorities.

    Returns:
        system:
            The model, with the group `tasks` and the execution `execution`, which reports the waiting time of the
            task it executes at every step.

    Examples:
        >>> from skcomplex.models.social.organizations import priority_queues
        >>> priority_queues(3, random_state=0).elements[0].check().prepare().size_
        3
    """
    priorities = np.random.default_rng(random_state).random(n_tasks)
    tasks = GroupElement('tasks', element=TaskElement, size=n_tasks, values={'priority': priorities, 'greed': greed})
    return System(elements=[tasks], interactions=[ExecutionInteraction('execution', target='tasks')])

shifting_the_burden(capability=0.5, intervention=0.0, *, desired=1.0, fix_rate=1.0, build_time=5.0, solution_delay=2.0, erosion=0.5)

Return Meadows' shifting the burden trap, a quick fix that relieves a symptom and erodes the system's capability.

The symptom is the gap between the desired state and the state that the capability of the system and an outside intervention deliver together. The intervention grows quickly with the symptom. The capability grows slowly with the symptom, perceived over the solution delay, and the intervention erodes it as a side effect. With a quick fix the intervention takes over and the capability withers away, an addiction. Without one, fix_rate = 0, the capability grows to the desired state. The book gives the structure only, so the parameters are a documented choice.

Parameters:

Name Type Description Default
capability float

The initial capability of the system to deliver the desired state on its own.

0.5
intervention float

The initial level of the intervention.

0.0
desired float

The desired state.

1.0
fix_rate float

The rate at which the intervention grows per unit of symptom, 0 for no quick fix.

1.0
build_time float

The time the fundamental solution needs to close a perceived symptom.

5.0
solution_delay float

The time over which the fundamental solution perceives the symptom.

2.0
erosion float

The rate at which a unit of intervention erodes the capability.

0.5

Returns:

Name Type Description
system System

The model, with the capability burden.capability and the intervention burden.intervention in years, the flows burden.fixing, burden.building, and burden.erosion. The observable of the model is burden.symptom.

Examples:

>>> from skcomplex.models.social.organizations import shifting_the_burden
>>> shifting_the_burden(fix_rate=0.0).elements[0].fix_rate
0.0
Source code in src/skcomplex/models/social/organizations/_traps.py
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
237
238
239
240
241
242
243
244
245
246
247
248
249
@register_model(
    field='social/organizations',
    tags=('system traps', 'addiction', 'stocks and flows', 'system dynamics', 'ode'),
    references=('Meadows 2008',),
    defaults={'until': 60.0, 'dt': 0.5},
    observables=lambda system: [ObservableSpec('symptom', _symptom, element='burden')],
)
def shifting_the_burden(
    capability: float = 0.5,
    intervention: float = 0.0,
    *,
    desired: float = 1.0,
    fix_rate: float = 1.0,
    build_time: float = 5.0,
    solution_delay: float = 2.0,
    erosion: float = 0.5,
) -> System:
    """Return Meadows' shifting the burden trap, a quick fix that relieves a symptom and erodes the system's capability.

    The symptom is the gap between the desired state and the state that the capability of the system and an outside
    intervention deliver together. The intervention grows quickly with the symptom. The capability grows slowly with
    the symptom, perceived over the solution delay, and the intervention erodes it as a side effect. With a quick fix
    the intervention takes over and the capability withers away, an addiction. Without one, `fix_rate = 0`, the
    capability grows to the desired state. The book gives the structure only, so the parameters are a documented
    choice.

    Args:
        capability:
            The initial capability of the system to deliver the desired state on its own.

        intervention:
            The initial level of the intervention.

        desired:
            The desired state.

        fix_rate:
            The rate at which the intervention grows per unit of symptom, 0 for no quick fix.

        build_time:
            The time the fundamental solution needs to close a perceived symptom.

        solution_delay:
            The time over which the fundamental solution perceives the symptom.

        erosion:
            The rate at which a unit of intervention erodes the capability.

    Returns:
        system:
            The model, with the capability `burden.capability` and the intervention `burden.intervention` in years,
            the flows `burden.fixing`, `burden.building`, and `burden.erosion`. The observable of the model is
            `burden.symptom`.

    Examples:
        >>> from skcomplex.models.social.organizations import shifting_the_burden
        >>> shifting_the_burden(fix_rate=0.0).elements[0].fix_rate
        0.0
    """
    burden = BurdenElement(
        'burden',
        capability=capability,
        intervention=intervention,
        desired=desired,
        fix_rate=fix_rate,
        build_time=build_time,
        solution_delay=solution_delay,
        erosion=erosion,
    )
    flows = TransferInteraction(
        'flows',
        source='burden',
        target='burden',
        state=Source('intervener'),
        into='intervention',
        amount=_fixing,
        flow='fixing',
        legs=[
            (Source('learning'), 'capability', _building, 'building'),
            ('capability', Sink('atrophy'), _erosion, 'erosion'),
        ],
    )
    return System(elements=[burden], interactions=[flows])

success_to_the_successful(a=55.0, b=45.0, *, budget=10.0, lifetime=10.0)

Return Meadows' success to the successful trap, two competitors that share a budget by their past success.

Each competitor holds a stock of resources that its share of the budget fills and that depreciates over its lifetime. The share of the first competitor is a table function of its success, its fraction of all resources, that gives more than a proportional share to the more successful one. The two reinforcing loops make the symmetric state unstable, so the competitor that starts larger ends with the whole budget and the other one disappears. The book gives the structure only, so the table and the parameters are a documented choice.

Parameters:

Name Type Description Default
a float

The initial resources of the first competitor.

55.0
b float

The initial resources of the second competitor.

45.0
budget float

The resources allocated per year.

10.0
lifetime float

The average lifetime of resources, in years.

10.0

Returns:

Name Type Description
system System

The model, with the resources contest.a and contest.b in years, the flows contest.allocation_a, contest.allocation_b, contest.depreciation_a, and contest.depreciation_b. The observables of the model are contest.success and contest.allocation.

Examples:

>>> from skcomplex.models.social.organizations import success_to_the_successful
>>> success_to_the_successful(a=60.0).elements[0].a
60.0
Source code in src/skcomplex/models/social/organizations/_traps.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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
159
160
161
162
163
164
@register_model(
    field='social/organizations',
    tags=('system traps', 'competitive exclusion', 'stocks and flows', 'system dynamics', 'ode'),
    references=('Meadows 2008',),
    defaults={'until': 100.0, 'dt': 0.5},
    observables=lambda system: [
        ObservableSpec('success', _success, element='contest'),
        ObservableSpec('allocation', _allocation, element='contest'),
    ],
)
def success_to_the_successful(
    a: float = 55.0,
    b: float = 45.0,
    *,
    budget: float = 10.0,
    lifetime: float = 10.0,
) -> System:
    """Return Meadows' success to the successful trap, two competitors that share a budget by their past success.

    Each competitor holds a stock of resources that its share of the budget fills and that depreciates over its
    lifetime. The share of the first competitor is a table function of its success, its fraction of all resources,
    that gives more than a proportional share to the more successful one. The two reinforcing loops make the symmetric
    state unstable, so the competitor that starts larger ends with the whole budget and the other one disappears. The
    book gives the structure only, so the table and the parameters are a documented choice.

    Args:
        a:
            The initial resources of the first competitor.

        b:
            The initial resources of the second competitor.

        budget:
            The resources allocated per year.

        lifetime:
            The average lifetime of resources, in years.

    Returns:
        system:
            The model, with the resources `contest.a` and `contest.b` in years, the flows `contest.allocation_a`,
            `contest.allocation_b`, `contest.depreciation_a`, and `contest.depreciation_b`. The observables of the
            model are `contest.success` and `contest.allocation`.

    Examples:
        >>> from skcomplex.models.social.organizations import success_to_the_successful
        >>> success_to_the_successful(a=60.0).elements[0].a
        60.0
    """
    allocation = TransferInteraction(
        'allocation',
        source='contest',
        target='contest',
        state=Source('budget'),
        into='a',
        amount=_allocation_a,
        flow='allocation_a',
        legs=[
            (Source('budget'), 'b', _allocation_b, 'allocation_b'),
            ('a', Sink('depreciation'), _depreciation_a, 'depreciation_a'),
            ('b', Sink('depreciation'), _depreciation_b, 'depreciation_b'),
        ],
    )
    return System(
        elements=[ContestElement('contest', a=a, b=b, budget=budget, lifetime=lifetime)],
        interactions=[allocation],
    )

trafficking(width=30, height=30, *, n_cities=3, n_houses=40, n_brothels=3, n_victims=12, n_free=30, random_state=0)

Return the brothel movement model, where victims moved between brothels leave traces in bills and train trips.

Parameters:

Name Type Description Default
width int

The number of columns of the grid.

30
height int

The number of rows of the grid.

30
n_cities int

The number of cities, vertical bands of equal width.

3
n_houses int

The number of houses.

40
n_brothels int

The number of brothels.

3
n_victims int

The number of victims, spread over the brothels.

12
n_free int

The number of women who travel freely.

30
random_state int | Generator | None

The seed or generator of the places, the women, and the efficiencies.

0

Returns:

Name Type Description
system System

The model, with the groups premises and women.

Examples:

>>> from skcomplex.models.social.organizations import trafficking
>>> [group.size for group in trafficking(n_houses=5, n_brothels=2, n_victims=4, n_free=3).elements]
[7, 7]
Source code in src/skcomplex/models/social/organizations/_trafficking.py
 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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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
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
@register_model(
    field='social/organizations',
    tags=('trafficking', 'agents', 'grid', 'crime'),
    references=('Chesney 2021',),
    defaults={'until': 90},
)
def trafficking(
    width: int = 30,
    height: int = 30,
    *,
    n_cities: int = 3,
    n_houses: int = 40,
    n_brothels: int = 3,
    n_victims: int = 12,
    n_free: int = 30,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the brothel movement model, where victims moved between brothels leave traces in bills and train trips.

    Args:
        width:
            The number of columns of the grid.

        height:
            The number of rows of the grid.

        n_cities:
            The number of cities, vertical bands of equal width.

        n_houses:
            The number of houses.

        n_brothels:
            The number of brothels.

        n_victims:
            The number of victims, spread over the brothels.

        n_free:
            The number of women who travel freely.

        random_state:
            The seed or generator of the places, the women, and the efficiencies.

    Returns:
        system:
            The model, with the groups `premises` and `women`.

    Examples:
        >>> from skcomplex.models.social.organizations import trafficking
        >>> [group.size for group in trafficking(n_houses=5, n_brothels=2, n_victims=4, n_free=3).elements]
        [7, 7]
    """
    rng = np.random.default_rng(random_state)
    n_places = n_houses + n_brothels
    cells = rng.permutation(width * height)[:n_places]
    places = [(int(cell % width), int(cell // width)) for cell in cells]
    brothel = (np.arange(n_places) >= n_houses).astype(float)
    premises = GroupElement(
        'premises',
        element=PremisesElement,
        size=n_places,
        values={'brothel': brothel, 'efficiency': rng.uniform(0, 3, n_places)},
        location=places,
    )
    homes = [places[n_houses + index % n_brothels] for index in range(n_victims)]
    homes += [(int(rng.integers(width)), int(rng.integers(height))) for _ in range(n_free)]
    victim = (np.arange(n_victims + n_free) < n_victims).astype(float)
    women = GroupElement(
        'women',
        element=TravellerElement,
        size=n_victims + n_free,
        values={'victim': victim},
        location=homes,
    )
    day = DailyMovementsInteraction(
        'day',
        women='women',
        premises='premises',
        n_cities=n_cities,
        phase=DAILY,
        victims_phase=MONTHLY,
    )
    bills = [
        MapInteraction(
            'days',
            target='premises',
            state='day',
            function=_count_day,
            mode='increment',
            form='agent',
            phase=BILLING,
        ),
        MapInteraction(
            'bills',
            target='premises',
            state='bills',
            function=_heat,
            sources=['women'],
            mode='increment',
            form='agent',
            phase=BILLING,
        ),
        MapInteraction(
            'record',
            target='premises',
            state='last_bills',
            function=_record,
            form='agent',
            phase=QUARTERLY,
        ),
        MapInteraction('reset', target='premises', state='bills', function=_reset, form='agent', phase=QUARTERLY),
    ]
    phases = [
        PhaseSpec(DAILY, activation='sequential'),
        PhaseSpec(MONTHLY, activation='sequential', period=MONTH),
        PhaseSpec(BILLING, activation='sequential'),
        PhaseSpec(QUARTERLY, activation='sequential', period=QUARTER),
    ]
    return System(
        elements=[premises, women],
        interactions=[day, *bills],
        space=LatticeSpace((width, height), boundary='fixed'),
        time=DiscreteTime(schedule=PhasesScheduleSpec(phases)),
    )