Triadic percolation
Triadic percolation, where regulating nodes switch links on and off from the giant component.
- Name:
triadic_percolation - Field:
mathematics/networks - Formalism: discrete map
- References: Sun et al. 2023; Millán, Sun, Torres, and Bianconi 2023
Factory
@register_model(
field='mathematics/networks',
tags=('network', 'hypergraph', 'percolation', 'triadic interactions', 'period doubling'),
references=('Sun et al. 2023', 'Millán, Sun, Torres, and Bianconi 2023'),
defaults={'until': 30, 'dt': 1},
observables=_giant_observables,
)
def triadic_percolation(
n_nodes: int = 1000,
*,
mean_degree: float = 4.0,
p: float = 0.5,
negative: float = 1.0,
random_state: int | np.random.Generator | None = 0,
) -> System:
"""Return triadic percolation, where regulating nodes switch links on and off from the giant component.
Every link of an Erdős-Rényi graph has one regulating node, drawn at random among the other nodes, which is
negative with probability `negative` and positive otherwise. The link and its regulator form a hyperedge of size
three. Every step is a time step of Millán et al. in two phases. In the phase `regulation` every link is active
with probability `p` when its positive regulator is active or its negative regulator is inactive, and inactive
otherwise. In the phase `percolation` a node is active when it belongs to the giant component of the links the
regulation of the same step left active. All nodes and links start active.
With the fraction `R` of nodes in the giant component, the links are active with probability
`p_L = p ((1 - negative) R + negative (1 - R))`, and `R` follows the map `R' = S(c p_L)`, where `S(m)` solves
`S = 1 - exp(-m S)` for the mean degree `c`. With negative regulators alone the map decreases, so above the
percolation point `p = 1 / c` its fixed point is unstable and `R` alternates between a large and a small giant
component, a cycle of period two. Below it the giant component vanishes.
Args:
n_nodes:
The number of nodes.
mean_degree:
The mean degree of the Erdős-Rényi graph.
p:
The probability that a link the regulation allows is active.
negative:
The probability that the regulator of a link is negative.
random_state:
The seed or generator of the graph, the regulators, and their signs.
Returns:
system:
The model, with the activities `nodes.active` and `links.active` and the signs `links.sign`.
Examples:
>>> from skcomplex.engine.runtime import Simulation
>>> from skcomplex.models.mathematics.networks import triadic_percolation
>>> system = triadic_percolation(200, p=0.8)
>>> trajectory = Simulation(system, random_state=0).check().prepare().run(until=3).trajectory_
>>> giant = trajectory.values('nodes.active').mean(axis=1)
>>> bool(giant[1] < 0.5 < giant[2])
True
"""
rng = np.random.default_rng(random_state)
graph = nx.gnp_random_graph(n_nodes, mean_degree / (n_nodes - 1), seed=rng)
hyperedges, signs = _regulated_links(graph, negative, rng)
space = NetworkSpace(graph, hyperedges=hyperedges)
nodes = LatticeElement('nodes', element=RegulatedNodeElement, space=space)
links = GroupElement('links', element=RegulatedLinkElement, space=space, on='edges', values={'sign': signs})
interactions = [
TriadicRegulationInteraction('regulation', nodes='nodes', links='links', p=p, phase='regulation'),
GiantComponentInteraction('percolation', nodes='nodes', links='links', phase='percolation'),
]
time = DiscreteTime(schedule=PhasesScheduleSpec((PhaseSpec('regulation'), PhaseSpec('percolation'))))
return System(elements=[nodes, links], interactions=interactions, time=time)
Components
class RegulatedNodeElement(Element):
"""Node of a network with triadic regulation, active when it belongs to the giant component of the active links.
Args:
label:
The label of the node group.
visual:
How the element is drawn, or `None` for `default_visual_`.
active:
1 when the node is in the giant component of the active links, and 0 otherwise.
Examples:
>>> from skcomplex.models.mathematics.networks import RegulatedNodeElement
>>> RegulatedNodeElement('nodes').active
1.0
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = ACTIVITY_VISUAL,
*,
active: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 1.0,
) -> None:
super().__init__(label, visual=visual)
self.active = active
class RegulatedLinkElement(Element):
"""Link of a network with triadic regulation, with its activity and the sign of its regulating node.
Args:
label:
The label of the link group.
visual:
How the element is drawn, or `None` for `default_visual_`.
active:
1 when the link is active, and 0 otherwise.
sign:
1 when the regulating node lets the link be active while the node is active, and -1 when it lets it be
active while the node is inactive.
Examples:
>>> from skcomplex.models.mathematics.networks import RegulatedLinkElement
>>> RegulatedLinkElement('links', sign=-1).sign
-1
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
active: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 1.0,
sign: Annotated[float, ParameterSpec(domain=FiniteEnumeratedSet([-1, 1]))] = 1,
) -> None:
super().__init__(label, visual=visual)
self.active = active
self.sign = sign
class TriadicRegulationInteraction(HyperedgeInteraction):
"""Regulation of every link by a third node, which lets the link be active with probability `p` in its phase.
Every hyperedge `(i, j, r)` holds the two ends of the link `(i, j)` and its regulating node `r`. In every step the
link becomes active with probability `p` when a positive regulator is active or a negative regulator is inactive,
and inactive otherwise.
Args:
label:
The label of the interaction, unique among the parts of its system.
nodes:
The path of the group on the nodes, whose activity the regulators read.
links:
The path of the group on the edges, whose activity the regulation sets.
p:
The probability that a link the regulation allows is active.
phase:
The phase of the step the regulation runs in, or `None` for the default phase.
Examples:
>>> from skcomplex.models.mathematics.networks import TriadicRegulationInteraction
>>> TriadicRegulationInteraction('regulation', nodes='nodes', links='links').check().size
3
"""
def __init__(
self,
label: str,
*,
nodes: Annotated[str, RoleSpec()],
links: Annotated[str, RoleSpec()],
p: Annotated[float, ParameterSpec(domain=RealSet.closed(0, 1))] = 0.5,
phase: str | None = None,
) -> None:
super().__init__(label, size=3)
self.nodes = nodes
self.links = links
self.p = p
self.phase = phase
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the activity of the link from its regulating node, the third member of the hyperedge."""
regulator = s.others[1].active
allowed = where(s.edge.sign > 0, regulator, 1 - regulator)
return [UpdateLaw(s.edge.active, bernoulli('retained', s.p) * allowed, phase=self.phase)]
class GiantComponentInteraction(Interaction):
"""Activity of every node in its phase, 1 in the giant component of the active links and 0 outside it.
Args:
label:
The label of the interaction, unique among the parts of its system.
nodes:
The path of the group on the nodes, whose activity the interaction sets.
links:
The path of the group on the edges, whose activity selects the links of the components.
phase:
The phase of the step the interaction runs in, or `None` for the default phase.
Examples:
>>> from skcomplex.models.mathematics.networks import GiantComponentInteraction
>>> GiantComponentInteraction('percolation', nodes='nodes', links='links').links
'links'
"""
def __init__(
self,
label: str,
*,
nodes: Annotated[str, RoleSpec()],
links: Annotated[str, RoleSpec()],
phase: str | None = None,
) -> None:
super().__init__(label)
self.nodes = nodes
self.links = links
self.phase = phase
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the membership of the giant component of the active links."""
return [UpdateLaw(s.nodes.active, components(s.links.active, reduce='giant'), phase=self.phase)]
Equations
nodes.active[i]: active_nodes(t + 1) == _neighbours_0
links.active[i]: active_links(t + 1) == where(bernoulli_retained_regulation < p_regulation, 1, 0)*where(sign_links__edge > 0, active_nodes__other1, -active_nodes__other1 + 1)