Skip to content

Yeast cell-cycle network

The Boolean network of the budding yeast cell cycle of Li et al., eleven proteins and their regulation.

  • Name: yeast_cell_cycle
  • Field: biology/systems_biology
  • Formalism: discrete map
  • References: Li et al. 2004; Gros 2015

Factory

@register_model(
    field='biology/systems_biology',
    tags=('boolean network', 'gene regulation', 'discrete map', 'cell cycle'),
    references=('Li et al. 2004', 'Gros 2015'),
    defaults={'until': 15, 'dt': 1},
)
def yeast_cell_cycle(active: Sequence[str] = YEAST_START) -> System:
    """Return the Boolean network of the budding yeast cell cycle of Li et al., eleven proteins and their regulation.

    Every protein turns on or off with the sign of the summed activations and inhibitions by the active proteins,
    and the proteins Cln3, Cln1,2, Swi5, Cdc20, and Mcm1 decay without input. Started from the G1 state with Cln3 on,
    the cell size signal, the network runs through the phases of the cycle into the stationary G1 state.

    Args:
        active:
            The proteins that are on at the start, among `Cln3`, `MBF`, `SBF`, `Cln1_2`, `Cdh1`, `Swi5`, `Cdc20`,
            `Clb5_6`, `Sic1`, `Clb1_2`, and `Mcm1`.

    Returns:
        system:
            The model, with the values `<protein>.x`.

    Examples:
        >>> from skcomplex.models.biology.systems_biology import yeast_cell_cycle
        >>> [node.label for node in yeast_cell_cycle().elements if node.x]
        ['Cln3', 'Cdh1', 'Sic1']
    """
    nodes = [BooleanNodeElement(name, x=int(name in active)) for name in YEAST_NODES]
    functions = [
        ThresholdFunctionInteraction(
            f'regulation_{name}',
            target=name,
            inputs=[source for source, _ in YEAST_INPUTS[name]],
            weights=[float(weight) for _, weight in YEAST_INPUTS[name]],
            decays=name in YEAST_DECAYING,
        )
        for name in YEAST_NODES
    ]
    return System(elements=nodes, interactions=functions)

Components

class BooleanNodeElement(Element):
    """Node of a Boolean network, a gene or protein that is on or off.

    Args:
        label:
            The label of the node.

        visual:
            How the element is drawn, or `None` for `default_visual_`.

        x:
            The initial value, 0 or 1.

    Examples:
        >>> from skcomplex.models.biology.systems_biology import BooleanNodeElement
        >>> BooleanNodeElement('Sic1', x=1).x
        1
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        x: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.x = x

class ThresholdFunctionInteraction(MapInteraction):
    """Synchronous update of a node by the sign of the weighted sum of its active inputs, after Li et al.

    The node turns on when the sum `w_0 x_0 + w_1 x_1 + ...` over its inputs is positive and off when it is negative.
    When the sum is zero, a node that decays turns off and any other node keeps its value.

    Args:
        label:
            The label of the interaction.

        target:
            The path of the updated node.

        inputs:
            The paths of the input nodes.

        weights:
            The weight of every input, positive for activation and negative for inhibition.

        decays:
            Whether the node turns off without net input.

    Examples:
        >>> from skcomplex.models.biology.systems_biology import ThresholdFunctionInteraction
        >>> ThresholdFunctionInteraction('sic1', target='Sic1', inputs=['Swi5', 'Cln1_2'], weights=[1.0, -1.0]).weights
        [1.0, -1.0]
    """

    def __init__(
        self,
        label: str,
        *,
        target: Annotated[str, RoleSpec()],
        inputs: Annotated[list[str], RoleSpec(variadic=True)],
        weights: Annotated[Sequence[float], ParameterSpec(per='inputs')] = (),
        decays: bool = False,
    ) -> None:
        super().__init__(label, target=target, state='x', function=self._get_next)
        self.inputs = inputs
        self.weights = weights
        self.decays = decays

    def _get_next(self, s: SimpleNamespace) -> Expression:
        """Return 1 for a positive weighted input, 0 for a negative one, and the held or decayed value for none."""
        total = sum((weight * item.x for weight, item in zip(s.weights, s.inputs, strict=True)), start=SR(0))
        unchanged = 0 if self.decays else s.target.x
        return where(total > 0, 1, where(total < 0, 0, unchanged))

Equations

Cln3.x: x_Cln3(t + 1) == 0
MBF.x: x_MBF(t + 1) == where(weights_regulation_MBF_1*x_Clb1_2(t) + weights_regulation_MBF_0*x_Cln3(t) > 0, 1, where(weights_regulation_MBF_1*x_Clb1_2(t) + weights_regulation_MBF_0*x_Cln3(t) < 0, 0, x_MBF(t)))
SBF.x: x_SBF(t + 1) == where(weights_regulation_SBF_1*x_Clb1_2(t) + weights_regulation_SBF_0*x_Cln3(t) > 0, 1, where(weights_regulation_SBF_1*x_Clb1_2(t) + weights_regulation_SBF_0*x_Cln3(t) < 0, 0, x_SBF(t)))
Cln1_2.x: x_Cln1_2(t + 1) == where(weights_regulation_Cln1_2_0*x_SBF(t) > 0, 1, 0)
Cdh1.x: x_Cdh1(t + 1) == where(weights_regulation_Cdh1_3*x_Cdc20(t) + weights_regulation_Cdh1_2*x_Clb1_2(t) + weights_regulation_Cdh1_1*x_Clb5_6(t) + weights_regulation_Cdh1_0*x_Cln1_2(t) > 0, 1, where(weights_regulation_Cdh1_3*x_Cdc20(t) + weights_regulation_Cdh1_2*x_Clb1_2(t) + weights_regulation_Cdh1_1*x_Clb5_6(t) + weights_regulation_Cdh1_0*x_Cln1_2(t) < 0, 0, x_Cdh1(t)))
Swi5.x: x_Swi5(t + 1) == where(weights_regulation_Swi5_2*x_Cdc20(t) + weights_regulation_Swi5_0*x_Clb1_2(t) + weights_regulation_Swi5_1*x_Mcm1(t) > 0, 1, 0)
Cdc20.x: x_Cdc20(t + 1) == where(weights_regulation_Cdc20_0*x_Clb1_2(t) + weights_regulation_Cdc20_1*x_Mcm1(t) > 0, 1, 0)
Clb5_6.x: x_Clb5_6(t + 1) == where(weights_regulation_Clb5_6_1*x_Cdc20(t) + weights_regulation_Clb5_6_0*x_MBF(t) + weights_regulation_Clb5_6_2*x_Sic1(t) > 0, 1, where(weights_regulation_Clb5_6_1*x_Cdc20(t) + weights_regulation_Clb5_6_0*x_MBF(t) + weights_regulation_Clb5_6_2*x_Sic1(t) < 0, 0, x_Clb5_6(t)))
Sic1.x: x_Sic1(t + 1) == where(weights_regulation_Sic1_3*x_Cdc20(t) + weights_regulation_Sic1_2*x_Clb1_2(t) + weights_regulation_Sic1_1*x_Clb5_6(t) + weights_regulation_Sic1_0*x_Cln1_2(t) + weights_regulation_Sic1_4*x_Swi5(t) > 0, 1, where(weights_regulation_Sic1_3*x_Cdc20(t) + weights_regulation_Sic1_2*x_Clb1_2(t) + weights_regulation_Sic1_1*x_Clb5_6(t) + weights_regulation_Sic1_0*x_Cln1_2(t) + weights_regulation_Sic1_4*x_Swi5(t) < 0, 0, x_Sic1(t)))
Clb1_2.x: x_Clb1_2(t + 1) == where(weights_regulation_Clb1_2_2*x_Cdc20(t) + weights_regulation_Clb1_2_3*x_Cdh1(t) + weights_regulation_Clb1_2_0*x_Clb5_6(t) + weights_regulation_Clb1_2_1*x_Mcm1(t) + weights_regulation_Clb1_2_4*x_Sic1(t) > 0, 1, where(weights_regulation_Clb1_2_2*x_Cdc20(t) + weights_regulation_Clb1_2_3*x_Cdh1(t) + weights_regulation_Clb1_2_0*x_Clb5_6(t) + weights_regulation_Clb1_2_1*x_Mcm1(t) + weights_regulation_Clb1_2_4*x_Sic1(t) < 0, 0, x_Clb1_2(t)))
Mcm1.x: x_Mcm1(t + 1) == where(weights_regulation_Mcm1_1*x_Clb1_2(t) + weights_regulation_Mcm1_0*x_Clb5_6(t) > 0, 1, 0)