Example #1
0
def create_graph_planetlab3(n_sources, n_helpers, n_receivers, varargin):
    # MATLAB function A = create_graph_planetlab3(n_sources, n_helpers, n_receivers, varargin)
    # Creates a network connection matrix from planetlab data
    # 
    # Input parameters:
    #   n_sources = number of source nodes
    #   n_helpers = number of helper nodes
    #   n_receivers = number of receivers
    #   optional 'paramater', 'value' pairs:
    #       'maxdistance'  => try to find a network with a given maximum distance between two nodes
    #       'maxtries'     => number of times to try to find a network of the specified maximum distance
    #       'removeunnecessary' = specifies whether the nodes which were randomly selected but do not have a connection to any receiver
    #                               or to any source are to be removed (default) or not. This means that in the end the number of helper nodes 
    #                               might be smaller than n_helpers
    #
    # Output parameters
    #   RA = the connection matrix
    #
    # Nicolae Cleju, TUIASI, 2009
    
    # Python
    varargin['n_sources'] = n_sources
    varargin['n_helpers'] = n_helpers
    varargin['n_receivers'] = n_receivers
    
    # parse inputs
    #p = inputParser;   # Create instance of inputParser class.
    p = MatlabInputParser.MatlabInputParser()   # Create instance of inputParser class.
    p.addRequired('n_sources',   lambda x: (numpy.isreal(x) and x > 0));
    p.addRequired('n_helpers',   lambda x: (numpy.isreal(x) and x > 0));
    p.addRequired('n_receivers', lambda x: (numpy.isreal(x) and x > 0));
    #p.addParamValue('maxdistance', -1, lambda x: (numpy.isreal(x) and x > 0));
    p.addParamValue('maxdistance', -1, lambda x: (numpy.isreal(x)));
    p.addParamValue('maxtries', 50, lambda x: (numpy.isreal(x) and x > 0));
    p.addParamValue('removeunnecessary', True, lambda x: (numpy.isreal(x) and x > 0));
    p.addParamValue('minnnodes', 15, lambda x: (numpy.isreal(x) and x > 0));
    #p.parse(n_sources, n_helpers, n_receivers, varargin{:});
    p.parse(varargin)
    n_sources   = p.Results['n_sources']
    n_helpers   = p.Results['n_helpers']
    n_receivers = p.Results['n_receivers']
    maxdistance = p.Results['maxdistance']
    maxtries    = p.Results['maxtries']
    removeunnecessary = p.Results['removeunnecessary']
    minnnodes   = p.Results['minnnodes']
    
    # Internal parameters
    #num_parents = 4;
    num_parents = 4
    
    # Select parents only from most distant nodes
    #do_distant_parents = 1;
    do_distant_parents = 1
    # Initial percent
    #dp_start = 0.6;
    dp_start = 0.6
    
    #if exist('planetlab_data.mat', 'file')
    #    load 'planetlab_data.mat'
    #else
    try:
        mdict = scipy.io.loadmat('planetlab_data.mat')
        M = mdict['M']
        Nmax = mdict['Nmax']
    except:
        print("Error: couldn't load 'planetlab_data.mat'");
        raise
        #        fid = fopen('planetlab.txt');
        #        C = textscan(fid, '#s #s #n #n #n #n #n #n', 'Delimiter',',', 'HeaderLines', 1, 'TreatAsEmpty', 'N/A');
        #        fclose(fid);
        #    
        #        B1 = unique(C{1,1});
        #        B2 = unique(C{1,2});
        #        nodesmap = union(B1, B2);
        #        Nmax = numel(nodesmap);
        #        Lmax = numel(C{1,1});
        #        M = zeros(Nmax, Nmax);
        #    
        #        for i = 1:Nmax
        #            # A struct in matlab _is_ a hash table, since you can use variables for addressing field names.
        #            fieldname = [ nodesmap{i};];
        #            fieldname(fieldname == '.') = '_';
        #            fieldname(fieldname == '-') = [];
        #            if ~isletter(fieldname(1))
        #                fieldname = ['n' fieldname];
        #            end
        #            structhash.(fieldname) = i;
        #    
        #        end
        #    
        #        for i = 1:Lmax
        #            fieldname = C{1,1}{i,1};
        #            fieldname(fieldname == '.') = '_';
        #            fieldname(fieldname == '-') = [];
        #            if ~isletter(fieldname(1))
        #                fieldname = ['n' fieldname];
        #            end
        #            node1 = fieldname;
        #    
        #            fieldname = C{1,2}{i,1};
        #            fieldname(fieldname == '.') = '_';
        #            fieldname(fieldname == '-') = [];
        #            if ~isletter(fieldname(1))
        #                fieldname = ['n' fieldname];
        #            end
        #            node2 = fieldname;
        #    
        #            value = C{1,6}(i);          #bottleneck_available_bandwidth_pathchirp(Kbps)
        #            if ~isfinite(value)
        #                value = C{1,7}(i);      #bottleneck_available_bandwidth_spruce(Kbps)
        #                if ~isfinite(value)
        #                    value = C{1,5}(i);  #bottleneck_capacity(Kbps)
        #                    if ~isfinite(value)
        #                        value = 0;      # no data available for this pair of nodes
        #                    end
        #                end
        #            end
        #                
        #            M(structhash.(node1), structhash.(node2)) = value;
        #        end
        #    
        #        clear C
        #        save 'planetlab_data.mat'
        #    end
    
    #M(isnan(M)) = 0;
    M[numpy.isnan(M)] = 0
    
    # assuming a packet of 1024B payload, 8B UDP header, 2*32B NC coeffs header
    ##pktsize = (1024 + 8 + 2*32) * 8; # in bps
    #pktsize = (512 + 8 + 2*32) * 8; # in bps
    pktsize = (512. + 8 + 2*32) * 8; # in bps
    
    # capacity values are in  Kbps
    ##M = (1000*M) ./ (100*pktsize);   # using only 1/100 of available bandwidth
    #M = (1000*M) ./ (200*pktsize);   # using only 1/200 of available bandwidth
    M = (1000.*M) / (200.*pktsize);   # using only 1/200 of available bandwidth
    
    #M = sparse(M);
    
    #nnodes = n_sources + n_helpers + n_receivers;
    nnodes = n_sources + n_helpers + n_receivers
    #G = zeros(nnodes, nnodes);
    G = numpy.zeros((nnodes, nnodes))
    
    #goodradius = 0;
    goodradius = 0
    #goodnnodes = 0;
    goodnnodes = 0
    #ntries = 0;
    ntries = 0
    #while (~goodradius || ~goodnnodes)&& ntries < maxtries
    while (not goodradius or not goodnnodes) and ntries < maxtries:
    
        # choose different sources
        #reachables = [];
        reachables = numpy.array([])
        #while numel(reachables) < (n_helpers + n_receivers)
        while reachables.size < (n_helpers + n_receivers):
            #s = [];
            #s = numpy.array([])
            s = []
            #for i = 1:n_sources
            for i in xrange(n_sources):
                #newsource = ceil(Nmax * rand());
                #newsource = math.ceil(Nmax * rng.rand())
                newsource = math.floor(Nmax * rng.rand()) # floor() because now we are 0-based
                #while ~isempty(find(s == newsource))
                while numpy.nonzero(s == newsource)[0].size != 0:
                    #newsource = ceil(Nmax * rand());
                    #newsource = math.ceil(Nmax * rng.rand())
                    newsource = math.floor(Nmax * rng.rand())  # floor() because now we are 0-based
                #end
                
                ##s(i) = newsource;
                #s[i] = newsource
                s.append(newsource)
    
                #[d pred] = shortest_paths(M, s(i));
                d = graph.shortest_paths(M, s[i])
                #curr_reachables = (1:Nmax);
                curr_reachables = numpy.arange(Nmax)
                #curr_reachables = curr_reachables(~isinf(d));
                curr_reachables = curr_reachables[numpy.logical_not(numpy.isinf(d))]
    
                #reachables = union(reachables, curr_reachables);
                reachables = numpy.union1d(reachables, curr_reachables)
            #end
        #end
        
        #allnodes = s;
        allnodes = numpy.array(s)
        #allnodesnotreceivers = s;
        allnodesnotreceivers = numpy.array(s)
    
        # choose helper nodes
        #for i = (n_sources+1):nnodes
        for i in xrange(n_sources,nnodes):
    
            # choose a new node
    
            #newnode = s(1); # init the new node, such that we surely enter the while loop
            newnode = s[0]   # init the new node, such that we surely enter the while loop
            #isreachable = 0;
            isreachable = 0
    
            #while ~isempty(find(allnodes == newnode)) || ~isreachable
            while numpy.nonzero(allnodes == newnode)[0].size !=0 or not isreachable:
                
                # choose random new node
                #newnode = ceil(Nmax * rand());
                #newnode = math.ceil(Nmax * rng.rand())
                newnode = math.floor(Nmax * rng.rand())  # floor() because now we are 0-bases
    
                # choose num_parents parents out of the previous nodes
                #parents = [];
                parents = numpy.array([])
                #if numel(allnodesnotreceivers) <= num_parents
                if allnodesnotreceivers.size <= num_parents:
                    #parents = allnodesnotreceivers;
                    parents = allnodesnotreceivers.copy()
                    #parents_indexes = 1:numel(allnodesnotreceivers);
                    parents_indexes = numpy.arange(allnodesnotreceivers.size)
                else:
                    
                    # Select only parents which are distant from sources ?
                    #if do_distant_parents
                    if do_distant_parents:
                        #parent_distance = Dmin(1:numel(allnodesnotreceivers));
                        parent_distance = Dmin[:allnodesnotreceivers.size]
                        #enough_candidate_parents = 0;
                        enough_candidate_parents = 0
                        #dp = dp_start;
                        dp = dp_start
                        #while ~enough_candidate_parents && dp > 0
                        while not enough_candidate_parents and dp > 0:
                            #candidate_parents_indexes = find(parent_distance >= dp*max(parent_distance));
                            candidate_parents_indexes = numpy.nonzero(parent_distance >= dp*numpy.max(parent_distance))[0]
                            #if numel(candidate_parents_indexes) < num_parents
                            if candidate_parents_indexes.size < num_parents:
                                #dp = dp - 0.1;
                                dp = dp - 0.1
                            else:
                            	  #enough_candidate_parents = 1;
                                enough_candidate_parents = 1
                            #end
                        #end
                        #if ~enough_candidate_parents
                        if not enough_candidate_parents:
                            #candidate_parents_indexes = 1:numel(allnodesnotreceivers);
                            candidate_parents_indexes = numpy.arange(allnodesnotreceivers.size)
                        #end
                    else:
                        #candidate_parents_indexes = 1:numel(allnodesnotreceivers);
                        candidate_parents_indexes = numpy.arange(allnodesnotreceivers.size)
                    #end
                    
                    #perm = randperm(numel(candidate_parents_indexes));
                    #perm = rng.shuffle(numpy.arange(candidate_parents_indexes.size))
                    #perm = numpy.random.shuffle(numpy.arange(candidate_parents_indexes.size))
                    perm = numpy.random.permutation(candidate_parents_indexes.size)
                    #parents_indexes = candidate_parents_indexes[perm[:num_parents]]
                    parents_indexes = candidate_parents_indexes[perm[:num_parents]]
                    #parents = allnodesnotreceivers(parents_indexes);
                    parents = allnodesnotreceivers[parents_indexes]
    
                #end
    
                # create corresponding column in the overlay graph matrix G
                #newcolumn = zeros(nnodes, 1);
                #newcolumn = numpy.zeros((nnodes, 1))
                newcolumn = numpy.zeros(nnodes)
                #for j = 1:numel(parents_indexes)
                for j in xrange(parents_indexes.size):
                    #newcolumn(parents_indexes(j)) = M(parents(j), newnode);
                    newcolumn[parents_indexes[j]] = M[parents[j], newnode]
                #end
    
                # if at least one input connection is non-zero, add node
                #if ~isempty(find(newcolumn))
                if numpy.nonzero(newcolumn)[0].size != 0:
                    #isreachable = true;
                    isreachable = True
                else:
                    #isreachable = false;
                    isreachable = False
                #end
    
            #end
            
            # add node
            #allnodes = [allnodes newnode];
            allnodes = numpy.hstack((allnodes, newnode))
            #G(:,i) = newcolumn;
            G[:,i] = newcolumn
            #if nnodes - numel(allnodes) >= n_receivers
            if nnodes - allnodes.size >= n_receivers:
                #allnodesnotreceivers = allnodes;
                allnodesnotreceivers = allnodes.copy()
            #end
            
            # Redo distance matrix
            #[R D] = breadthdist(G);
            R,D = graph.all_pairs_sp(G)
            
            # Min distance from a source to the node
            #D(1:(nnodes+1):end) = 0;
            #D[1:(nnodes+1):end] = 0;
            #Dmin = min(D(1:n_sources, :),[],1);
            Dmin = numpy.min(D[:n_sources, :],0)
        #end        
        
        
        # no cycles possible!
        ##A = break_cycles(G);
        ##assert(A == G);
        #A = G;
        A = G.copy()
    
        # remove unnecessary nodes, i.e. nodes that cannot be reached by any
        #  source and nodes that do no reach any client
        #if removeunnecessary
        if removeunnecessary:
            #[R D] = breadthdist(A);
            R,D = graph.all_pairs_sp(A)
            #unnecessary = [];
            unnecessary = numpy.array([])
            
            # not sources or receivers, only helper nodes
            #for i = (n_sources+1):(nnodes-n_receivers)
            for i in xrange(n_sources,nnodes-n_receivers):
                #if ~any(R(1:n_sources,i)) || ~any(R(i,(nnodes-n_receivers+1):nnodes))  # by means of construction, every node is reachable from at least one source
                if (not numpy.any(R[:n_sources,i])) or (not numpy.any(R[i,(nnodes-n_receivers):nnodes])):  # by means of construction, every node is reachable from at least one source
                    #unnecessary = [unnecessary i];
                    unnecessary = numpy.hstack((unnecessary, i))
                #end
            #end
            #A(unnecessary, :) = [];
            numpy.delete(A,unnecessary,0)
            #A(:, unnecessary) = [];
            numpy.delete(A,unnecessary,1)
        #end
        
        # check if number of remaining nodes >= minnodes
        #if size(A,1) >= minnnodes
        if A.shape[0] >= minnnodes:
            #goodnnodes = 1;
            goodnnodes = 1
        else:
            #goodnnodes = 0;
            goodnnodes = 0
        #end
        
        # find max radius (in not-directional network)
        #[R D] = breadthdist(A + A');
        R,D = graph.all_pairs_sp(A + A.T)
        #D(find(D==Inf)) = 0;
        D[D==numpy.Inf] = 0
        #maxradius = max(max(D));
        maxradius = numpy.max(D)
    
        # check if maxdistance == maxradius
        #if maxdistance ~= -1
        if maxdistance != -1:
            #if maxdistance == maxradius
            if maxdistance == maxradius:
                #goodradius = 1;
                goodradius = 1
            else:
                #goodradius = 0;
                goodradius = 0
            #end
        else:
            #goodradius = 1;
            goodradius = 1
        #end
        
        #ntries = ntries + 1;
        ntries = ntries + 1
        #if mod(ntries,10) == 0
        if ntries%10 == 0:
            #disp([num2str(ntries) ' tries...']);
            print(str(ntries) + ' tries...')
        #end
    #end
    
    #if ~goodradius || ~goodnnodes
    if not goodradius or not goodnnodes:
        #error('create_graph_planetlab3:cantcreatenetwork', 'Could not create network with specified max distance and number of nodes');
        print('Could not create network with specified max distance and number of nodes')
        raise
    else:
        #disp(['Created network after ' num2str(ntries) ' tries']);
        print('Created network after ' + str(ntries) + ' tries')
        #disp(['Total number of nodes = ' num2str(size(A,1))]);
        print('Total number of nodes = ' + str(A.shape[0]))
    #end
    
    #end
    
    return A
Example #2
0
def run_example(folder, randstate, net, sim, runopts):
    # MATLAB function run_example(folder, randstate, net, sim, runopts)
    #============================================
    ## Description
    #============================================
    # Runs a loaded scenario
    #
    # Nicolae Cleju, EPFL, 2008/2009,
    #                TUIASI, 2009/2011
    #============================================
    
    # Init winners
    winners = {}
    #winners.global_delay = [];
    winners['global_delay'] = numpy.array([])
    winners['global_flow']  = numpy.array([])
    #winners.dist_delay   = {};
    winners['dist_delay']   = {'guard': 'dict must be non-empty otherwise scipy.io.savemat() fails'}
    winners['dist_flow']    = {'guard': 'dict must be non-empty otherwise scipy.io.savemat() fails'}
    #winners.r = [];
    winners['r'] = numpy.array([])
    
    # First create basic file (nonc, allnodesnc, random)
    #output_scenario_folder(folder, net, sim, runopts, winners);
    files.output_scenario_folder(folder, net, sim, runopts, winners)
    
    # Run Algorithm 2
    #if runopts.do_global_delay
    if runopts['do_global_delay']:
        #disp('Global, delay:');
        print('Global, delay:')
        #winners.global_delay = Algo2_Centralized_NC_sel(net,sim,runopts,'delay');
        winners['global_delay'] = Algos.Algo2_Centralized_NC_sel(net,sim,runopts,'delay')
    #end
    # Save winners
    #save([folder '/matlab.mat'], 'winners', '-append');
    mdict = scipy.io.loadmat(folder + '/matlab.mat')
    mdict['winners'] = winners  # append
    scipy.io.savemat(folder + '/matlab.mat', mdict)
    
    #if runopts.do_global_flow
    if runopts['do_global_flow']:
        #disp('Global, flow:');
        print('Global, flow:')
        #winners.global_flow = Algo2_Centralized_NC_sel(net,sim,runopts,'flow');
        winners['global_flow'] = Algos.Algo2_Centralized_NC_sel(net,sim,runopts,'flow')
    #end
    # Save winners
    #save([folder '/matlab.mat'], 'winners', '-append');
    mdict = scipy.io.loadmat(folder + '/matlab.mat')
    mdict['winners'] = winners  # append
    scipy.io.savemat(folder + '/matlab.mat', mdict)
    
    # Run Algorithm 3
    # Set maximum eccentricity (radius)
    #if runopts.rmax == Inf
    if runopts['rmax'] == numpy.Inf:
        #[R D] = breadthdist(net.capacities + net.capacities');
        R,D = graph.all_pairs_sp(net['capacities'] + net['capacities'].T)
        #runopts.rmax = max(max(D));
        runopts['rmax'] = numpy.max(D)
    #end
    
    #if runopts.do_dist_delay || runopts.do_dist_flow
    if runopts['do_dist_delay'] or runopts['do_dist_flow']:
        #winners.r = runopts.rmin:runopts.rstep:runopts.rmax;
        winners['r'] = numpy.arange(runopts['rmin'], runopts['rmax']+1, runopts['rstep'])
    #end
    #if runopts.do_dist_delay
    if runopts['do_dist_delay']:
        #for r = runopts.rmin:runopts.rstep:runopts.rmax
        for r in winners['r']:
            #disp(['Distributed, delay, r = ' num2str(r) ' :']);
            print('Distributed, delay, r = ' + str(r) + ' :')
            #winners.dist_delay{r} = Algo3_Semidistributed_NC_sel(net,sim,runopts,'delay',r);
            winners['dist_delay'][r] = Algos.Algo3_Semidistributed_NC_sel(net,sim,runopts,'delay',r)
            # Save winners
            #save([folder '/matlab.mat'], 'winners', '-append');
            mdict = scipy.io.loadmat(folder + '/matlab.mat')
            mdict['winners'] = winners  # append
            scipy.io.savemat(folder + '/matlab.mat', mdict)
        #end
    #end
    
    #if runopts.do_dist_flow
    if runopts['do_dist_flow']:
        #for r = runopts.rmin:runopts.rstep:runopts.rmax
        for r in winners['r']:
            #disp(['Distributed, flow, r = ' num2str(r) ' :']);
            print('Distributed, flow, r = ' + str(r) + ' :')
            #winners.dist_flow{r} = Algo3_Semidistributed_NC_sel(net,sim,runopts,'flow',r);
            winners['dist_flow'][r] = Algos.Algo3_Semidistributed_NC_sel(net,sim,runopts,'flow',r)
            # Save winners
            #save([folder '/matlab.mat'], 'winners', '-append');
            mdict = scipy.io.loadmat(folder + '/matlab.mat')
            mdict['winners'] = winners  # append
            scipy.io.savemat(folder + '/matlab.mat', mdict)
        #end
    #end
    
    # Create files
    #output_scenario_folder(folder, net, sim, runopts, winners);
    files.output_scenario_folder(folder, net, sim, runopts, winners)
Example #3
0
def create_local_network(net, centralnode, p0matrix, ecc):
    # MATLAB function [localnet local2global global2local] = create_local_network(net, centralnode, p0matrix, ecc)
    # Create a local network structure newnet from the neighborhood of radius
    # ecc around node centralnode
    
    # The local network contains the known neighborhood + receivers (they are known)
    # The receivers are added below the bottom descendants of the neighborhood,
    #  creating links between bottom descendants and added receivers.
    
    # Nodes' input capacities
    #b_i = sum(net.capacities .* (1-net.errorrates), 1);
    #b_i = numpy.sum(net['capacities'] * (1-net['errorrates']), 0)
    # Nodes' output capacities
    #b_o = sum(net.capacities, 2)';
    b_o = numpy.sum(net['capacities'], 1)
    
    # Compute the distance matrix between the nodes of the global net (operate
    # on the undirected graph)
    #[R D] = breadthdist(net.capacities + net.capacities');
    R,D = graph.all_pairs_sp(net['capacities'] + net['capacities'].T)
    
    # Find the known subnetwork
    #subnet_nodes  = union ( find( D(:,centralnode) <= ecc ), find( D(centralnode,:) <= ecc ) );
    subnet_nodes  = numpy.union1d ( numpy.nonzero( D[:,centralnode] <= ecc )[0], numpy.nonzero( D[centralnode,:] <= ecc )[0] )
    ##subnet_nodes  = union ( subnet_nodes, centralnode);
    
    # make subnet_nodes a row vector, if not already
    #if size(subnet_nodes, 1) > size(subnet_nodes, 2)
    #    subnet_nodes = subnet_nodes';
    #end
    # In numpy, vectors are 1D, so neither row or col
    
    # The known subnetwork matrix
    #subnet_caps        = net.capacities(subnet_nodes, subnet_nodes);
    subnet_caps        = net['capacities'][numpy.ix_(subnet_nodes, subnet_nodes)]
    # The known subnetwork error matrix
    #subnet_errorrates  = net.errorrates(subnet_nodes, subnet_nodes);
    subnet_errorrates  = net['errorrates'][numpy.ix_(subnet_nodes, subnet_nodes)]
    # The top ancestors (sources) of the known subnet = the nodes which have no incoming links in the subnet
    #top_ancestors      = subnet_nodes(find(sum(subnet_caps, 1)==0))'; # nodes with input capacity == 0
    # The bottom descendants (clients) of the known subnet = the nodes which have no outgoing links in the subnet
    #bottom_descendants = subnet_nodes(find(sum(subnet_caps, 2)==0))'; # nodes with output capacity == 0
    #bottom_descendants = subnet_nodes[numpy.nonzero(numpy.sum(subnet_caps, 1)==0)] # nodes with output capacity == 0
    # Simpler:
    bottom_descendants = subnet_nodes[numpy.sum(subnet_caps, 1)==0] # nodes with output capacity == 0
    
    # Add the real receivers (the ones which are not already in the subnet)
    # Exclude receivers which happen to lie in the known neighborhood
    #remaining_receivers = setdiff(net.receivers, subnet_nodes);
    remaining_receivers = numpy.setdiff1d(net['receivers'], subnet_nodes)
    #numtoadd = numel(remaining_receivers);
    numtoadd = remaining_receivers.size
    
    # Augment subnet matrix with the added receivers
    #subnet_caps = [subnet_caps  zeros(numel(subnet_nodes), numtoadd)];             # pad with zeros to the right
    #subnet_caps = [subnet_caps; zeros(numtoadd, numel(subnet_nodes) + numtoadd)];  # pad with zeros below
    #subnet_errorrates = [subnet_errorrates  zeros(numel(subnet_nodes), numtoadd)];             # pad with zeros to the right
    #subnet_errorrates = [subnet_errorrates; zeros(numtoadd, numel(subnet_nodes) + numtoadd)];  # pad with zeros below    
    subnet_caps = numpy.hstack((subnet_caps, numpy.zeros((subnet_nodes.size, numtoadd))))             # pad with zeros to the right
    subnet_caps = numpy.vstack((subnet_caps, numpy.zeros((numtoadd, subnet_nodes.size + numtoadd))))  # pad with zeros below
    subnet_errorrates = numpy.hstack((subnet_errorrates, numpy.zeros((subnet_nodes.size, numtoadd))))             # pad with zeros to the right
    subnet_errorrates = numpy.vstack((subnet_errorrates, numpy.zeros((numtoadd, subnet_nodes.size + numtoadd))))  # pad with zeros below
    
    # indices of the bottom descendant nodes, relative to the new node numbers of the subnet
    #rel_bottom_descendants = [];  
    rel_bottom_descendants = numpy.array([])
    
    # create links between subnet bottom descendants and the added real receivers
    #for j = 1:numel(bottom_descendants)
    for j in range(bottom_descendants.size):
    
        # index of the bottom descendant node, relative to the subnetwork node numbers
        #descendantnumber = find(subnet_nodes == bottom_descendants(j));
        descendantnumber = numpy.nonzero(subnet_nodes == bottom_descendants[j])[0]
    
        # For each extra receiver we have to add
        #for i = 1:numtoadd
        for i in range(numtoadd):
    
            # real receiver number, relative to the subnetwork node numbers
            #recvnumber = numel(subnet_nodes) + i;
            recvnumber = subnet_nodes.size + i
    
            # Create a link of capacity equal to the output capacity of the
            #  bottom descendant, but with the loss probability equal to p0(node, client)
            # In this way the total number of packets received by the client
            #  from the node stays the same
            # All the subgraph between the node and the client is  modeled by
            #  this single virtual link of capacity b_0 and loss probability p0
            #subnet_caps      (descendantnumber, recvnumber) = b_o(bottom_descendants(j));
            subnet_caps      [descendantnumber, recvnumber] = b_o[bottom_descendants[j]]
            # Simpler:
            #subnet_errorrates[descendantnumber, recvnumber] = p0matrix[bottom_descendants[j], numpy.nonzero(net['receivers'] == remaining_receivers[i])]
            subnet_errorrates[descendantnumber, recvnumber] = p0matrix[bottom_descendants[j], net['receivers'] == remaining_receivers[i]]
        #end
        
        # add the descendant number to the list of bottom descendants relative numbers
        #rel_bottom_descendants = union(rel_bottom_descendants, descendantnumber);
        rel_bottom_descendants = numpy.union1d(rel_bottom_descendants, descendantnumber)
    #end
    
    # Create the local network structure
    localnet = dict() # Python
    #localnet.nnodes     = numel(subnet_nodes) + numtoadd;
    localnet['nnodes']     = subnet_nodes.size + numtoadd
    #localnet.capacities = subnet_caps;
    localnet['capacities'] = subnet_caps.copy()
    #localnet.errorrates = subnet_errorrates;
    localnet['errorrates'] = subnet_errorrates.copy()
    #localnet.sources    = find(sum(subnet_caps .* (1-subnet_errorrates), 1)==0); # nodes with input capacity == 0, relative to the new node numbers
    localnet['sources']    = numpy.nonzero(numpy.sum(subnet_caps * (1.-subnet_errorrates), 0)==0)[0] # nodes with input capacity == 0, relative to the new node numbers
    #localnet.receivers  = (localnet.nnodes - numel(net.receivers) + 1):localnet.nnodes;  # the last nodes
    localnet['receivers']  = numpy.arange((localnet['nnodes'] - net['receivers'].size), localnet['nnodes'])  # the last nodes
    #allnodes = 1:(localnet.nnodes - numel(localnet.receivers));     # excluding receivers
    allnodes = numpy.arange(localnet['nnodes'] - localnet['receivers'].size)     # excluding receivers
    #localnet.helpers    = setdiff (allnodes, localnet.sources);     # excluding sources
    localnet['helpers']    = numpy.setdiff1d (allnodes, localnet['sources'])     # excluding sources
    
    # Create the local to global mapping of node indices
    #local2global = zeros(1, localnet.nnodes);
    local2global = numpy.zeros(localnet['nnodes'])
    #for i = 1:localnet.nnodes
    for i in range(localnet['nnodes']):
        #if i <= numel(subnet_nodes)
        if i <= (subnet_nodes.size - 1):
            #local2global(i) = subnet_nodes(i);
            local2global[i] = subnet_nodes[i]
        else:
            #local2global(i) = remaining_receivers(i-numel(subnet_nodes));
            local2global[i] = remaining_receivers[i-subnet_nodes.size]
        #end
    #end
    
    # Create the global to local mapping of node indices
    #global2local = zeros(1, net.nnodes);
    global2local = numpy.zeros(net['nnodes'])
    #alllocalnodes = union(subnet_nodes, remaining_receivers);
    alllocalnodes = numpy.union1d(subnet_nodes, remaining_receivers)
    #for i = 1:net.nnodes
    for i in range(net['nnodes']):
        #if ~any(alllocalnodes == i)
        if not numpy.any(alllocalnodes == i):
            #global2local(i) = 0; # Node i not in the local network
            global2local[i] = 0 # Node i not in the local network
        else:
            #global2local(i) = find(alllocalnodes == i);
            global2local[i] = numpy.nonzero(alllocalnodes == i)[0]
        #end
    #end
    
    return localnet,local2global,global2local