def fw_heterogeneous_2(graphs, demands, past=10, max_iter=100, eps=1e-8, q=50, display=0, stop=1e-8): ''' Frank-Wolfe algorithm on the heterogeneous game given a list of graphs in the format g = [[link_id from to a0 a1 a2 a3 a4]] and demand in the format d = [[o d flow]] ''' assert past <= q, "'q' must be bigger or equal to 'past'" # construct graph and demand objects suiteable for AoN_igraph gs = [construct_igraph(graph) for graph in graphs] ods = [construct_od(demand) for demand in demands] # construct empty vector to be filled in with values links = graphs[0].shape[0] types = len(graphs) # initial flow assignment is null f = np.zeros(links * types, dtype="float64") fs = np.zeros((links * types, past), dtype="float64") L = np.zeros(links * types, dtype="float64") grad = np.zeros(links * types, dtype="float64") L2 = np.zeros(links * types, dtype="float64") grad2 = np.zeros(links * types, dtype="float64") error = 'N/A' # compute re-normalization constant K = sum([total_free_flow_cost(g, od) for g, od in zip(gs, ods)]) if K < eps: K = sum([np.sum(demand[:, 2]) for demand in demands]) elif display >= 1: print 'average free-flow travel time', \ K / sum([np.sum(demand[:, 2]) for demand in demands]) # compute iterations for i in range(max_iter): if display >= 1: print 'iteration: {}, error: {}'.format(i + 1, error) # construct weighted graph with latest flow assignment # print 'f', f # print 'reshape', np.reshape(f,(links,types)) total_f = np.sum(np.reshape(f, (types, links)).T, 1) # print 'total flow', total_f for j, (graph, g, od) in enumerate(zip(graphs, gs, ods)): l, gr = search_direction(total_f, graph, g, od) L[(j * links):((j + 1) * links)] = l grad[(j * links):((j + 1) * links)] = gr # print 'L', L # print 'grad', grad fs[:, i % past] = L w = L - f if i >= 1: error = -grad.dot(w) / K # if error < stop and error > 0.0: if error < stop: if display >= 1: print 'stop with error: {}'.format(error) return np.reshape(f, (types, links)).T if i > q: # step 3 of Fukushima v = np.sum(fs, axis=1) / min(past, i + 1) - f norm_v = np.linalg.norm(v, 1) if norm_v < eps: if display >= 1: print 'stop with norm_v: {}'.format(norm_v) return np.reshape(f, (types, links)).T norm_w = np.linalg.norm(w, 1) if norm_w < eps: if display >= 1: print 'stop with norm_w: {}'.format(norm_w) return np.reshape(f, (types, links)).T # step 4 of Fukushima gamma_1 = grad.dot(v) / norm_v gamma_2 = grad.dot(w) / norm_w if gamma_2 > -eps: if display >= 1: print 'stop with gamma_2: {}'.format(gamma_2) return np.reshape(f, (types, links)).T d = v if gamma_1 < gamma_2 else w # step 5 of Fukushima s = line_search( lambda a: merit(f + a * d, graphs, gs, ods, L2, grad2)) # print 'step', s if s < eps: if display >= 1: print 'stop with step_size: {}'.format(s) return np.reshape(f, (types, links)).T f = f + s * d else: f = f + 2. * w / (i + 2.) return np.reshape(f, (types, links)).T
def test_line_search(self): def f(x): return (x-0.3)**2 out = line_search(f) self.assertTrue(np.linalg.norm(out - .3) < 1e-5)
def fw_heterogeneous_2(graphs, demands, past=10, max_iter=100, eps=1e-8, q=50, \ display=0, stop=1e-8): ''' Frank-Wolfe algorithm on the heterogeneous game given a list of graphs in the format g = [[link_id from to a0 a1 a2 a3 a4]] and demand in the format d = [[o d flow]] ''' assert past <= q, "'q' must be bigger or equal to 'past'" # construct graph and demand objects suiteable for AoN_igraph gs = [construct_igraph(graph) for graph in graphs] ods = [construct_od(demand) for demand in demands] # construct empty vector to be filled in with values links = graphs[0].shape[0] types = len(graphs) f = np.zeros(links*types,dtype="float64") # initial flow assignment is null fs = np.zeros((links*types,past),dtype="float64") L = np.zeros(links*types,dtype="float64") grad = np.zeros(links*types,dtype="float64") L2 = np.zeros(links*types,dtype="float64") grad2 = np.zeros(links*types,dtype="float64") # compute re-normalization constant K = sum([total_free_flow_cost(g, od) for g,od in zip(gs, ods)]) if K < eps: K = sum([np.sum(demand[:,2]) for demand in demands]) elif display >= 1: print 'average free-flow travel time', \ K / sum([np.sum(demand[:,2]) for demand in demands]) # compute iterations for i in range(max_iter): if display >= 1: if i <= 1: print 'iteration: {}'.format(i+1) else: print 'iteration: {}, error: {}'.format(i+1, error) # construct weighted graph with latest flow assignment #print 'f', f #print 'reshape', np.reshape(f,(links,types)) total_f = np.sum(np.reshape(f,(types,links)).T,1) #print 'total flow', total_f for j, (graph, g, od) in enumerate(zip(graphs, gs, ods)): l, gr = search_direction(total_f, graph, g, od) L[(j*links) : ((j+1)*links)] = l grad[(j*links) : ((j+1)*links)] = gr #print 'L', L #print 'grad', grad fs[:,i%past] = L w = L - f if i >= 1: error = -grad.dot(w) / K # if error < stop and error > 0.0: if error < stop: if display >= 1: print 'stop with error: {}'.format(error) return np.reshape(f,(types,links)).T if i > q: # step 3 of Fukushima v = np.sum(fs,axis=1) / min(past,i+1) - f norm_v = np.linalg.norm(v,1) if norm_v < eps: if display >= 1: print 'stop with norm_v: {}'.format(norm_v) return np.reshape(f,(types,links)).T norm_w = np.linalg.norm(w,1) if norm_w < eps: if display >= 1: print 'stop with norm_w: {}'.format(norm_w) return np.reshape(f,(types,links)).T # step 4 of Fukushima gamma_1 = grad.dot(v) / norm_v gamma_2 = grad.dot(w) / norm_w if gamma_2 > -eps: if display >= 1: print 'stop with gamma_2: {}'.format(gamma_2) return np.reshape(f,(types,links)).T d = v if gamma_1 < gamma_2 else w # step 5 of Fukushima s = line_search(lambda a: merit(f+a*d, graphs, gs, ods, L2, grad2)) # print 'step', s if s < eps: if display >= 1: print 'stop with step_size: {}'.format(s) return np.reshape(f,(types,links)).T f = f + s*d else: f = f + 2. * w/(i+2.) return np.reshape(f,(types,links)).T
def fw_heterogeneous_2(graphs, demands, past=10, max_iter=100, eps=1e-8, q=50, \ display=0, stop=1e-8): ''' Frank-Wolfe algorithm on the heterogeneous game given a list of graphs in the format g = [[link_id from to a0 a1 a2 a3 a4]] and demand in the format d = [[o d flow]] ''' assert past <= q, "'q' must be bigger or equal to 'past'" # construct graph and demand objects suiteable for AoN_igraph gs = [construct_igraph(graph) for graph in graphs] ods = [construct_od(demand) for demand in demands] # construct empty vector to be filled in with values links = graphs[0].shape[0] types = len(graphs) f = np.zeros(links * types, dtype="float64") # initial flow assignment is null fs = np.zeros((links * types, past), dtype="float64") L = np.zeros(links * types, dtype="float64") grad = np.zeros(links * types, dtype="float64") L2 = np.zeros(links * types, dtype="float64") grad2 = np.zeros(links * types, dtype="float64") h = defaultdict(np.float64) # initial path flow assignment is null hs = defaultdict( lambda: [0. for _ in range(past)]) # initial path flow assignment is null # compute re-normalization constant K = sum([total_free_flow_cost(g, od) for g, od in zip(gs, ods)]) if K < eps: K = sum([np.sum(demand[:, 2]) for demand in demands]) elif display >= 1: print 'average free-flow travel time', \ K / sum([np.sum(demand[:,2]) for demand in demands]) # compute iterations start_time = timeit.default_timer() for i in range(max_iter): if display >= 1: if i <= 1: print 'iteration: {}'.format(i + 1) else: print 'iteration: {}, error: {}'.format(i + 1, error) # construct weighted graph with latest flow assignment #print 'f', f #print 'reshape', np.reshape(f,(links,types)) total_f = np.sum(np.reshape(f, (types, links)).T, 1) #print 'total flow', total_f L, grad, path_flows = search_direction_multi(f, graphs, gs, ods, L, grad) #print 'L', L #print 'grad', grad fs[:, i % past] = L for k in set(h.keys()).union(set(path_flows.keys())): hs[k][i % past] = path_flows[k] w = L - f w_h = defaultdict(np.float64) for k in set(h.keys()).union(set(path_flows.keys())): w_h[k] = path_flows[k] - h[k] if i >= 1: error = -grad.dot(w) / K # if error < stop and error > 0.0: if error < stop: if display >= 1: print 'stop with error: {}'.format(error) return np.reshape(f, (types, links)).T if i > q: # step 3 of Fukushima v = np.sum(fs, axis=1) / min(past, i + 1) - f v_h = np.defaultdict(np.float64) for k in set(hs.keys()).union(set(path_flows.keys())): v_h[k] = sum(hs[k]) / min(past, i + 1) - h[k] norm_v = np.linalg.norm(v, 1) if norm_v < eps: if display >= 1: print 'stop with norm_v: {}'.format(norm_v) return np.reshape(f, (types, links)).T norm_w = np.linalg.norm(w, 1) if norm_w < eps: if display >= 1: print 'stop with norm_w: {}'.format(norm_w) return np.reshape(f, (types, links)).T # step 4 of Fukushima gamma_1 = grad.dot(v) / norm_v gamma_2 = grad.dot(w) / norm_w if gamma_2 > -eps: if display >= 1: print 'stop with gamma_2: {}'.format(gamma_2) return np.reshape(f, (types, links)).T d = v if gamma_1 < gamma_2 else w d_h = v_h if gamma_1 < gamma_2 else w_h # step 5 of Fukushima s = line_search( lambda a: merit(f + a * d, graphs, gs, ods, L2, grad2)) # print 'step', s if s < eps: if display >= 1: print 'stop with step_size: {}'.format(s) return np.reshape(f, (types, links)).T f = f + s * d for k in set(hs.keys()).union(set(path_flows.keys())): h[k] = h[k] + s * d_h[k] else: f = f + 2. * w / (i + 2.) for k in set(h.keys()).union(set(path_flows.keys())): h[k] = h[k] + 2. * (w_h[k]) / (i + 2.) print 'iteration', i print 'time(sec):', timeit.default_timer() - start_time print 'num path flows:', len(h) f_h = np.zeros(graph.shape[0], dtype='float64') # initial flow assignment is null for k in h: flow = h[k] for link in k[2]: f_h[link] += flow print "path vs link flow diff:", np.sum( np.abs(f_h - np.sum(np.reshape(f, (types, links)).T, 1))), f.shape # find how many paths each od pair really has od_paths = defaultdict(int) most_paths = 0 for k in h.keys(): od_paths[(k[:2])] += 1 most_paths = max(most_paths, od_paths[(k[:2])]) path_counts = [0 for i in range(most_paths + 1)] for k in od_paths.keys(): path_counts[od_paths[k]] += 1 for i in range(len(path_counts)): print i, path_counts[i] return np.reshape(f, (types, links)).T