def MCCFRobustInstance( network, capacity, supply, cost, U ) : """ this wrapper transforms any convex cost flow instance into an equivalent one for which every Delta-residual graph is strongly connected """ network_aug = mygraph() capacity_rename = {} cost_aug = {} for e in network.edges() : i,j = network.endpoints(e) newedge = (ALGGLOBAL.REGULAR,e) network_aug.add_edge( newedge, i, j ) if e in capacity : capacity_rename[ newedge ] = capacity[e] if e in cost : cost_aug[ newedge ] = cost[e] # add a directed cycle, with prohibitive cost CBOUND = sum([ c(U) for c in cost.values() ]) # since costs are convex, a feasible flow cannot have cost greater than CBOUND prohibit = line(CBOUND) NODES = network.nodes() edgegen = itertools.count() for i,j in zip( NODES, NODES[1:] + NODES[:1] ) : frwd = (ALGGLOBAL.AUGMENTING, edgegen.next() ) network_aug.add_edge( frwd, i, j ) cost_aug[frwd] = prohibit return network_aug, capacity_rename, cost_aug
def SOLVER(roadnet, surplus, objectives): network = mygraph() capacity = {} supply = {i: 0. for i in roadnet.nodes()} cost = {} # functions # oneway_offset = {} # for one-way roads for i, j, road, data in roadnet.edges_iter(keys=True, data=True): supply[j] += surplus[road] cost_data = objectives[road] # edge construction if data.get('oneway', False): # if one-way road # record minimum allowable flow on road zmin = cost_data.max_key() oneway_offset[road] = zmin supply[i] -= zmin supply[j] += zmin # shift and record the cost function on a forward edge only cc = roadbm.costWrapper(cost_data) cc_offset = offsetWrapper(cc, zmin) network.add_edge(road, i, j) cost[road] = cc_offset else: # if bi-directional road... currently, instantiate two edges cc = roadbm.costWrapper(cost_data) ncc = negativeWrapper( cc) # won't have to worry about the C(0) offset network.add_edge((road, +1), i, j) cost[(road, +1)] = cc # network.add_edge((road, -1), j, i) cost[(road, -1)] = ncc # we need to compute the size U of the first cvxcost algorithm phase #U = sum([ len( obj_dict ) for obj_dict in objectives.values() ]) # below is almost certainly just as good a bound, but I'm a scaredy-cat U = sum([len(obj_dict) - 2 for obj_dict in objectives.values()]) f = MinConvexCostFlow(network, {}, supply, cost, U) flow = {} for i, j, road in roadnet.edges_iter(keys=True): if road in oneway_offset: flow[road] = f[road] + oneway_offset[road] else: flow[road] = f[(road, +1)] - f[(road, -1)] flow[road] = int(flow[road]) #print flow return flow
def SOLVER(roadnet, surplus, objectives): network = mygraph() capacity = {} supply = {i: 0.0 for i in roadnet.nodes()} cost = {} # functions # oneway_offset = {} # for one-way roads for i, j, road, data in roadnet.edges_iter(keys=True, data=True): supply[j] += surplus[road] cost_data = objectives[road] # edge construction if data.get("oneway", False): # if one-way road # record minimum allowable flow on road zmin = cost_data.max_key() oneway_offset[road] = zmin supply[i] -= zmin supply[j] += zmin # shift and record the cost function on a forward edge only cc = roadbm.costWrapper(cost_data) cc_offset = offsetWrapper(cc, zmin) network.add_edge(road, i, j) cost[road] = cc_offset else: # if bi-directional road... currently, instantiate two edges cc = roadbm.costWrapper(cost_data) ncc = negativeWrapper(cc) # won't have to worry about the C(0) offset network.add_edge((road, +1), i, j) cost[(road, +1)] = cc # network.add_edge((road, -1), j, i) cost[(road, -1)] = ncc # we need to compute the size U of the first cvxcost algorithm phase # U = sum([ len( obj_dict ) for obj_dict in objectives.values() ]) # below is almost certainly just as good a bound, but I'm a scaredy-cat U = sum([len(obj_dict) - 2 for obj_dict in objectives.values()]) f = MinConvexCostFlow(network, {}, supply, cost, U) flow = {} for i, j, road in roadnet.edges_iter(keys=True): if road in oneway_offset: flow[road] = f[road] + oneway_offset[road] else: flow[road] = f[(road, +1)] - f[(road, -1)] flow[road] = int(flow[road]) # print flow return flow
def SOLVER( roadnet, surplus, measure_dict, congestion_dict ) : from setiptah.nxopt.cvxcostflow import MinConvexCostFlow from setiptah.basic_graph.mygraph import mygraph # a rather crucial measure of the problem's complexity; # see bm.SOLVER for relevant commentary U = sum([ len(m) - 1 for m in measure_dict.values() ]) # instantiate cvxcostflow components network = mygraph() capacity = {} supply = { i : 0. for i in roadnet.nodes() } cost = {} # functions # oneway_offset = {} # to process one-way roads for i,j, road, data in roadnet.edges_iter( keys=True, data=True ) : supply[j] += surplus[road] measure = measure_dict[road] rho = congestion_dict[road] fobj = CONGESTION_OBJECTIVE( measure, rho, U ) # U, here, restricts domain # edge construction if data.get( 'oneway', False ) : # if one-way road # record minimum allowable flow on road zmin = -measure.min_key() # i.e., z + min key of measure >= 0 oneway_offset[road] = zmin # create a 'bias point' supply[i] -= zmin supply[j] += zmin # shift and record the cost function on only a forward edge fobj_offset = roadbm.offsetWrapper( fobj, zmin ) network.add_edge( road, i, j ) cost[ road ] = fobj_offset else : # if bi-directional road... instantiate pair of edges #cc = roadbm.costWrapper( cost_data ) n_fobj = roadbm.negativeWrapper( fobj ) # won't have to worry about the C(0) offset network.add_edge( (road,+1), i, j ) cost[ (road,+1) ] = fobj # network.add_edge( (road,-1), j, i ) cost[ (road,-1) ] = n_fobj f = MinConvexCostFlow( network, {}, supply, cost, U ) # U, here, determines phase count flow = {} for i, j, road in roadnet.edges_iter( keys=True ) : if road in oneway_offset : flow[road] = f[road] + oneway_offset[road] else : flow[road] = f[(road,+1)] - f[(road,-1)] flow[road] = int( flow[road] ) return flow
def SOLVER(roadnet, surplus, measure_dict): network = mygraph() capacity = {} supply = {i: 0. for i in roadnet.nodes()} cost = {} # functions # oneway_offset = {} # for one-way roads for i, j, road, data in roadnet.edges_iter(keys=True, data=True): supply[j] += surplus[road] measure = measure_dict[road] fobj = OBJECTIVE_FUNC(measure) # edge construction if data.get('oneway', False): # if one-way road # record minimum allowable flow on road zmin = -measure.min_key() # i.e., z + min key of measure >= 0 oneway_offset[road] = zmin # create a 'bias point' supply[i] -= zmin supply[j] += zmin # shift and record the cost function on only a forward edge fobj_offset = offsetWrapper(fobj, zmin) network.add_edge(road, i, j) cost[road] = fobj_offset else: # if bi-directional road... instantiate pair of edges #cc = roadbm.costWrapper( cost_data ) n_fobj = negativeWrapper( fobj) # won't have to worry about the C(0) offset network.add_edge((road, +1), i, j) cost[(road, +1)] = fobj # network.add_edge((road, -1), j, i) cost[(road, -1)] = n_fobj """ compute the width U of the first cvxcost algorithm phase; a bound on the optimal flow on any edge; Logic: there cannot be more flow on a given road in the graph than there are total intervals between levels in the network (Proof Sketch): 1. U <= M ; 2. (Prove...) Given any matching instance which induces a measure network w/ U' total intervals between levels, a new matching instance realizing the same measure network can be constructed on just U' points in each set """ # safe-ish... #U = sum([ len(m) + 1 for m in measure_dict.values() ]) # below is almost certainly just as good a bound, but I'm a scaredy-cat U = sum([len(m) - 1 for m in measure_dict.values()]) f = MinConvexCostFlow(network, {}, supply, cost, U) flow = {} for i, j, road in roadnet.edges_iter(keys=True): if road in oneway_offset: flow[road] = f[road] + oneway_offset[road] else: flow[road] = f[(road, +1)] - f[(road, -1)] flow[road] = int(flow[road]) #print flow return flow
def FragileMCCF( network, capacity_in, supply, cost, U, epsilon=None ) : """ network is a mygraph (above) capacity is a dictionary from E -> real capacities supply is a dictionary from E -> real supplies cost is a dictionary from E -> lambda functions of convex cost edge costs 1. Assumes supply is conservative (sum to zero). 2. Assumes every Delta-residual graph is strongly connected, i.e., there exists a path with inf capacity b/w any two nodes; """ if epsilon is None : epsilon = 1 # initialize algorithm data rgraph = mygraph() lincost = {} redcost = {} excess = {} """ ALGORITHM """ # computing U from supplies was wrong, it must be passed in now #U = sum([ b for b in supply.values() if b > 0. ]) #print 'total supply: %f' % U # trimming infinite capacities to U allows negative initial slopes # the initial flow may not be Delta-optimal at the beginning of Stage One, # but achieves Delta-optimality by the end, by saturating any negative cost edges. # most treatments fail to consider negative initial slope, which is totally possible... capacity = {} for e in network.edges() : capacity[e] = min( U, capacity_in.get( e, np.Inf ) ) try : temp = math.floor( math.log(U,2) ) except Exception as ex : ex.U = U raise ex Delta = 2.**temp print 'Delta: %d' % Delta flow = { e : 0. for e in network.edges() } Excess( excess, flow, network, supply ) potential = { i : 0. for i in network.nodes() } while Delta >= epsilon : print '\nnew phase: Delta=%f' % Delta # Delta is fresh, so we need to [re-] linearize the costs and compute residual graph LinearizeCost( lincost, cost, flow, Delta, network ) ReducedCost( redcost, lincost, potential, network ) ResidualGraph( rgraph, flow, capacity, Delta, network ) # cert = { re : c for (re,c) in redcost.iteritems() if re in rgraph.edges() } print 'reduced costs on res. graph, phase init: %s' % repr( cert ) """ Stage 1. """ # for every arc (i,j) in the residual network G(x) for resedge in rgraph.edges() : e,dir = resedge # theory says, we only need to do this at most once per edge... # wouldn't want to question theory # ... keep an eye out for a flip-flop; in theory, shouldn't happen if redcost[resedge] < 0. : print 'correcting negative red. cost on resedge %s: %f' % ( resedge, redcost[resedge] ) # no augment, just saturate! flow[e] += dir * Delta Excess( excess, flow, network, supply, edge=e ) #print 'flow correction: %s' % repr( flow ) LinearizeCost( lincost, cost, flow, Delta, network, edge=e ) ResidualGraph( rgraph, flow, capacity, Delta, network, edge=e ) ReducedCost( redcost, lincost, potential, network, edge=e ) # at end of each stage, verify the optimality certificate (should be empty every time) CERT = { re : c for (re,c) in redcost.iteritems() if re in rgraph.edges() and c < 0. } print 'certificate, end stage ONE: %s' % repr( CERT ) #if len( CERT ) > 0 : print "STAGE ONE CERTIFICATE CORRUPT!" # am considering removing this assertion, but leaving the stage two one # could be running into problems where the functional form is defined beyond saturation bounds RELAXCERT = { re : c for (re,c) in redcost.iteritems() if re in rgraph.edges() and c < -PHASE_ERROR } if len( RELAXCERT ) > 0 : print RELAXCERT print "STAGE ONE CERTIFICATE CORRUPT!" print RELAXCERT assert len( RELAXCERT ) <= 0 """ Stage 2. """ # while there are imbalanced nodes while True : print 'flow: %s' % repr( flow ) #excess = Excess( flow, network, supply ) # last function that needs to be increment-ized #print 'excess: %s' % repr(excess) SS = [ i for i,ex in excess.iteritems() if ex >= Delta ] TT = [ i for i,ex in excess.iteritems() if ex <= -Delta ] print 'surplus nodes: %s' % repr( SS ) print 'deficit nodes: %s' % repr( TT ) if len( SS ) <= 0 or len( TT ) <= 0 : break s = SS[0] ; t = TT[0] print 'shall augment %s to %s' % ( repr(s), repr(t) ) #print 'potentials: %s' % repr( potential ) cert = { re : c for (re,c) in redcost.iteritems() if re in rgraph.edges() } #print 'reduced costs on res. graph, for shortest paths: %s' % repr( cert ) dist, upstream = Dijkstra( rgraph, redcost, s ) #print 'Dijkstra shortest path distances: %s' % repr( dist ) #print 'Dijkstra upstreams: %s' % repr( upstream ) # find shortest path w.r.t. reduced costs (just follow ancestry links to the root) try : PATH = [] ; j = t while j != s : # previously was "is"... that created problems non-deterministically e = upstream[j] i,_ = rgraph.endpoints(e) PATH.insert( 0, e ) j = i except Exception as e : e.rgraph = rgraph e.redcost = redcost e.s = s e.path_so_far = PATH e.j = j raise e print 'using path: %s' % repr( PATH ) # augment Delta flow along the path P for e,dir in PATH : flow[e] += dir * Delta Excess( excess, flow, network, supply, edge=e ) LinearizeCost( lincost, cost, flow, Delta, network, edge=e ) # all edges ResidualGraph( rgraph, flow, capacity, Delta, network, edge=e ) # update the potentials; # by connectivity, should touch *every* node for i in network.nodes() : potential[i] -= dist[i] # re-compute the reduced costs... everywhere? (all the potentials have changed) ReducedCost( redcost, lincost, potential, network ) # at end of each stage, verify the optimality certificate (should be empty every time) CERT = { re : c for (re,c) in redcost.iteritems() if re in rgraph.edges() and c < 0. } print 'certificate, end stage TWO: %s' % repr( CERT ) RELAXCERT = { re : c for (re,c) in redcost.iteritems() if re in rgraph.edges() and c < -PHASE_ERROR } if len( RELAXCERT ) > 0 : print RELAXCERT print "STAGE TWO CERTIFICATE CORRUPT!" assert len( RELAXCERT ) <= 0 # end the phase if Delta <= epsilon : break Delta = Delta / 2 return flow
""" convert linear instances on non-multi graphs to networkx format for comparison against nx.min_cost_flow() algorithm """ def mincostflow_nx( network, capacity, supply, weight ) : digraph = nx.DiGraph() for i in network.nodes() : digraph.add_node( i, demand=-supply.get(i, 0. ) ) for e in network.edges() : i,j = network.endpoints(e) digraph.add_edge( i, j, capacity=capacity.get(e, np.Inf ), weight=weight.get(e, 1. ) ) return digraph g = mygraph() if False : g.add_edge( 'a', 0, 1 ) g.add_edge( 'b', 1, 2 ) g.add_edge( 'c', 2, 3 ) g.add_edge( 'd', 3, 0 ) u = { e : 10. for e in g.edges() } supply = { 0 : 1., 1 : 2., 2 : -3., 3 : 0. } c = { 'a' : 10., 'b' : 5., 'c' : 1., 'd' : .5 } else : u = {} c = {} s = {}
def SOLVER( roadnet, surplus, measure_dict ) : network = mygraph() capacity = {} supply = { i : 0. for i in roadnet.nodes() } cost = {} # functions # oneway_offset = {} # for one-way roads for i,j, road, data in roadnet.edges_iter( keys=True, data=True ) : supply[j] += surplus[road] measure = measure_dict[road] fobj = OBJECTIVE_FUNC( measure ) # edge construction if data.get( 'oneway', False ) : # if one-way road # record minimum allowable flow on road zmin = -measure.min_key() # i.e., z + min key of measure >= 0 oneway_offset[road] = zmin # create a 'bias point' supply[i] -= zmin supply[j] += zmin # shift and record the cost function on only a forward edge fobj_offset = offsetWrapper( fobj, zmin ) network.add_edge( road, i, j ) cost[ road ] = fobj_offset else : # if bi-directional road... instantiate pair of edges #cc = roadbm.costWrapper( cost_data ) n_fobj = negativeWrapper( fobj ) # won't have to worry about the C(0) offset network.add_edge( (road,+1), i, j ) cost[ (road,+1) ] = fobj # network.add_edge( (road,-1), j, i ) cost[ (road,-1) ] = n_fobj """ compute the width U of the first cvxcost algorithm phase; a bound on the optimal flow on any edge; Logic: there cannot be more flow on a given road in the graph than there are total intervals between levels in the network (Proof Sketch): 1. U <= M ; 2. (Prove...) Given any matching instance which induces a measure network w/ U' total intervals between levels, a new matching instance realizing the same measure network can be constructed on just U' points in each set """ # safe-ish... #U = sum([ len(m) + 1 for m in measure_dict.values() ]) # below is almost certainly just as good a bound, but I'm a scaredy-cat U = sum([ len(m) - 1 for m in measure_dict.values() ]) f = MinConvexCostFlow( network, {}, supply, cost, U ) flow = {} for i, j, road in roadnet.edges_iter( keys=True ) : if road in oneway_offset : flow[road] = f[road] + oneway_offset[road] else : flow[road] = f[(road,+1)] - f[(road,-1)] flow[road] = int( flow[road] ) #print flow return flow