Acyclic boolean models of cell signaling (experimental)#

This guide shows the basics of modeling intracellular signaling using acyclic boolean models for multi-perturbation experiments. For this, we will use an extension of the CellNopt ILP method implemented with CORNETO.

  • Author: Attila Gabor (Heidelberg University, SaezLab)

  • Reviewers: Pablo Rodriguez Mier (Heidelberg University, SaezLab)

NOTE: This notebook is still experimental.

import corneto as cn
import corneto.methods.signal.cellnopt_ilp as cno

cn.info()
Installed version:v1.0.0.dev39+1266a7d
Available backends:CVXPY v1.6.6, PICOS v2.6.1
Default backend (corneto.opt):CVXPY
Installed solvers:CVXOPT, GLPK, GLPK_MI, HIGHS, SCIP, SCIPY
Graphviz version:v0.20.3
Installed path:/home/runner/work/corneto/corneto/corneto
Repository:https://github.com/saezlab/corneto

Introduction to a Boolean model#

As a first step we define start with a small network and corresponding datasets just to illustrate the concepts. The prior knowledge network of ‘G1’ contains three nodes, EGF, TNFa and Ras. Edges show the potential activation of the RAS protein.

# Definition of the prior knowledge network
G1 = cn.Graph.from_sif_tuples(
    [
        ("EGF", 1, "AND1"),
        ("TNFa", 1, "AND1"),
        ("AND1", 1, "Ras"),
        ("EGF", 1, "Ras"),
        ("TNFa", 1, "Ras"),
    ]
)
G1.plot()
../../_images/4e46465ca2e4f77c764f290c442ebba66ba6d1e724855d58478655f7aa98c482.svg

Now, we organize some in-silico experimental data into a format that can be used by the CNO toolbox.

First, we define 4 conditions (exp0 … exp3). In each condition, the activated nodes (EGF or TNFa) are set to 1 and the non-activated nodes to 0. We also define the values of the corresponding outputs (Ras).

Note that the output, Ras is only active (1.0) in the 4th condition, when both of the inputs are also activated. This corresponds to an AND relationship: RAS is active only if both EGF and TNFa are activated. We expect that the optimization finds the correct subgraph of the above network, which contains only the 3 nodes with the AND gates, while individual edges from EGF and TNFa to RAS are removed.

# RAS is only active iff both EGF and TNFa are active -> we need to identify the AND gate
exp_list_G1_and = {
    "exp0": {"input": {"EGF": 0, "TNFa": 0}, "output": {"Ras": 0}},
    "exp1": {"input": {"EGF": 1, "TNFa": 0}, "output": {"Ras": 0}},
    "exp2": {"input": {"EGF": 0, "TNFa": 1}, "output": {"Ras": 0}},
    "exp3": {"input": {"EGF": 1, "TNFa": 1}, "output": {"Ras": 1}},
}
G1_prep = cno.expand_graph_for_flows(G1, exp_list_G1_and)
G1_prep.plot()
../../_images/1546144f370aebc9f777064deaeaabf575b34856bab153da3f03e6eb6a5ac4f2.svg
cno.plot_data(exp_list_G1_and)
../../_images/6217679679c5155fb5e23a625174e761621cac306832ba215cb9fa7675e6100c.png
P = cno.cellnoptILP(G1_prep, exp_list_G1_and, verbose=False)
cno.plot_fitness(G1, exp_list_G1_and, P, measured_only=True)
../../_images/7067980e3bd9ff45e800975e7ebf0c21dca07c889bbf8a9db5ed528527cae5f6.png
G1_prep.plot(custom_edge_attr=cno.cno_style(P, flow_name="edge_activates", scale=None, iexp=3))
../../_images/948bdf1e359117d170b6215721d64202cad886a6905cddc80e462572b065f6e5.svg

Tasks:

  1. modify the experiments so that it corresponds to a scenario where only EGF could activate RAS, but not TNFa.

  2. modify the experiments so that it corresponds to a scenario where both TNFa or EGF could activate RAS.

Inhibition: !EGF activates RAS#

Let’s define the same network but now we will add an inhibition of EGF on RAS. This means that if EGF is active, RAS should be inactive. Note the difference in the definition of the prior knowledge network.

We also change the experiments so that the output RAS is active only when TNFa is active.

# Test graph
G2 = cn.Graph.from_sif_tuples(
    [
        ("EGF", -1, "AND1"),
        ("TNFa", 1, "AND1"),
        ("AND1", 1, "Ras"),
        ("EGF", -1, "Ras"),
        ("TNFa", 1, "Ras"),
    ]
)

# RAS is only active iff both EGF and TNFa are active -> we need to idetify the AND gate
exp_list_G2_egf = {
    "exp0": {"input": {"EGF": 0, "TNFa": 0}, "output": {"Ras": 1}},
    "exp1": {"input": {"EGF": 1, "TNFa": 0}, "output": {"Ras": 0}},
    "exp2": {"input": {"EGF": 0, "TNFa": 1}, "output": {"Ras": 1}},
    "exp3": {"input": {"EGF": 1, "TNFa": 1}, "output": {"Ras": 0}},
}
G2_prep = cno.expand_graph_for_flows(G2, exp_list_G2_egf)
G2_prep.plot()
../../_images/275c5aec54365edce6fdd3ec0c91c4f4562eafd75b459f01047e6f7353a552a2.svg
P2 = cno.cellnoptILP(G2_prep, exp_list_G2_egf, verbose=False)
cno.plot_fitness(G2_prep, exp_list_G2_egf, P2, measured_only=True)
../../_images/96e4e309042cdebbef4b15312bc792f597cc5abbd632fdcb1ccbbd5c53dd7b46.png
G2_prep.plot(custom_edge_attr=cno.cno_style(P2, flow_name="edge_activates", scale=None, iexp=0))
../../_images/12e64f1ddab6f70e27976dd8809e0f7fa4ad998ad54c4efbb5b606059df00a20.svg

Inhibition with AND gates#

Please remember, inhibitions are added to the incoming edge of the AND node. The outgoing edges of the AND node are always activating by convention.

# Test graph
G2 = cn.Graph.from_sif_tuples(
    [
        ("EGF", -1, "AND1"),
        ("TNFa", 1, "AND1"),
        ("AND1", 1, "Ras"),
        ("EGF", -1, "Ras"),
        ("TNFa", 1, "Ras"),
    ]
)

# RAS is only active iff both EGF and TNFa are active -> we need to idetify the AND gate
exp_list_G2_and = {
    "exp0": {"input": {"EGF": 0, "TNFa": 0}, "output": {"Ras": 0}},
    "exp1": {"input": {"EGF": 1, "TNFa": 0}, "output": {"Ras": 0}},
    "exp2": {"input": {"EGF": 0, "TNFa": 1}, "output": {"Ras": 1}},
    "exp3": {"input": {"EGF": 1, "TNFa": 1}, "output": {"Ras": 0}},
}
G2_prep = cno.expand_graph_for_flows(G2, exp_list_G2_and)
G2_prep.plot()
../../_images/275c5aec54365edce6fdd3ec0c91c4f4562eafd75b459f01047e6f7353a552a2.svg
P2 = cno.cellnoptILP(G2_prep, exp_list_G2_and, verbose=False)
G2_prep.plot(custom_edge_attr=cno.cno_style(P2, flow_name="edge_activates", scale=None, iexp=2))
../../_images/3a01e5226f5f0b377f55156244a7a3b57e3cc46a8525f884a36fc97f9e993e4a.svg
cno.plot_fitness(G2_prep, exp_list_G2_and, P2, measured_only=True)
../../_images/14720ce9164dc8e4392fdce72cf6a92c24dc4d4c44bb490313383ba8de080e99.png

Complex case study with inhibition#

Let’s look at a more realistic case study which contains a larger network and more experiments.

Also note that the we define inhibition in the experimental description. This could be, for example, the effect of small molecule kinase inhibitor.

G3 = cn.Graph.from_sif_tuples(
    [
        ("EGF", 1, "Ras"),
        ("EGF", 1, "PI3K"),
        ("TNFa", 1, "PI3K"),
        ("TNFa", 1, "TRAF6"),
        ("TRAF6", 1, "p38"),
        ("TRAF6", 1, "Jnk"),
        ("TRAF6", 1, "NFkB"),
        ("Jnk", 1, "cJun"),
        ("p38", 1, "Hsp27"),
        ("PI3K", 1, "Akt"),
        ("Ras", 1, "Raf"),
        ("Raf", 1, "Mek"),
        ("Akt", -1, "Mek"),
        ("Mek", 1, "p90RSK"),
        ("Mek", 1, "Erk"),
        ("Erk", 1, "Hsp27"),
    ]
)
# G3.add_edge((), 'EGF')
# G3.add_edge((), 'TNFa')
# G3.add_edge('Akt', ())
# G3.add_edge('cJun', ())
# G3.add_edge('Hsp27', ())
# G3.add_edge('NFkB', ())
# G3.add_edge('Erk', ())
# G3.add_edge('p90RSK', ())
# G3.add_edge('Jnk', ())

exp_list_toy_full = {
    "exp0": {
        "input": {"EGF": 0, "TNFa": 0},
        "inhibition": {},
        "output": {
            "Akt": 0,
            "Hsp27": 0,
            "NFkB": 0,
            "Erk": 0,
            "p90RSK": 0,
            "Jnk": 0,
            "cJun": 0,
        },
    },
    "exp1": {
        "input": {"EGF": 1, "TNFa": 0},
        "inhibition": {},
        "output": {
            "Akt": 0.91,
            "Hsp27": 0,
            "NFkB": 0.86,
            "Erk": 0.8,
            "p90RSK": 0.88,
            "Jnk": 0,
            "cJun": 0,
        },
    },
    "exp2": {
        "input": {"EGF": 0, "TNFa": 1},
        "inhibition": {},
        "output": {
            "Akt": 0.82,
            "Hsp27": 0.7,
            "NFkB": 0.90,
            "Erk": 0.0,
            "p90RSK": 0.0,
            "Jnk": 0.25,
            "cJun": 0.4,
        },
    },
    "exp3": {
        "input": {"EGF": 1, "TNFa": 1},
        "inhibition": {},
        "output": {
            "Akt": 0.91,
            "Hsp27": 0.7,
            "NFkB": 0.90,
            "Erk": 0.8,
            "p90RSK": 0.88,
            "Jnk": 0.25,
            "cJun": 0.4,
        },
    },
    "exp4": {
        "input": {"EGF": 1, "TNFa": 0},
        "inhibition": {"Raf": 1},
        "output": {
            "Akt": 0.91,
            "Hsp27": 0,
            "NFkB": 0.86,
            "Erk": 0.0,
            "p90RSK": 0.0,
            "Jnk": 0,
            "cJun": 0,
        },
    },
    "exp5": {
        "input": {"EGF": 0, "TNFa": 1},
        "inhibition": {"Raf": 1},
        "output": {
            "Akt": 0.82,
            "Hsp27": 0.7,
            "NFkB": 0.90,
            "Erk": 0.0,
            "p90RSK": 0.0,
            "Jnk": 0.25,
            "cJun": 0.4,
        },
    },
    "exp6": {
        "input": {"EGF": 1, "TNFa": 1},
        "inhibition": {"Raf": 1},
        "output": {
            "Akt": 0.91,
            "Hsp27": 0.7,
            "NFkB": 0.90,
            "Erk": 0.0,
            "p90RSK": 0.0,
            "Jnk": 0.25,
            "cJun": 0.4,
        },
    },
    "exp7": {
        "input": {"EGF": 1, "TNFa": 0},
        "inhibition": {"PI3K": 1},
        "output": {
            "Akt": 0.0,
            "Hsp27": 0,
            "NFkB": 0.0,
            "Erk": 0.8,
            "p90RSK": 0.88,
            "Jnk": 0,
            "cJun": 0,
        },
    },
    "exp8": {
        "input": {"EGF": 0, "TNFa": 1},
        "inhibition": {"PI3K": 1},
        "output": {
            "Akt": 0.0,
            "Hsp27": 0.7,
            "NFkB": 0.90,
            "Erk": 0.0,
            "p90RSK": 0.0,
            "Jnk": 0.25,
            "cJun": 0.4,
        },
    },
    "exp9": {
        "input": {"EGF": 1, "TNFa": 1},
        "inhibition": {"PI3K": 1},
        "output": {
            "Akt": 0.0,
            "Hsp27": 0.7,
            "NFkB": 0.90,
            "Erk": 0.8,
            "p90RSK": 0.88,
            "Jnk": 0.25,
            "cJun": 0.4,
        },
    },
}


G3.plot()
../../_images/a7b2152f0be063ecdd4d073baa74125df189602839b0ffcfd773741e4eb18dcf.svg
cno.plot_data(exp_list_toy_full)
../../_images/0f466a1a96d874db6b21737ff0f97a42224766b5e8bf68fe5953ca59ef59f438.png
G3_prep = cno.expand_graph_for_flows(G3, exp_list_toy_full)
G3_prep.plot()
../../_images/d92d3774835c177def92ed41da67f1489e2af508c4bf24310cbd5a13340a950a.svg
P_G3 = cno.cellnoptILP(G3_prep, exp_list_toy_full, verbose=False)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[21], line 1
----> 1 P_G3 = cno.cellnoptILP(G3_prep, exp_list_toy_full, verbose=False)

File ~/work/corneto/corneto/corneto/methods/signal/cellnopt_ilp.py:265, in cellnoptILP(G, exp_list, solver, alpha_flow, verbose, backend)
    261     P += (
    262         V[is_regular_node, iexp] <= (At @ Eact)[is_regular_node, iexp]
    263     )  # but it has an upper constraint, so it takes 0 when all input are 0
    264     if V_is_inhibited[:, iexp].any():
--> 265         P += V[inh_idx[:, iexp], iexp] == np.zeros(sum(V_is_inhibited[:, iexp]))
    267 # AND relation expressed as follows:
    268 # - we only define these constraints for the AND gates
    269 # - we count the sum of flows (selected edges) and sum of activated edges
    270 # - if sum of flow equal to the sum of incoming edge activation, i.e. all edge are activated, then the vertex is activated:
    271 # - to ensure that the AND gate is not active when there is no flow (an no active edge), we add the second constraint:
    272 if V_is_and.any():
    273     # and_idx = np.flatnonzero(V_is_and)

IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed
G3_prep.plot(custom_edge_attr=cno.cno_style(P_G3, flow_name="edge_activates", scale=None, iexp=3))
../../_images/f5a7ba5206e22f4830ac6c5d977b66766d5924e11f6ef1c580dfe864f1eead8e.svg
cno.plot_fitness(G3_prep, exp_list_toy_full, P_G3)
../../_images/652eae71063c5f07b3c4441298bc298b5c7ebdd82f73c8a0997e8c28ad64e440.png