Criminality and security
The kinetic model of criminality in a society and the security forces that act against it.
- Name:
criminality - Field:
social/crowds - Formalism: ordinary differential equations
- References: Bellomo et al. 2017; Bellomo, Colasuonno, Knopoff, and Soler 2015
Factory
@register_model(
field='social/crowds',
tags=('criminality', 'security', 'kinetic', 'distribution'),
references=CRIMINALITY_REFERENCES,
defaults={'until': 60, 'dt': 0.5},
observables=lambda system: criminality_observables(*_sizes_of(system)),
)
def criminality(
classes: int = 5,
forces: int = 3,
*,
criminals: float = 0.2,
security: float = 0.05,
attraction: float = 0.3,
redemption: float = 0.1,
deterrence: float = 2.0,
recruitment: float = 0.5,
retirement: float = 0.1,
) -> System:
"""Return the kinetic model of criminality in a society and the security forces that act against it.
Citizens are a distribution over criminality classes and security agents a distribution over efficacy classes,
held by site groups on lattices of the classes. Encounters between citizens spread crime when it attracts more than
honesty redeems, encounters with security agents move criminal citizens toward honesty, a micro-scale action, and
the security forces grow in proportion to the mean criminality, a macro-scale action that reads a moment of the
citizens. The security forces grow while crime is high, crime recedes, and the forces retire, so crime and
security oscillate toward a balance where some crime persists.
Args:
classes:
The number of criminality classes.
forces:
The number of efficacy classes of the security forces.
criminals:
The initial fraction of citizens in the most criminal class, the others being honest.
security:
The initial density of the security forces, spread evenly over their efficacy classes.
attraction:
The probability that a citizen who meets a more criminal one moves one class up.
redemption:
The probability that a citizen who meets a more honest one moves one class down.
deterrence:
The rate at which the most effective agents deter a criminal citizen.
recruitment:
The recruitment rate of security agents per unit of mean criminality.
retirement:
The retirement rate of security agents.
Returns:
system:
The model, with the distributions `citizens` and `security` and the interactions `contagion`,
`deterrence`, and `recruitment`. Its standard observables are the mean criminality, the criminal fraction,
and the density of the security forces.
Examples:
>>> from skcomplex.models.social.crowds import criminality
>>> [item.label for item in criminality().interactions]
['contagion', 'deterrence', 'recruitment']
"""
citizens = np.zeros(classes)
citizens[0], citizens[-1] = 1.0 - criminals, criminals
return System(
elements=[
GroupElement('citizens', element=DensityElement, size=classes, on='sites', values={'f': citizens}),
GroupElement(
'security',
element=SecurityAgentsElement,
size=forces,
on='sites',
space=LatticeSpace(forces, boundary='fixed'),
values={'f': np.full(forces, security / forces), 'share': np.full(forces, 1.0 / forces)},
),
],
interactions=[
CriminalContagionInteraction(
'contagion',
candidate='citizens',
field='citizens',
classes=classes,
attraction=attraction,
redemption=redemption,
),
DeterrenceInteraction(
'deterrence',
candidate='citizens',
field='security',
classes=classes,
forces=forces,
deterrence=deterrence,
),
RecruitmentInteraction(
'recruitment',
target='security',
citizens='citizens',
classes=classes,
recruitment=recruitment,
retirement=retirement,
),
],
space=LatticeSpace(classes, boundary='fixed'),
time=ContinuousTime(),
)
Components
class DensityElement(Element):
"""Value of a density at one class, a member of a distribution over the classes.
A distribution is a group of densities on the sites of a lattice of classes, one member per class, and encounter,
proliferation, and destruction laws change it. A density has no laws of its own.
Args:
label:
The label of the density, unique among the parts of its system.
visual:
How the element is drawn, or `None` for `default_visual_`.
f:
The initial density, a nonnegative number.
Attributes:
default_visual_:
The visual of the element when it sets none.
Examples:
>>> from skcomplex.core.elements import DensityElement, GroupElement
>>> from skcomplex.core.spaces import LatticeSpace
>>> classes = LatticeSpace(5, boundary='fixed')
>>> group = GroupElement('distribution', element=DensityElement, space=classes, on='sites', values={'f': 0.2})
>>> group.check().prepare().size_
5
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
f: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
) -> None:
super().__init__(label, visual=visual)
self.f = f
class SecurityAgentsElement(Element):
"""Density of security agents at one efficacy class, with the share of new recruits that join the class.
Args:
label:
The label of the distribution.
visual:
How the agent is drawn, or `None` for `default_visual_`.
f:
The initial density.
share:
The share of new recruits that join the efficacy class.
Examples:
>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.social.crowds import SecurityAgentsElement
>>> GroupElement('security', element=SecurityAgentsElement, size=3, values={'share': [0.5, 0.3, 0.2]}).size
3
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
f: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
share: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
) -> None:
super().__init__(label, visual=visual)
self.f = f
self.share = share
class CriminalContagionInteraction(EncounterInteraction):
"""Conservative encounters between citizens, in which crime attracts and honesty redeems.
Args:
label:
The label of the interaction.
candidate:
The path of the distribution of citizens whose members move.
field:
The path of the distribution of citizens they meet, usually the same distribution.
classes:
The number of criminality classes.
eta:
The encounter rate.
attraction:
The probability that a citizen who meets a more criminal one moves one class up.
redemption:
The probability that a citizen who meets a more honest one moves one class down.
Examples:
>>> from skcomplex.models.social.crowds import CriminalContagionInteraction
>>> CriminalContagionInteraction('contagion', candidate='citizens', field='citizens').attraction
0.3
"""
def __init__(
self,
label: str,
*,
candidate: Annotated[str, RoleSpec()],
field: Annotated[str, RoleSpec()],
classes: Annotated[int, ParameterSpec(domain=RealSet.open(0, oo))] = 5,
eta: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
attraction: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.3,
redemption: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
) -> None:
super().__init__(label, candidate=candidate, field=field, rate=self._get_rate, kernel=self._get_kernel)
self.classes = classes
self.eta = eta
self.attraction = attraction
self.redemption = redemption
def _get_rate(self, s: SimpleNamespace) -> object:
"""Return the encounter rate."""
return s.eta
def _get_kernel(self, s: SimpleNamespace) -> object:
"""Return the table of the attraction of crime and the redemption of honesty."""
return contagion_table(int(self.classes), s.attraction, s.redemption)
class DeterrenceInteraction(EncounterInteraction):
"""Encounters of citizens with security forces, which move criminal citizens toward honesty.
Args:
label:
The label of the interaction.
candidate:
The path of the distribution of citizens.
field:
The path of the distribution of the security forces over efficacy.
classes:
The number of criminality classes.
forces:
The number of efficacy classes of the security forces.
deterrence:
The rate at which the most effective agents deter a criminal citizen.
Examples:
>>> from skcomplex.models.social.crowds import DeterrenceInteraction
>>> DeterrenceInteraction('deterrence', candidate='citizens', field='security').deterrence
2.0
"""
def __init__(
self,
label: str,
*,
candidate: Annotated[str, RoleSpec()],
field: Annotated[str, RoleSpec()],
classes: Annotated[int, ParameterSpec(domain=RealSet.open(0, oo))] = 5,
forces: Annotated[int, ParameterSpec(domain=RealSet.open(0, oo))] = 3,
deterrence: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 2.0,
) -> None:
super().__init__(label, candidate=candidate, field=field, rate=self._get_rate, kernel=self._get_kernel)
self.classes = classes
self.forces = forces
self.deterrence = deterrence
def _get_rate(self, s: SimpleNamespace) -> object:
"""Return the deterrence rate of every pair of a criminality class and an efficacy class."""
return deterrence_table(int(self.classes), int(self.forces))[0] * s.deterrence
def _get_kernel(self, s: SimpleNamespace) -> object:
"""Return the table that moves a deterred citizen one class down."""
return deterrence_table(int(self.classes), int(self.forces))[1]
class RecruitmentInteraction(RateInteraction):
"""Recruitment of security agents in proportion to the mean criminality, a macro-scale external action.
It is a `RateInteraction` of the density `f` of the security forces that reads the citizens. The security forces
grow at the rate `recruitment m share_k` in efficacy class `k`, where `m` is the mean criminality of the citizens,
a moment of their distribution, and retire at the rate `retirement`.
Args:
label:
The label of the interaction.
target:
The path of the distribution of the security forces over efficacy.
citizens:
The path of the distribution of citizens.
classes:
The number of criminality classes.
recruitment:
The recruitment rate per unit of mean criminality.
retirement:
The retirement rate.
Examples:
>>> from skcomplex.models.social.crowds import RecruitmentInteraction
>>> RecruitmentInteraction('recruitment', target='security', citizens='citizens').retirement
0.1
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
citizens: Annotated[str, RoleSpec()],
classes: Annotated[int, ParameterSpec(domain=RealSet.open(0, oo))] = 5,
recruitment: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
retirement: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
) -> None:
super().__init__(label, target=target, state='f', rate=self._get_rate)
self.citizens = citizens
self.classes = classes
self.recruitment = recruitment
self.retirement = retirement
def _get_rate(self, s: SimpleNamespace) -> Expression:
"""Return the recruitment in proportion to the mean criminality of the citizens, less the retirement."""
security, citizens = s.target, s.citizens
criminality = moment(citizens.f, 1, criminality_grid(int(self.classes)), normalized=True)
return s.recruitment * criminality * security.share - s.retirement * security.f
Equations
citizens.f[i]: diff(f_citizens(t), t) == 0
security.f[i]: diff(f_security(t), t) == _reduced_0*recruitment_recruitment*share_security/_reduced_1 - retirement_recruitment*f_security(t)