Employment takeover
The employment takeover model on a periodic checkerboard, with half of each kind coloured at random.
- Name:
employment_takeover - Field:
social/organizations - Formalism: lattice dynamics
- References: Chesney 2021
Factory
@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)
Components
class WorkplaceElement(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.
Args:
label:
The label of the cell or the lattice group.
visual:
How the agent is drawn, or `None` for `default_visual_`.
colour:
The initial colour, 0 or 1.
employer:
Whether the cell is an employer, 1, or a worker, 0.
Examples:
>>> from skcomplex.models.social.organizations import WorkplaceElement
>>> WorkplaceElement('cell', employer=1).employer
1
"""
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
class LocalRuleInteraction(Interaction):
"""Local rule of a cellular automaton, which sets the next value of every cell from its ordered neighbourhood.
The rule is a table of the next value of every configuration of the neighbourhood, `TableSpec`, a rule of the sum
of the neighbourhood, `TotalisticSpec`, a rule of the cell and the sum of its neighbours, `OuterTotalisticSpec`, an
object whose `table()` returns a `TableSpec`, or a function. A function receives one array per neighbour of every
read state, in neighbourhood order, with one value per cell, the uniform array `random` when the rule is stochastic,
and the parameters of the bound group it names, and returns the next values of the states, one array per state.
Args:
label:
The label of the interaction, unique among the parts of its system.
target:
The path of the group on the cells of a lattice, the nodes of a network, or a Cayley graph.
state:
The name of the state the rule sets, or a tuple of the names of the states it sets together.
rule:
The table, the totalistic rule, or the function.
neighborhood:
The ordered neighbourhood of every cell, such as `VonNeumannNeighborhood(center=True)`.
reads:
The names of the states whose neighbourhoods a function reads, or `None` for the states it sets.
schedule:
`'synchronous'`, `'random-sequential'`, `'random-site'`, `'checkerboard'`, or `'colored'`.
stochastic:
Whether a function receives the uniform random array `random`.
Raises:
ValidationError:
The state or the read states are not names, or the neighbourhood is not a neighbourhood.
Examples:
>>> from skcomplex.core.interactions import LocalRuleInteraction
>>> from skcomplex.core.spaces import VonNeumannNeighborhood
>>> from skcomplex.specs import TotalisticSpec
>>> parity = TotalisticSpec({1: 1, 3: 1})
>>> around = VonNeumannNeighborhood(n_dim=1, center=True)
>>> rule = LocalRuleInteraction('parity', target='cells', state='cell', rule=parity, neighborhood=around)
>>> rule.check().schedule
'synchronous'
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
state: str | tuple[str, ...],
rule: object,
neighborhood: BaseNeighborhood,
reads: Sequence[str] | None = None,
schedule: str = 'synchronous',
stochastic: bool = False,
) -> None:
super().__init__(label)
self.target = target
self.state = state
self.rule = rule
self.neighborhood = neighborhood
self.reads = reads
self.schedule = schedule
self.stochastic = stochastic
def _get_names(self) -> tuple[str, ...]:
"""Return the names of the states the rule sets."""
return (self.state,) if isinstance(self.state, str) else tuple(self.state)
def _check(self) -> None:
"""Raise an error for states that are not names, or a neighbourhood that is not one.
Raises:
ValidationError:
The state or the read states are not names, or the neighbourhood is not a neighbourhood.
"""
super()._check()
names = [*self._get_names(), *(self.reads or ())]
if not names or not all(isinstance(name, str) and name.isidentifier() for name in names):
error_msg = f'The local rule {self.label!r} sets {self.state!r} and reads {self.reads!r}. Give state names.'
raise ValidationError(error_msg)
if not isinstance(self.neighborhood, BaseNeighborhood):
error_msg = f'The neighbourhood of the local rule {self.label!r} is {self.neighborhood!r}. Give one.'
raise ValidationError(error_msg)
def _get_laws(self, namespace: SimpleNamespace) -> list[BaseLaw]:
"""Return the local rule law of the states of the target.
Args:
namespace:
The namespace of the symbols the laws can use, with `namespace.target`.
Returns:
laws:
The local rule law.
"""
targets = tuple(getattr(self._get_member(namespace, 'target'), name) for name in self._get_names())
reads = (
None
if self.reads is None
else tuple(getattr(self._get_member(namespace, 'target'), name) for name in self.reads)
)
return [
LocalRuleLaw(
targets[0] if isinstance(self.state, str) else targets,
self.neighborhood,
self.rule,
reads=reads,
schedule=self.schedule,
stochastic=self.stochastic,
label=self.label,
),
]