Implementing an alternative network sampler#

In this tutorial, we demonstrate how to use CORNETO to implement a simple sampler to explore alternative solutions for network inference problems. By perturbing the objective function, we can explore the solution space and identify robust as well as variable components of the network.

We will illustrate this process using a simple example of a network inference problem with CARNIVAL, using the data from the CARNIVAL transcriptomics tutorial.

NOTE: This notebook uses gurobi as the solver and cvxpy as the backend to accelerate the generation of alternative solutions using the warm_start option from cvxpy

import corneto as cn

cn.info()
Installed version:v1.0.0rc1
Available backends:CVXPY v1.7.1
Default backend (corneto.opt):CVXPY
Installed solvers:CLARABEL, GUROBI, HIGHS, OSQP, SCIPY, SCS
Plot backend (default):auto -> graphviz
Available plot backends:graphviz v0.21; graphviz-wasm
Installed path:/docs/tutorials/carnival/.pixi/envs/default/lib/python3.11/site-packages/corneto
Repository:https://github.com/saezlab/corneto
from corneto.data import GraphData

# Load dataset from tutorial
dataset = GraphData.load("data/carnival_transcriptomics_dataset.zip")
from corneto.methods import CarnivalFlow

m = CarnivalFlow(lambda_reg=0.1)
P = m.build(dataset.graph, dataset.data)
P.objectives
Unreachable vertices for sample: 0
[error_sample1_0: Expression(AFFINE, UNKNOWN, ()),
 regularization_edge_has_signal: Expression(AFFINE, NONNEGATIVE, ())]
# We will sample solutions by perturbing the edge_has_signal variable
P.expr
{'_flow': _flow: Variable((3251,), _flow),
 'edge_inhibits': edge_inhibits: Variable((3251, 1), edge_inhibits, boolean=True),
 'edge_activates': edge_activates: Variable((3251, 1), edge_activates, boolean=True),
 'const0x103326ca2e6db1fe': const0x103326ca2e6db1fe: Constant(CONSTANT, NONNEGATIVE, (937, 3251)),
 '_dag_layer': _dag_layer: Variable((937, 1), _dag_layer),
 'const0x18ac965210dd36c7': const0x18ac965210dd36c7: Constant(CONSTANT, NONNEGATIVE, (937, 3251)),
 'flow': _flow: Variable((3251,), _flow),
 'vertex_value': Expression(AFFINE, UNKNOWN, (937, 1)),
 'vertex_activated': Expression(AFFINE, NONNEGATIVE, (937, 1)),
 'vertex_inhibited': Expression(AFFINE, NONNEGATIVE, (937, 1)),
 'edge_value': Expression(AFFINE, UNKNOWN, (3251, 1)),
 'edge_has_signal': Expression(AFFINE, NONNEGATIVE, (3251, 1)),
 'vertex_max_depth': _dag_layer: Variable((937, 1), _dag_layer)}

Sampling Solutions Using Random Perturbation#

We’ll implement a generic approach to sample alternative solutions by:

  1. Introducing small random perturbations to the objective function

  2. Re-optimizing the model with these perturbations

  3. Collecting solutions that remain close to optimal (within a tolerance threshold)

This technique allows us to explore the space of near-optimal solutions and assess the robustness of different model components.

import numpy as np
import pandas as pd


def sample_alternative_solutions(
    problem,
    target_variable,
    percentage=0.1,
    scale=0.03,
    rel_opt_tol=0.05,
    max_samples=30,
    time_limit=60,
):
    """Sample alternative solutions by perturbing the objective function.

    Parameters:
    -----------
    problem : CORNETO optimization problem
        The optimization problem to sample
    target_variable : variable expression
        The variable to perturb (e.g., P.expr.edge_has_signal or P.expr.vertex_value)
    percentage : float
        Percentage of elements to perturb
    scale : float
        Scale parameter for normal distribution perturbation
    rel_opt_tol : float
        Relative optimality tolerance
    max_samples : int
        Maximum number of samples to generate
    time_limit : int
        Time limit for solver

    Returns:
    --------
    dict : A dictionary containing the results
    """
    # Save the original problem state
    original_problem = problem.solve(solver="gurobi", verbosity=1)

    # Store original objective values
    orig_objectives = []
    for o in problem.objectives:
        print(o.name, o.value)
        orig_objectives.append(o.value)

    # Prepare for perturbation
    shape = target_variable.shape
    num_elements = shape[0]
    n_elements = int(num_elements * percentage)
    vec = np.zeros(shape=num_elements)
    pert = problem.backend.Parameter("perturbation", shape=vec.shape, value=vec)

    # Add perturbation objective
    problem.add_objective(target_variable.T @ pert, name="perturbation")

    # Sample solutions
    selected_edges = []
    edge_values = []
    vertex_values = []

    for i in range(max_samples):
        # Generate random perturbation
        c = np.random.normal(scale=scale, size=n_elements)
        random_indices = np.random.choice(vec.shape[0], size=n_elements, replace=False)
        vec[:] = 0  # Reset vector
        vec[random_indices] = c

        # Update parameter and solve
        pert.value = vec
        solved_problem = problem.solve(
            solver="gurobi",
            warm_start=True,
            ignore_dpp=True,
            TimeLimit=time_limit,
            verbosity=0,
        )
        print(f"Sample {i + 1}/{max_samples}: Objective value = {solved_problem.value:.4f}")

        # Check if solution is within tolerance
        accept = True
        for o_orig, o in zip(orig_objectives, problem.objectives):
            if o.name == "perturbation":
                continue
            relative_error = np.abs(o.value - o_orig) / np.abs(o_orig + 1e-10)
            print(f"  {o.name}: {o.value:.4f}, rel error: {relative_error:.4f}")
            if relative_error > rel_opt_tol:
                print(f"  > Rejected! ({relative_error:.4f} >= {rel_opt_tol})")
                accept = False
                break

        if accept:
            selected_edges.append(problem.expr.edge_has_signal.value > 0.5)
            edge_values.append(problem.expr.edge_value.value)
            vertex_values.append(problem.expr.vertex_value.value)

    return {
        "selected_edges": selected_edges,
        "edge_values": edge_values,
        "vertex_values": vertex_values,
    }

Edge-Based Perturbation#

First, we’ll sample solutions by perturbing the edge variables. This allows us to explore alternative network topologies where different edges might be selected.

# Create a fresh model
m = CarnivalFlow(lambda_reg=0.1)
P = m.build(dataset.graph, dataset.data)

# Sample solutions by perturbing edge_has_signal
edge_results = sample_alternative_solutions(
    problem=P,
    target_variable=P.expr.edge_has_signal,
    percentage=0.10,
    scale=0.03,
    rel_opt_tol=0.05,
    max_samples=30,
)

# Analyze edge-based results
df_sols = pd.DataFrame(np.concatenate(edge_results["selected_edges"], axis=1), index=m.processed_graph.E).astype(int)
print(f"Generated {df_sols.shape[1]} alternative solutions")
df_sols.head()
Unreachable vertices for sample: 0
===============================================================================
                                     CVXPY                                     
                                     v1.7.1                                    
===============================================================================
-------------------------------------------------------------------------------
                                  Compilation                                  
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
                                Numerical solver                               
-------------------------------------------------------------------------------
Set parameter Username
Set parameter LicenseID to value 2775543
Academic license - for non-commercial use only - expires 2027-02-05
Set parameter OutputFlag to value 1
Set parameter QCPDual to value 1
Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (mac64[arm] - Darwin 25.5.0 25F84)
CPU model: Apple M4 Pro
Thread count: 12 physical cores, 12 logical processors, using up to 12 threads
Non-default parameters:
QCPDual  1
Optimize a model with 30607 rows, 10690 columns and 108040 nonzeros
Model fingerprint: 0xf48e83e6
Variable types: 4188 continuous, 6502 integer (6502 binary)
Coefficient statistics:
  Matrix range     [1e+00, 9e+02]
  Objective range  [1e-01, 7e+00]
  Bounds range     [1e+00, 1e+00]
  RHS range        [1e+00, 1e+03]
Found heuristic solution: objective 0.1000000
Presolve removed 17840 rows and 1466 columns
Presolve time: 0.24s
Presolved: 12767 rows, 9224 columns, 81311 nonzeros
Variable types: 3504 continuous, 5720 integer (5714 binary)
Root relaxation: objective -1.186277e+02, 1171 iterations, 0.05 seconds (0.07 work units)
    Nodes    |    Current Node    |     Objective Bounds      |     Work
 Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time
     0     0 -118.62771    0   53    0.10000 -118.62771      -     -    0s
     0     0 -118.51700    0   62    0.10000 -118.51700      -     -    0s
H    0     0                      -3.7564221 -118.51700  3055%     -    0s
H    0     0                     -76.6465356 -118.51700  54.6%     -    0s
H    0     0                     -76.7465356 -118.51700  54.4%     -    0s
     0     0 -118.21224    0   60  -76.74654 -118.21224  54.0%     -    0s
H    0     0                     -79.0760121 -118.21224  49.5%     -    0s
H    0     0                    -113.3642782 -118.12057  4.20%     -    0s
     0     0 -118.12057    0   55 -113.36428 -118.12057  4.20%     -    0s
     0     0 -118.07057    0   41 -113.36428 -118.07057  4.15%     -    0s
H    0     0                    -113.4642782 -118.07057  4.06%     -    0s
     0     0 -118.07057    0   36 -113.46428 -118.07057  4.06%     -    0s
     0     0 -118.07057    0   33 -113.46428 -118.07057  4.06%     -    0s
     0     0 -118.07057    0   18 -113.46428 -118.07057  4.06%     -    0s
     0     0 -118.07057    0   18 -113.46428 -118.07057  4.06%     -    0s
H    0     0                    -117.5705690 -118.07057  0.43%     -    0s
     0     0 -118.07057    0   18 -117.57057 -118.07057  0.43%     -    0s
     0     0 -118.07057    0   13 -117.57057 -118.07057  0.43%     -    1s
     0     0 -118.07057    0   13 -117.57057 -118.07057  0.43%     -    1s
     0     0 -118.07057    0    2 -117.57057 -118.07057  0.43%     -    1s
     0     0 -118.07057    0   37 -117.57057 -118.07057  0.43%     -    1s
     0     0 -118.07057    0   25 -117.57057 -118.07057  0.43%     -    1s
     0     0 -118.07057    0   20 -117.57057 -118.07057  0.43%     -    1s
     0     0 -118.07057    0    2 -117.57057 -118.07057  0.43%     -    1s
H    0     0                    -117.8705690 -118.07057  0.17%     -    1s
     0     0 -118.07057    0   11 -117.87057 -118.07057  0.17%     -    1s
     0     0 -118.07057    0   11 -117.87057 -118.07057  0.17%     -    1s
     0     0 -118.07057    0   11 -117.87057 -118.07057  0.17%     -    1s
     0     0 -118.07057    0   44 -117.87057 -118.07057  0.17%     -    2s
H    0     0                    -117.9705690 -118.07057  0.08%     -    2s
     0     0 -118.07057    0   38 -117.97057 -118.07057  0.08%     -    2s
     0     0 -118.07057    0   31 -117.97057 -118.07057  0.08%     -    2s
     0     0 -118.07057    0   23 -117.97057 -118.07057  0.08%     -    2s
     0     0 -118.07057    0   22 -117.97057 -118.07057  0.08%     -    2s
     0     0 -118.07057    0   22 -117.97057 -118.07057  0.08%     -    2s
     0     0 -118.07057    0   22 -117.97057 -118.07057  0.08%     -    2s
     0     0 -118.07057    0   13 -117.97057 -118.07057  0.08%     -    2s
     0     0 -118.07057    0   13 -117.97057 -118.07057  0.08%     -    2s
Cutting planes:
  Implied bound: 17
  MIR: 8
  Inf proof: 1
  Zero half: 1
  RLT: 12
  Relax-and-lift: 4
Explored 1 nodes (11239 simplex iterations) in 2.68 seconds (2.40 work units)
Thread count was 12 (of 12 available processors)
Solution count 10: -117.971 -117.971 -117.871 ... -76.7465
No other solutions better than -117.971
Optimal solution found (tolerance 1.00e-04)
Best objective -1.179705689649e+02, best bound -1.179705689649e+02, gap 0.0000%
-------------------------------------------------------------------------------
                                    Summary                                    
-------------------------------------------------------------------------------
error_sample1_0 0.0
regularization_edge_has_signal 54.0
Sample 1/30: Objective value = 5.2910
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 2/30: Objective value = 5.3342
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 3/30: Objective value = 5.2666
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 4/30: Objective value = 5.2464
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 5/30: Objective value = 5.3448
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 6/30: Objective value = 5.1948
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 7/30: Objective value = 5.3077
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 8/30: Objective value = 5.2081
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 9/30: Objective value = 5.3122
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 10/30: Objective value = 5.2652
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 11/30: Objective value = 5.2137
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 12/30: Objective value = 5.3676
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 13/30: Objective value = 5.2529
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 14/30: Objective value = 5.4194
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 15/30: Objective value = 5.2722
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 16/30: Objective value = 5.1633
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 17/30: Objective value = 5.4053
  error_sample1_0: -0.0000, rel error: 315.2032
  > Rejected! (315.2032 >= 0.05)
Sample 18/30: Objective value = 5.2273
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 19/30: Objective value = 5.3283
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 20/30: Objective value = 5.1877
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 21/30: Objective value = 5.2407
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 22/30: Objective value = 5.3266
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 23/30: Objective value = 5.3726
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 24/30: Objective value = 5.3392
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 25/30: Objective value = 5.2540
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 26/30: Objective value = 5.2754
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 27/30: Objective value = 5.2294
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 28/30: Objective value = 5.2531
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 29/30: Objective value = 5.3124
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 30/30: Objective value = 5.3054
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 55.0000, rel error: 0.0185
Generated 29 alternative solutions
0 1 2 3 4 5 6 7 8 9 ... 19 20 21 22 23 24 25 26 27 28
(SMAD3) (MYOD1) 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
(GRK2) (BDKRB2) 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
(MAPK14) (MAPKAPK2) 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
(DEPTOR_EEF1A1_MLST8_MTOR_PRR5_RICTOR) (FBXW8) 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
(SLK) (MAP3K5) 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0

5 rows × 29 columns

# Analyze variability in edge selection
df_var = pd.concat([df_sols.mean(axis=1), df_sols.std(axis=1)], axis=1)
df_var.columns = ["mean", "std"]
print("Edges with highest variability across solutions:")
df_var.sort_values(by="std", ascending=False).head(50).sort_values(by="mean")
Edges with highest variability across solutions:
mean std
(TBK1) (AKT1) 0.172414 0.384426
(PPP1CA) (AKT1) 0.172414 0.384426
(MAPK14) (SMAD3) 0.172414 0.384426
(CASP3) 0.206897 0.412251
(PRKCA) (TP53) 0.206897 0.412251
(PPP1CA) (TP53) 0.206897 0.412251
(TBK1) (RELA) 0.206897 0.412251
(TAB1) (ROR2) 0.206897 0.412251
(MAPK8) (TP53) 0.206897 0.412251
(PRKCD) (MAPK8) 0.206897 0.412251
(MAP3K7) (MAP2K3) 0.206897 0.412251
(RELA) (JUN) 0.206897 0.412251
(MAP2K3) (MAPK14) 0.206897 0.412251
(TRAF6) (TAB1) 0.206897 0.412251
(MAPK8) (MYC) 0.241379 0.435494
(TRAF2) (TICAM1) 0.241379 0.435494
(PPP2CA) (TRAF2) 0.241379 0.435494
(GSK3B) (RELA) 0.241379 0.435494
(AIMP1) (SMURF2) 0.241379 0.435494
(MAPK8) (AKT1) 0.241379 0.435494
(AIMP1) 0.241379 0.435494
(SMURF2) (SMAD5) 0.241379 0.435494
(ROR2) (MAPK8) 0.241379 0.435494
(MAP2K6) (MAPK14) 0.241379 0.435494
(MAP3K7) (MAP2K6) 0.241379 0.435494
(TBK1) (NFKB1_RELA) 0.275862 0.454859
(MAPK14) (CDKN1A) 0.275862 0.454859
(CDK1) (TP53) 0.310345 0.470824
(MAPK1) (JUN) 0.379310 0.493804
(MAP3K7) (MAP2K4) 0.413793 0.501230
(MAP2K4) (MAPK14) 0.413793 0.501230
(CDK1) (AKAP12) 0.448276 0.506120
(PHLPP1) (PRKCA) 0.448276 0.506120
(GSK3B) (PHLPP1) 0.448276 0.506120
(SMAD3) (SMAD4) 0.448276 0.506120
(AKAP12) (PRKCA) 0.448276 0.506120
(RELA) (EGR1) 0.482759 0.508548
(NFKB1_RELA) (EGR1) 0.517241 0.508548
(SMAD2) (SMAD4) 0.551724 0.506120
(GSK3B) (CDKN1A) 0.689655 0.470824
(PPP1CA) (MAPK1) 0.724138 0.454859
(CDK1) (PPP1CA) 0.724138 0.454859
(JUN) (SPI1) 0.758621 0.435494
(GSK3B) (PRKCE) 0.758621 0.435494
(PRKCE) (TICAM2) 0.758621 0.435494
(MAPK8) (STAT1) 0.758621 0.435494
(MAPK1) (MYC) 0.758621 0.435494
(AKT1) (SKI) 0.758621 0.435494
(SKI) (SMAD5) 0.758621 0.435494
(TICAM2) (TICAM1) 0.758621 0.435494
# Analyze edge values across solutions
df_edge_val = pd.DataFrame(np.concatenate(edge_results["edge_values"], axis=1), index=m.processed_graph.E).astype(int)
df_edge_val.head()
0 1 2 3 4 5 6 7 8 9 ... 19 20 21 22 23 24 25 26 27 28
(SMAD3) (MYOD1) 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
(GRK2) (BDKRB2) 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
(MAPK14) (MAPKAPK2) 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
(DEPTOR_EEF1A1_MLST8_MTOR_PRR5_RICTOR) (FBXW8) 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
(SLK) (MAP3K5) 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0

5 rows × 29 columns

Vertex-Based Perturbation#

Next, we’ll sample solutions by perturbing vertex variables. This approach can highlight alternative active node sets that are consistent with the data.

# Create a fresh model
m = CarnivalFlow(lambda_reg=0.1)
P = m.build(dataset.graph, dataset.data)

# Sample solutions by perturbing vertex_value
vertex_results = sample_alternative_solutions(
    problem=P,
    target_variable=P.expr.vertex_value,
    percentage=0.10,
    scale=0.03,
    rel_opt_tol=0.05,
    max_samples=30,
)

# Analyze vertex-based results
df_vertex_val = pd.DataFrame(np.concatenate(vertex_results["vertex_values"], axis=1), index=m.processed_graph.V).astype(
    int
)
print(f"Generated {df_vertex_val.shape[1]} alternative solutions")
df_vertex_val.head()
Unreachable vertices for sample: 0
===============================================================================
                                     CVXPY                                     
                                     v1.7.1                                    
===============================================================================
-------------------------------------------------------------------------------
                                  Compilation                                  
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
                                Numerical solver                               
-------------------------------------------------------------------------------
Set parameter OutputFlag to value 1
Set parameter QCPDual to value 1
Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (mac64[arm] - Darwin 25.5.0 25F84)
CPU model: Apple M4 Pro
Thread count: 12 physical cores, 12 logical processors, using up to 12 threads
Non-default parameters:
QCPDual  1
Optimize a model with 30607 rows, 10690 columns and 108040 nonzeros
Model fingerprint: 0xf48e83e6
Variable types: 4188 continuous, 6502 integer (6502 binary)
Coefficient statistics:
  Matrix range     [1e+00, 9e+02]
  Objective range  [1e-01, 7e+00]
  Bounds range     [1e+00, 1e+00]
  RHS range        [1e+00, 1e+03]
Found heuristic solution: objective 0.1000000
Presolve removed 17840 rows and 1466 columns
Presolve time: 0.20s
Presolved: 12767 rows, 9224 columns, 81311 nonzeros
Variable types: 3504 continuous, 5720 integer (5714 binary)
Root relaxation: objective -1.186277e+02, 1171 iterations, 0.04 seconds (0.07 work units)
    Nodes    |    Current Node    |     Objective Bounds      |     Work
 Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time
     0     0 -118.62771    0   53    0.10000 -118.62771      -     -    0s
     0     0 -118.51700    0   62    0.10000 -118.51700      -     -    0s
H    0     0                      -3.7564221 -118.51700  3055%     -    0s
H    0     0                     -76.6465356 -118.51700  54.6%     -    0s
H    0     0                     -76.7465356 -118.51700  54.4%     -    0s
     0     0 -118.21224    0   60  -76.74654 -118.21224  54.0%     -    0s
H    0     0                     -79.0760121 -118.21224  49.5%     -    0s
H    0     0                    -113.3642782 -118.12057  4.20%     -    0s
     0     0 -118.12057    0   55 -113.36428 -118.12057  4.20%     -    0s
     0     0 -118.07057    0   41 -113.36428 -118.07057  4.15%     -    0s
H    0     0                    -113.4642782 -118.07057  4.06%     -    0s
     0     0 -118.07057    0   36 -113.46428 -118.07057  4.06%     -    0s
     0     0 -118.07057    0   33 -113.46428 -118.07057  4.06%     -    0s
     0     0 -118.07057    0   18 -113.46428 -118.07057  4.06%     -    0s
     0     0 -118.07057    0   18 -113.46428 -118.07057  4.06%     -    0s
H    0     0                    -117.5705690 -118.07057  0.43%     -    0s
     0     0 -118.07057    0   18 -117.57057 -118.07057  0.43%     -    0s
     0     0 -118.07057    0   13 -117.57057 -118.07057  0.43%     -    0s
     0     0 -118.07057    0   13 -117.57057 -118.07057  0.43%     -    0s
     0     0 -118.07057    0    2 -117.57057 -118.07057  0.43%     -    1s
     0     0 -118.07057    0   37 -117.57057 -118.07057  0.43%     -    1s
     0     0 -118.07057    0   25 -117.57057 -118.07057  0.43%     -    1s
     0     0 -118.07057    0   20 -117.57057 -118.07057  0.43%     -    1s
     0     0 -118.07057    0    2 -117.57057 -118.07057  0.43%     -    1s
H    0     0                    -117.8705690 -118.07057  0.17%     -    1s
     0     0 -118.07057    0   11 -117.87057 -118.07057  0.17%     -    1s
     0     0 -118.07057    0   11 -117.87057 -118.07057  0.17%     -    1s
     0     0 -118.07057    0   11 -117.87057 -118.07057  0.17%     -    1s
     0     0 -118.07057    0   44 -117.87057 -118.07057  0.17%     -    1s
H    0     0                    -117.9705690 -118.07057  0.08%     -    1s
     0     0 -118.07057    0   38 -117.97057 -118.07057  0.08%     -    1s
     0     0 -118.07057    0   31 -117.97057 -118.07057  0.08%     -    1s
     0     0 -118.07057    0   23 -117.97057 -118.07057  0.08%     -    1s
     0     0 -118.07057    0   22 -117.97057 -118.07057  0.08%     -    1s
     0     0 -118.07057    0   22 -117.97057 -118.07057  0.08%     -    1s
     0     0 -118.07057    0   22 -117.97057 -118.07057  0.08%     -    1s
     0     0 -118.07057    0   13 -117.97057 -118.07057  0.08%     -    1s
     0     0 -118.07057    0   13 -117.97057 -118.07057  0.08%     -    1s
Cutting planes:
  Implied bound: 17
  MIR: 8
  Inf proof: 1
  Zero half: 1
  RLT: 12
  Relax-and-lift: 4
Explored 1 nodes (11239 simplex iterations) in 1.86 seconds (2.40 work units)
Thread count was 12 (of 12 available processors)
Solution count 10: -117.971 -117.971 -117.871 ... -76.7465
No other solutions better than -117.971
Optimal solution found (tolerance 1.00e-04)
Best objective -1.179705689649e+02, best bound -1.179705689649e+02, gap 0.0000%
-------------------------------------------------------------------------------
                                    Summary                                    
-------------------------------------------------------------------------------
error_sample1_0 0.0
regularization_edge_has_signal 54.0
Sample 1/30: Objective value = 5.3506
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 2/30: Objective value = 5.3926
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 3/30: Objective value = 5.4280
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 4/30: Objective value = 5.3630
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 5/30: Objective value = 5.4156
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 6/30: Objective value = 5.3873
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 7/30: Objective value = 5.3842
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 8/30: Objective value = 5.3906
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 9/30: Objective value = 5.3064
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 10/30: Objective value = 5.4230
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 11/30: Objective value = 5.3525
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 12/30: Objective value = 5.3631
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 13/30: Objective value = 5.3528
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 14/30: Objective value = 5.3961
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 15/30: Objective value = 5.3628
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 16/30: Objective value = 5.3982
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 17/30: Objective value = 5.3655
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 55.0000, rel error: 0.0185
Sample 18/30: Objective value = 5.2853
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 19/30: Objective value = 5.3232
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 20/30: Objective value = 5.3768
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 21/30: Objective value = 5.2673
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 22/30: Objective value = 5.3205
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 23/30: Objective value = 5.3125
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 55.0000, rel error: 0.0185
Sample 24/30: Objective value = 5.3489
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 25/30: Objective value = 5.4452
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 26/30: Objective value = 5.3848
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 27/30: Objective value = 5.2272
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 28/30: Objective value = 5.3814
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 29/30: Objective value = 5.3650
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Sample 30/30: Objective value = 5.2891
  error_sample1_0: 0.0000, rel error: 0.0000
  regularization_edge_has_signal: 54.0000, rel error: 0.0000
Generated 30 alternative solutions
0 1 2 3 4 5 6 7 8 9 ... 20 21 22 23 24 25 26 27 28 29
SMAD2_SMAD4 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
NFKBIB -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ... -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
MDC1 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
BRSK1 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
EREG 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0

5 rows × 30 columns

# Focus on signaling proteins with no measurement data
df_sig_prot_pred = df_vertex_val.loc[df_vertex_val.index.difference(dataset.data.query.pluck_features())]
df_sig_prot_pred.head()
0 1 2 3 4 5 6 7 8 9 ... 20 21 22 23 24 25 26 27 28 29
AAK1 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
ABI1 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
ABL1 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
ABRAXAS1 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
ACAT1 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0

5 rows × 30 columns

# Visualize the variability in predicted signaling protein activities
import matplotlib.pyplot as plt

plt.figure(figsize=(12, 6))
df_sig_prot_pred.std(axis=1).sort_values().tail(30).plot.bar(title="Variability in Signaling Protein Activity")
plt.xlabel("Protein")
plt.ylabel("Standard Deviation")
plt.tight_layout()
plt.show()
../../_images/5c4d13613e03fdf0b9a0942f517dfa015210941bfec7350bcad4036068408725.png

Summary#

In this notebook, we:

  1. Created a generic sampling method for exploring alternative network solutions

  2. Applied the method to both edge and vertex perturbations

  3. Analyzed the variability in solutions to identify:

    • Edges with high uncertainty (appearing in some but not all solutions)

    • Signaling proteins with variable activity across solutions

This approach helps assess the robustness of network inference results and can guide further experimental validation efforts by highlighting the most uncertain parts of the network.