Independent cascade modelling in Python
To start with, I'd add the activation probability as a parameter of the independant_cascade_igraph function. That way you are assured that the attribute is defined on the edges and can also manage default value. Thus you are not required to know the name of the edge attribute  in advance to use your function:

def independent_cascade_igraph(G, seeds, probs=0.1, steps=0):
    # init activation probabilities
    if (type(probs) in (int, float) and (0 &lt;= probs &lt;= 1)) or all(0 &lt;= p &lt;= 1 for p in probs):
        G.es['act_prob'] = probs
    else:
        raise Exception("Found probability outside of [0, 1]")


However I recommend you define your own Exception so further handling is better performed.

The copy of the seeds can be simplified since it represent vertices which are always integers:

    # perform diffusion
    A = seeds[:] # prevent side effect


The two functions for the diffusion hold the same logic, only the stopping condition differ. DRY:

    if steps &lt;= 0:
        # perform diffusion until no more nodes can be activated
        return _diffuse(G, A)
    # perform diffusion for at most "steps" rounds
    return _diffuse(G, A, steps)

def _diffuse(G, A, steps=None):
    vertices = G.vcount()
    tried_edges = set()
    layer_i_nodes = [A[:]]
    while True:
        len_old = len(A)
        activated_nodes_round, tried_edges_round = _diffuse_one_round(G, A, tried_edges)
        layer_i_nodes.append(activated_nodes_round)
        tried_edges |= tried_edges_round
        if len(A) == len_old:
            break
        if steps is not None:
            steps -= 1
            if not steps or len(A) &gt;= vertices:
                break
    return layer_i_nodes


Updating the set is more idiomatic.

You don't need to return A from _diffuse_one_round since it is modified in place:

def _diffuse_one_round(G, A, tried_edges):
    cur_tried_edges = set((s, nb) for s in A for nb in G.successors(s)) - tried_edges
    activated_nodes = [node for src,node in cur_tried_edges if node not in A and _prop_success(G, src, node)]
    A.extend(set(activated_nodes))
    return activated_nodes, cur_tried_edges


I'm going to assume that you actually what to retrieve the attribute here, orherwise just remove this function, all the attribute handling at the beginning and use random.random() &lt;= 0.1 in the list comprehension:

def _prop_success(G, src, dest, r=random.random): # Faster access to random
    id = G.get_eid(src, dest)
    return r() &lt;= G.es[id]['act_prob']


You can then test with:

test_g = Graph([(1,2), (2,1), (1,3), (3,1), (2,3), (2,4), (3,4), (3,5), (4,5), (5,6), (6,5), (6,4), (6,2)], directed=True)
independent_cascade_igraph(test_g, [1], [.5, .5, .2, .2, .3, .5, .1, .2, .2, .6, .6, .3, .4])




Note however that the very purpose of _diffuse_one_round, its gained simplicity and the fact that it has the non-trivial side effect of modifying A in place, all play in favor of merging it in the while loop of _diffuse instead of keeping it:

def _diffuse(G, active_nodes, steps=None):
    vertices = G.vcount()
    visited_edges = set()
    activation_history = [active_nodes[:]]
    previous_len = None
    while previous_len != len(active_nodes) and (step is None or step):
        previous_len = len(active_nodes)
        current_edges = set((begin, end) for begin in active_nodes for end in G.successors(begin)) - visited_edges
        activated_nodes = [node for src,node in current_edges if node not in A and _prop_success(G, src, node)]
        active_nodes.extend(set(activated_nodes))
        activation_history.append(activated_nodes)
        visited_edges |= current_edges
        if steps is not None:
            steps -= 1
    return activation_history


I also renamed some variables to make the code more explicit, which you should do more often.