Hora and Tempus watchmakers
Simon's fable of the watchmakers Hora and Tempus, told by Meadows, with and without stable subassemblies.
- Name:
watchmakers - Field:
social/organizations - Formalism: agent-based system
- References: Simon 1962; Meadows 2008
Select this system
from skcomplex.systems import create_simulation, create_system, describe
system = create_system('watchmakers')
entry = describe('watchmakers')
trajectory = create_simulation('watchmakers').prepare().run().trajectory_
Factory
@register_system(
field='social/organizations',
tags=('hierarchy', 'agents', 'stable intermediate forms', 'interruptions'),
references=('Simon 1962', 'Meadows 2008'),
defaults={'until': 5000},
)
def watchmakers(
parts: int = 1000,
*,
subassembly: int = 10,
interruption: float = 0.01,
) -> System:
"""Return Simon's fable of the watchmakers Hora and Tempus, told by Meadows, with and without stable subassemblies.
Both watchmakers build watches of `parts` parts. Tempus puts all of them together at once, so an interruption,
a customer's call, makes the unfinished watch fall to pieces. Hora puts together stable subassemblies of
`subassembly` elements, then assemblies of those, up to the whole watch, so an interruption loses only the
assembly in hand. Hora has more actions to take, `parts - 1` divided by `subassembly - 1` tasks of `subassembly`
actions each, 111 tasks of 10 actions for 1000 parts. A task of `k` actions interrupted with the probability `p` at
every action takes `((1 - p)^-k - 1) / p` actions on average, so at `p = 0.01` Hora finishes a watch every 1173
steps and Tempus one every 2.3 million steps. The book tells the fable as a narrative, so the step of one action
and the interruption at every action follow Simon's original telling.
Args:
parts:
The number of parts of a watch, a power of `subassembly`.
subassembly:
The number of elements of a stable assembly of Hora.
interruption:
The probability that a watchmaker is interrupted at an action.
Returns:
system:
The system, with the group `watchmakers`, Hora first and Tempus second.
Examples:
>>> from skcomplex.systems.social.organizations import watchmakers
>>> watchmakers().elements[0].prepare().size_
2
"""
hora_tasks = (parts - 1) // (subassembly - 1)
group = GroupElement(
'watchmakers',
element=WatchmakerElement,
size=2,
values={'task_size': [float(subassembly), float(parts)], 'tasks': [float(hora_tasks), 1.0]},
)
return System(
elements=[group],
interactions=[AssemblyInteraction('assembly', target='watchmakers', interruption=interruption)],
)
Components
Elements:
WatchmakerElement, fromElement
Interactions:
AssemblyInteraction, fromInteraction
class WatchmakerElement(Element):
"""Watchmaker who assembles a watch as a number of tasks of a number of actions each.
Args:
label:
The label of the watchmaker or the group.
visual:
How the agent is drawn, or `None` for `default_visual_`.
progress:
The number of actions done on the task in hand, which an interruption undoes.
done:
The number of stable tasks done on the watch in hand.
watches:
The number of finished watches.
task_size:
The number of actions of a task, the parts or subassemblies put together into a stable assembly.
tasks:
The number of tasks of a watch.
Examples:
>>> from skcomplex.systems.social.organizations import WatchmakerElement
>>> WatchmakerElement('tempus', task_size=1000.0, tasks=1.0).task_size
1000.0
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
progress: Annotated[float, StateSpec(domain=NN)] = 0.0,
done: Annotated[float, StateSpec(domain=NN)] = 0.0,
watches: Annotated[float, StateSpec(domain=NN)] = 0.0,
task_size: Annotated[float, StateSpec(domain=NN)] = 10.0,
tasks: Annotated[float, StateSpec(domain=NN)] = 111.0,
) -> None:
super().__init__(label, visual=visual)
self.progress = progress
self.done = done
self.watches = watches
self.task_size = task_size
self.tasks = tasks
@classmethod
def _get_view(cls) -> type[WatchmakerView]:
"""Return the class of the views of the agents, whose attributes are the states and parameters."""
return WatchmakerView
class AssemblyInteraction(Interaction):
"""Assembly of the Hora and Tempus fable, one action per step that an interruption can undo.
Every step every watchmaker takes one action on the task in hand, unless a customer calls, with the probability
`interruption`, and the unfinished task falls to pieces. A finished task is stable, and a watchmaker who finishes
the last task of a watch sells it and starts the next one.
Args:
label:
The label of the interaction.
target:
The path of the group of watchmakers.
interruption:
The probability that a watchmaker is interrupted at an action.
Examples:
>>> from skcomplex.systems.social.organizations import AssemblyInteraction
>>> AssemblyInteraction('assembly', target='watchmakers').interruption
0.01
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
interruption: Annotated[float, ParameterSpec(domain=RealSet.closed(0, 1))] = 0.01,
) -> None:
super().__init__(label)
self.target = target
self.interruption = interruption
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the assembly rule.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the component.
"""
return [RuleLaw(self.assemble)]
def assemble(self, system: SystemView) -> None:
"""Take one action of every watchmaker, or undo the task in hand at an interruption.
Args:
system:
The system.
"""
for watchmaker in system.agents(self.target, WatchmakerView):
if system.rng.random() < self.interruption:
watchmaker.progress = 0.0
continue
watchmaker.progress += 1.0
if watchmaker.progress < watchmaker.task_size:
continue
watchmaker.progress = 0.0
watchmaker.done += 1.0
if watchmaker.done >= watchmaker.tasks:
watchmaker.done = 0.0
watchmaker.watches += 1.0