Skip to content

Boolean model of the mammalian cell cycle

The Boolean model of the mammalian cell cycle of Faure et al., ten proteins updated synchronously.

  • Name: mammalian_cell_cycle
  • Field: biology/systems_biology
  • Formalism: discrete map
  • References: Raman 2021; Faure et al. 2006

Factory

@register_model(
    field='biology/systems_biology',
    tags=('boolean network', 'cell cycle', 'discrete map', 'attractors', 'knock-out'),
    references=(RAMAN, 'Faure et al. 2006'),
    defaults={'until': 21, 'dt': 1},
)
def mammalian_cell_cycle(
    active: Sequence[str] = CELL_CYCLE_START,
    *,
    knockouts: Sequence[str] = (),
) -> System:
    """Return the Boolean model of the mammalian cell cycle of Faure et al., ten proteins updated synchronously.

    The rules are those printed by Raman for CycD, Rb, E2F, CycE, CycA, p27, Cdc20, Cdh1, UbcH10 (`Ubc`), and CycB.
    The growth signal CycD keeps its value. Without it the network rests in the quiescent state with Rb, p27, and Cdh1
    on. With it the network leaves that state and runs through a cycle of period 7 in which the cyclins E, A, and B
    rise and fall in turn. A knocked-out protein stays off.

    Args:
        active:
            The proteins that are on at the start.

        knockouts:
            The proteins that are knocked out.

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

    Raises:
        ValidationError:
            A name is not one of the ten proteins.

    Examples:
        >>> from skcomplex.models.biology.systems_biology import mammalian_cell_cycle
        >>> [node.label for node in mammalian_cell_cycle().elements if node.x]
        ['CycD', 'Rb', 'p27', 'Cdh1']
    """
    return _rule_network(CELL_CYCLE_RULES, active, knockouts)

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 BooleanFunctionInteraction(MapInteraction):
    """Synchronous update of a node by a truth table of any number of input nodes.

    A table of `2^k` entries reads the `k` inputs as the bits of a binary number, from the most significant, and gives
    the next value of the target. The update is the sum, over the patterns whose entry is 1, of the product that is 1
    exactly on that pattern.

    Args:
        label:
            The label of the interaction.

        target:
            The path of the updated node.

        inputs:
            The paths of the input nodes, in the order of the bits of the table.

        table:
            The next value of the target for each of the `2^k` input patterns.

    Raises:
        ValidationError:
            The table does not have one entry per pattern of the inputs.

    Examples:
        >>> from skcomplex.models.biology.systems_biology import BooleanFunctionInteraction
        >>> BooleanFunctionInteraction('and', target='c', inputs=['a', 'b'], table=(0, 0, 0, 1)).table
        (0, 0, 0, 1)
    """

    def __init__(
        self,
        label: str,
        *,
        target: Annotated[str, RoleSpec()],
        inputs: Annotated[list[str], RoleSpec(variadic=True)],
        table: tuple[int, ...] = (0,),
    ) -> None:
        super().__init__(label, target=target, state='x', function=self._get_next)
        self.inputs = inputs
        self.table = table

    def _get_pattern(self, inputs: Sequence[Expression], index: int) -> Expression:
        """Return the product that is 1 when the inputs, read as bits from the most significant, spell an index."""
        bits = [(index >> (len(inputs) - 1 - position)) & 1 for position in range(len(inputs))]
        return prod((value if bit else 1 - value for value, bit in zip(inputs, bits, strict=True)), start=SR(1))

    def _get_next(self, s: SimpleNamespace) -> Expression:
        """Return the next value of the target by the truth table."""
        inputs = [item.x for item in s.inputs]
        terms = [self._get_pattern(inputs, index) for index, entry in enumerate(self.table) if entry]
        return sum(terms, start=SR(0))

    def _check(self) -> None:
        """Raise an error for a table without one entry per pattern of the inputs.

        Raises:
            ValidationError:
                The table does not have one entry per pattern of the inputs.
        """
        super()._check()
        if len(self.table) != 2 ** len(self.inputs):
            error_msg = (
                f'BooleanFunction {self.label} reads {len(self.inputs)} inputs, which take {2 ** len(self.inputs)} '
                f'patterns, but its table has {len(self.table)} entries.'
            )
            raise ValidationError(error_msg)

Equations

CycD.x: x_CycD(t + 1) == x_CycD(t)
Rb.x: x_Rb(t + 1) == -(x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_CycD(t) - 1)*(x_CycE(t) - 1)*(x_p27(t) - 1) + (x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_CycD(t) - 1)*(x_CycE(t) - 1)*x_p27(t) - (x_CycB(t) - 1)*(x_CycD(t) - 1)*(x_CycE(t) - 1)*x_CycA(t)*x_p27(t) - (x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_CycD(t) - 1)*x_CycE(t)*x_p27(t) + (x_CycB(t) - 1)*(x_CycD(t) - 1)*x_CycA(t)*x_CycE(t)*x_p27(t)
E2F.x: x_E2F(t + 1) == (x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_Rb(t) - 1)*(x_p27(t) - 1) - (x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_Rb(t) - 1)*x_p27(t) + (x_CycB(t) - 1)*(x_Rb(t) - 1)*x_CycA(t)*x_p27(t)
CycE.x: x_CycE(t + 1) == -(x_Rb(t) - 1)*x_E2F(t)
CycA.x: x_CycA(t + 1) == -(x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_E2F(t) - 1)*(x_Rb(t) - 1)*(x_Ubc(t) - 1)*x_CycA(t) + (x_Cdc20(t) - 1)*(x_E2F(t) - 1)*(x_Rb(t) - 1)*(x_Ubc(t) - 1)*x_Cdh1(t)*x_CycA(t) - (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_CycA(t) - 1)*(x_Rb(t) - 1)*(x_Ubc(t) - 1)*x_E2F(t) + (x_Cdc20(t) - 1)*(x_CycA(t) - 1)*(x_Rb(t) - 1)*(x_Ubc(t) - 1)*x_Cdh1(t)*x_E2F(t) + (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_Rb(t) - 1)*(x_Ubc(t) - 1)*x_CycA(t)*x_E2F(t) - (x_Cdc20(t) - 1)*(x_Rb(t) - 1)*(x_Ubc(t) - 1)*x_Cdh1(t)*x_CycA(t)*x_E2F(t) + (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_E2F(t) - 1)*(x_Rb(t) - 1)*x_CycA(t)*x_Ubc(t) + (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_CycA(t) - 1)*(x_Rb(t) - 1)*x_E2F(t)*x_Ubc(t) - (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_Rb(t) - 1)*x_CycA(t)*x_E2F(t)*x_Ubc(t)
p27.x: x_p27(t + 1) == -(x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_CycD(t) - 1)*(x_CycE(t) - 1)*(x_p27(t) - 1) + (x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_CycD(t) - 1)*(x_CycE(t) - 1)*x_p27(t) - (x_CycB(t) - 1)*(x_CycD(t) - 1)*(x_CycE(t) - 1)*x_CycA(t)*x_p27(t) - (x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_CycD(t) - 1)*x_CycE(t)*x_p27(t)
Cdc20.x: x_Cdc20(t + 1) == x_CycB(t)
Cdh1.x: x_Cdh1(t + 1) == (x_Cdc20(t) - 1)*(x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_p27(t) - 1) - (x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_p27(t) - 1)*x_Cdc20(t) + (x_CycB(t) - 1)*(x_p27(t) - 1)*x_Cdc20(t)*x_CycA(t) + (x_CycA(t) - 1)*(x_p27(t) - 1)*x_Cdc20(t)*x_CycB(t) - (x_p27(t) - 1)*x_Cdc20(t)*x_CycA(t)*x_CycB(t) - (x_Cdc20(t) - 1)*(x_CycA(t) - 1)*(x_CycB(t) - 1)*x_p27(t) + (x_CycA(t) - 1)*(x_CycB(t) - 1)*x_Cdc20(t)*x_p27(t) + (x_Cdc20(t) - 1)*(x_CycB(t) - 1)*x_CycA(t)*x_p27(t) - (x_CycB(t) - 1)*x_Cdc20(t)*x_CycA(t)*x_p27(t) - (x_CycA(t) - 1)*x_Cdc20(t)*x_CycB(t)*x_p27(t) + x_Cdc20(t)*x_CycA(t)*x_CycB(t)*x_p27(t)
Ubc.x: x_Ubc(t + 1) == -(x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_Ubc(t) - 1) + (x_Cdh1(t) - 1)*(x_CycA(t) - 1)*(x_CycB(t) - 1)*(x_Ubc(t) - 1)*x_Cdc20(t) + (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_CycB(t) - 1)*(x_Ubc(t) - 1)*x_CycA(t) - (x_Cdh1(t) - 1)*(x_CycB(t) - 1)*(x_Ubc(t) - 1)*x_Cdc20(t)*x_CycA(t) + (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_CycA(t) - 1)*(x_Ubc(t) - 1)*x_CycB(t) - (x_Cdh1(t) - 1)*(x_CycA(t) - 1)*(x_Ubc(t) - 1)*x_Cdc20(t)*x_CycB(t) - (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_Ubc(t) - 1)*x_CycA(t)*x_CycB(t) + (x_Cdh1(t) - 1)*(x_Ubc(t) - 1)*x_Cdc20(t)*x_CycA(t)*x_CycB(t) + (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_CycA(t) - 1)*(x_CycB(t) - 1)*x_Ubc(t) - (x_Cdh1(t) - 1)*(x_CycA(t) - 1)*(x_CycB(t) - 1)*x_Cdc20(t)*x_Ubc(t) + (x_CycA(t) - 1)*(x_CycB(t) - 1)*x_Cdc20(t)*x_Cdh1(t)*x_Ubc(t) - (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_CycB(t) - 1)*x_CycA(t)*x_Ubc(t) + (x_Cdh1(t) - 1)*(x_CycB(t) - 1)*x_Cdc20(t)*x_CycA(t)*x_Ubc(t) + (x_Cdc20(t) - 1)*(x_CycB(t) - 1)*x_Cdh1(t)*x_CycA(t)*x_Ubc(t) - (x_CycB(t) - 1)*x_Cdc20(t)*x_Cdh1(t)*x_CycA(t)*x_Ubc(t) - (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*(x_CycA(t) - 1)*x_CycB(t)*x_Ubc(t) + (x_Cdh1(t) - 1)*(x_CycA(t) - 1)*x_Cdc20(t)*x_CycB(t)*x_Ubc(t) + (x_Cdc20(t) - 1)*(x_CycA(t) - 1)*x_Cdh1(t)*x_CycB(t)*x_Ubc(t) - (x_CycA(t) - 1)*x_Cdc20(t)*x_Cdh1(t)*x_CycB(t)*x_Ubc(t) + (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)*x_CycA(t)*x_CycB(t)*x_Ubc(t) - (x_Cdh1(t) - 1)*x_Cdc20(t)*x_CycA(t)*x_CycB(t)*x_Ubc(t) - (x_Cdc20(t) - 1)*x_Cdh1(t)*x_CycA(t)*x_CycB(t)*x_Ubc(t) + x_Cdc20(t)*x_Cdh1(t)*x_CycA(t)*x_CycB(t)*x_Ubc(t)
CycB.x: x_CycB(t + 1) == (x_Cdc20(t) - 1)*(x_Cdh1(t) - 1)