Prior Knowledge and Graphs#

One of the central ideas of the CORNETO framework revolves around using prior knowledge in the form of networks or graphs to build specialized network inference methods. By prior knowledge, we mean any information that is available about the problem at hand, such as protein-protein interaction networks, genome-scale metabolic networks, or even causal connections between random variables.

CORNETO provides a Graph class to construct prior kwnoledge graphs. This class is very flexible and can be used to build different types of graphs and hypergraphs, including undirected, directed, and mixed graphs, as well as graphs with multiple edge types and self-loops. It offers basic functionality to store vertices and edges with specific attributes, and basic graph operations.

In this tutorial, we will see how the Graph class is used in CORNETO to encode graph problems or prior knowledge in general.

Note

CORNETO is a library to design graph-based optimization problems, not a graph library.

The Graph class within CORNETO is designed as a base class for constructing general-purpose network methods on top of graphs. For users who require conventional, solver-free graph algorithms, such as Dijkstra for shortest path, the NetworkX library provides a comprehensive suite of tools for graph operations. Additionally, for ease of interoperability, the to_networkx method facilitates straightforward conversion from a CORNETO graph to a NetworkX graph. This dual approach ensures flexibility and depth for diverse network analysis needs.

import corneto as cn

cn.info()
Installed version:v1.0.0a0 (latest: v1.0.0-alpha)
Available backends:CVXPY v1.5.3, PICOS v2.4.17
Default backend (corneto.opt):CVXPY
Installed solvers:CLARABEL, CVXOPT, ECOS, ECOS_BB, GLPK, GLPK_MI, OSQP, SCIP, SCIPY, SCS
Graphviz version:v0.20.3
Repository:https://github.com/saezlab/corneto

Manually creating a graph#

G = cn.Graph()
G.add_edge(1, 2)
G.add_edge(2, 3)
G.add_edge(1, 3)
G.plot()
../../_images/b26ad1cae5f801af9f1f8987fd50baf9d02e13ca30bb919b1953c71386255646.svg

By default, edges are directed. Undirected edges can be mixed with directed edges. The method add_edge returns the index of the new edge. Here is an example:

idx = G.add_edge(3, 4, cn.EdgeType.UNDIRECTED)
print(idx)
G.plot()
3
../../_images/3d58d4f3685537ff716a68c12d87450878bc3b8b54014327f9c529564e4a8514.svg

Parallel edges are also supported. You can add multiple edges, both directed or undirected between vertices:

G.add_edge(3, 4, cn.EdgeType.UNDIRECTED)
G.add_edge(3, 4)
G.plot()
../../_images/87ff78c8b391c3b8515d387ba3e5c8c151cfad37ede8f233da6414bb51a95ed5.svg

Order of vertices and edges are preserved in the order of addition. Given an edge (u, v) added to the graph, u is added to the graph only if it’s not already present.

G.V
(1, 2, 3, 4)
G.E
((frozenset({1}), frozenset({2})),
 (frozenset({2}), frozenset({3})),
 (frozenset({1}), frozenset({3})),
 (frozenset({3}), frozenset({4})),
 (frozenset({3}), frozenset({4})),
 (frozenset({3}), frozenset({4})))

Edge attributes#

idx = G.add_edge(3, 4, weight=0.5, label="E(3->4)")
attr = G.get_attr_edge(idx)
attr
{'__edge_type': 'directed',
 'weight': 0.5,
 'label': 'E(3->4)',
 '__source_attr': {3: {'__value': {}}},
 '__target_attr': {4: {'__value': {}}}}
attr.weight
0.5
G.get_attr_edge(idx)
{'__edge_type': 'directed',
 'weight': 0.5,
 'label': 'E(3->4)',
 '__source_attr': {3: {'__value': {}}},
 '__target_attr': {4: {'__value': {}}}}
list(G.get_edges_by_attr("label", "E(3->4)"))
[6]
edge = G.get_edge(idx)
edge
(frozenset({3}), frozenset({4}))

Graphs have some special attributes, starting by __, for example __source_attr and __target_attr. These are automatically added to store attributes between the edge and the vertices. The recommended way to access the special attributes are through the get_attr method and the corneto.Attr attributes, for example:

attr = G.get_attr_edge(idx)
attr.get_attr(cn.Attr.SOURCE_ATTR)
{3: {'__value': {}}}
attr.__source_attr
{3: {'__value': {}}}

Note

The API for handling attributes on graphs is still under development and will change in future versions. It is used mostly internally to transform different representations of prior knowledge.

Importing graphs#

CORNETO implements also few adapters to import prior knowledge from other sources as corneto Graphs. For example, when working with signaling networks, one common format is the SIF files, which store vertices and edges in triples: Source Interaction Target, for example A -1 B to indicate that vertex (e.g. protein) A inhibits protein B. When importing a SIF file, CORNETO creates a graph with the attribute interaction which stores the type of interaction between vertices. The method from_sif_tuples takes a list of these triplets to generate the graph:

sif_graph = cn.Graph.from_sif_tuples([("A", 1, "B"), ("A", -1, "C")])
sif_graph.plot()
../../_images/891da5ece19614c75dc6a08c655cad606d63d71c65c5e6731d8b2d945b1a8a4c.svg
sif_graph.get_attr_edges()
[{'__edge_type': 'directed',
  'interaction': 1,
  '__source_attr': {'A': {'__value': {}}},
  '__target_attr': {'B': {'__value': {}}}},
 {'__edge_type': 'directed',
  'interaction': -1,
  '__source_attr': {'A': {'__value': {}}},
  '__target_attr': {'C': {'__value': {}}}}]
# cn.Graph.from_sif_file()

Saving and reading#

The method save allows you…

G.save("my_graph")
G_c = cn.Graph.load("my_graph.pkl.xz")
G_c.plot()
../../_images/830d8c157a3ed412b2e33bab6169b3c68529baac55c68065dbb982d81c7dfb00.svg

Hypergraphs#

Graphs in corneto support also hyper-edges connecting sets of vertices, something which is not supported by networkx. This is very useful to model more complex prior knowledge, such as metabolic networks, where edges are reactions connecting multiple vertices (reactants and products), e.g., A + B -> C + D, D -> E + F.

G = cn.Graph()
G.add_edge(("A", "B"), ("C", "D"))
G.add_edge("D", ("E", "F"))
G.add_edge((), "A")
G.add_edge((), "B")
G.add_edge("C", ())
G.plot()
../../_images/440e0b05ea329fdc52902e77e55c9ab6bbcc3aef57a9c07fbdf25949a30c0f6c.svg
G.V
('B', 'A', 'D', 'C', 'F', 'E')
G.E
((frozenset({'A', 'B'}), frozenset({'C', 'D'})),
 (frozenset({'D'}), frozenset({'E', 'F'})),
 (frozenset(), frozenset({'A'})),
 (frozenset(), frozenset({'B'})),
 (frozenset({'C'}), frozenset()))