Module coinor.gimpy.examples.simplex_test
tests network simplex method and cycle canceling method of GIMPy.
Expand source code
'''
tests network simplex method and cycle canceling method of GIMPy.
'''
from __future__ import print_function
from builtins import str
from builtins import range
from coinor.gimpy import Graph, DIRECTED_GRAPH
from random import seed, randint, random
from pulp import LpProblem, LpVariable, LpMinimize, lpSum, value, GLPK
import time
# global variables, experimental parameters
capacity_range = (10,20)
cost_range = (30,50)
demand_range = (10,15)
demand_numnodes = 3
supply_numnodes = 2
numnodes = 6
density = 0.4
#testing
# nodes 10, 15, 20, 25, 30
# arcs sparse 20, 30, 45, 65, 90
# arcs dense 35, 80, 150, 200, 300
def get_solution(g):
'''
returns optimal solution (optimal flows) of min cost flow problem for a
given graph instance g. return type is dictionary, keys are edge keys values
are int.
'''
sol = {}
for e in g.get_edge_list():
sol[e] = g.get_edge_attr(e[0],e[1],'flow')
return sol
def generate_graph(seed_i):
g = mGraph(type=DIRECTED_GRAPH, splines='true', layout = 'dot')
#g = mGraph(type=DIRECTED_GRAPH)
seed(seed_i)
for i in range(numnodes):
for j in range(numnodes):
if i==j:
continue
if (i, j) in g.get_edge_list():
continue
if (j, i) in g.get_edge_list():
continue
if random() < density:
cap = randint(capacity_range[0], capacity_range[1])
cos = randint(cost_range[0], cost_range[1])
g.add_edge(i, j, cost=cos, capacity=cap)
# set supply/demand
# select random demand_numnodes many nodes
demand_node = {}
while len(demand_node) < demand_numnodes:
n = randint(0,numnodes-1)
if n in demand_node:
continue
demand_node[n] = 0
# select random supply_numnodes many nodes (different than above)
supply_node = {}
while len(supply_node) < supply_numnodes:
n = randint(0,numnodes-1)
if n in demand_node or n in supply_node:
continue
supply_node[n] = 0
# set demand amounts
total_demand = 0
for n in demand_node:
demand_node[n] = randint(demand_range[0], demand_range[1])
total_demand += demand_node[n]
# set supply amounts
total_supply = 0
for n in supply_node:
supply_node[n] = randint(demand_range[0], demand_range[1])
total_supply += supply_node[n]
if total_demand > total_supply:
# this line may create random behavior, because of dictionary query
for n in supply_node:
excess = total_demand-total_supply
supply_node[n] += excess
break
elif total_demand < total_supply:
for n in demand_node:
excess = total_supply-total_demand
demand_node[n] += excess
break
# set demand attributes
for n in g.get_node_list():
if n in demand_node:
g.set_node_attr(n, 'demand', -1*demand_node[n])
elif n in supply_node:
g.set_node_attr(n, 'demand', supply_node[n])
else:
g.set_node_attr(n, 'demand', 0)
return g, demand_node, supply_node
def get_obj_value(g):
el = g.get_edge_list()
cost = 0
for e in el:
cost_e = g.get_edge_attr(e[0], e[1], 'cost')
flow_e = g.get_edge_attr(e[0], e[1], 'flow')
cost += cost_e*flow_e
return cost
def solve(g):
el = g.get_edge_list()
nl = g.get_node_list()
p = LpProblem('min_cost', LpMinimize)
capacity = {}
cost = {}
demand = {}
x = {}
for e in el:
capacity[e] = g.get_edge_attr(e[0], e[1], 'capacity')
cost[e] = g.get_edge_attr(e[0], e[1], 'cost')
for i in nl:
demand[i] = g.get_node_attr(i, 'demand')
for e in el:
x[e] = LpVariable("x"+str(e), 0, capacity[e])
# add obj
objective = lpSum (cost[e]*x[e] for e in el)
p += objective
# add constraints
for i in nl:
out_neig = g.get_out_neighbors(i)
in_neig = g.get_in_neighbors(i)
p += lpSum(x[(i,j)] for j in out_neig) -\
lpSum(x[(j,i)] for j in in_neig)==demand[i]
p.solve()
return x, value(objective)
class mGraph(Graph):
def __init__(self, **attrs):
Graph.__init__(self, **attrs)
def network_simplex(self, display, pivot, root):
'''
API:
network_simplex(self, display, pivot)
Description:
Solves minimum cost feasible flow problem using network simplex
algorithm. It is recommended to use min_cost_flow(algo='simplex')
instead of using network_simplex() directly. Returns True when an
optimal solution is found, returns False otherwise. 'flow' attribute
values of arcs should be considered as junk when returned False.
Pre:
(1) check Pre section of min_cost_flow()
Inputs:
pivot: specifies pivot rule. Check min_cost_flow()
display: 'off' for no display, 'matplotlib' for live update of
spanning tree.
Post:
(1) Changes 'flow' attribute of edges.
Return:
Returns True when an optimal solution is found, returns
False otherwise.
'''
# ==== determine an initial tree structure (T,L,U)
# find a feasible flow
iter = 0
if not self.find_feasible_flow():
return (False, iter)
t = self.simplex_find_tree()
# mark spanning tree arcs
self.simplex_mark_st_arcs(t)
self.set_display_mode(display)
# display initial spanning tree
self.display()
self.set_display_mode('off')
# set predecessor, depth and thread indexes
t.simplex_search(root, 1)
# compute potentials
self.simplex_compute_potentials(t, root)
# while some nontree arc violates optimality conditions
while not self.simplex_optimal(t):
self.set_display_mode(display)
self.display()
self.set_display_mode('off')
# select an entering arc (k,l)
(k,l) = self.simplex_select_entering_arc(t, pivot)
self.simplex_mark_entering_arc(k, l)
# determine leaving arc
((p,q), capacity, cycle)=self.simplex_determine_leaving_arc(t,k,l)
# mark leaving arc
self.simplex_mark_leaving_arc(p, q)
self.set_display_mode(display)
# display after marking leaving arc
self.display()
self.simplex_mark_st_arcs(t)
self.display()
self.set_display_mode('off')
# remove arc
self.simplex_remove_arc(t, p, q, capacity, cycle)
# set predecessor, depth and thread indexes
t.simplex_search(root, 1)
# compute potentials
self.simplex_compute_potentials(t, root)
return (True, iter)
if __name__=='__main__':
print()
dantzig_file = open('dantzig_results.txt', 'w')
eligible_file = open('eligible_results.txt', 'w')
dantzig_file.write('# seed, simplex obj value, pulp obj value,'+\
' time, iteration\n')
eligible_file.write('# seed, simplex obj value, pulp obj value,'+\
' time, iteration\n')
for seed_i in range(0,100):
g, d, s = generate_graph(seed_i)
root = 0
#==========solve using simplex, first eligible
start = time.perf_counter()
tup = g.network_simplex('off', 'first_eligible', root)
elapsed_time = time.perf_counter() - start
if tup[0]:
sol = get_solution(g)
eligible_obj_value = get_obj_value(g)
else:
# skip infeasible ones
continue
# set obj value to 0 if the problem is infeasible.
eligible_obj_value = 0.0
#==========solve using pulp
x, pulp_obj_value = solve(g)
pulp_obj_value = int(pulp_obj_value)
# check success of simplex with first eligible
flag = True
if eligible_obj_value != pulp_obj_value:
flag = False
e_line = str(seed_i).ljust(5)+str(eligible_obj_value).ljust(6)+\
str(pulp_obj_value).ljust(6)+str(elapsed_time).ljust(7)+str(tup[1])+'\n'
eligible_file.write(e_line)
#==========solve using simplex, dantzig
start = time.perf_counter()
tup = g.network_simplex('off', 'dantzig', root)
elapsed_time = time.perf_counter() - start
if tup[0]:
sol = get_solution(g)
dantzig_obj_value = get_obj_value(g)
else:
# set obj value to 0 if the problem is infeasible.
simplex_obj_value = 0.0
# write to file
d_line = str(seed_i).ljust(5)+str(dantzig_obj_value).ljust(6)+\
str(pulp_obj_value).ljust(6)+str(elapsed_time).ljust(7)+str(tup[1])+'\n'
dantzig_file.write(d_line)
flag = True
if dantzig_obj_value != pulp_obj_value:
flag = False
dantzig_file.close()
eligible_file.close()
if flag:
print('All problems solved accurately.')
Functions
def generate_graph(seed_i)
-
Expand source code
def generate_graph(seed_i): g = mGraph(type=DIRECTED_GRAPH, splines='true', layout = 'dot') #g = mGraph(type=DIRECTED_GRAPH) seed(seed_i) for i in range(numnodes): for j in range(numnodes): if i==j: continue if (i, j) in g.get_edge_list(): continue if (j, i) in g.get_edge_list(): continue if random() < density: cap = randint(capacity_range[0], capacity_range[1]) cos = randint(cost_range[0], cost_range[1]) g.add_edge(i, j, cost=cos, capacity=cap) # set supply/demand # select random demand_numnodes many nodes demand_node = {} while len(demand_node) < demand_numnodes: n = randint(0,numnodes-1) if n in demand_node: continue demand_node[n] = 0 # select random supply_numnodes many nodes (different than above) supply_node = {} while len(supply_node) < supply_numnodes: n = randint(0,numnodes-1) if n in demand_node or n in supply_node: continue supply_node[n] = 0 # set demand amounts total_demand = 0 for n in demand_node: demand_node[n] = randint(demand_range[0], demand_range[1]) total_demand += demand_node[n] # set supply amounts total_supply = 0 for n in supply_node: supply_node[n] = randint(demand_range[0], demand_range[1]) total_supply += supply_node[n] if total_demand > total_supply: # this line may create random behavior, because of dictionary query for n in supply_node: excess = total_demand-total_supply supply_node[n] += excess break elif total_demand < total_supply: for n in demand_node: excess = total_supply-total_demand demand_node[n] += excess break # set demand attributes for n in g.get_node_list(): if n in demand_node: g.set_node_attr(n, 'demand', -1*demand_node[n]) elif n in supply_node: g.set_node_attr(n, 'demand', supply_node[n]) else: g.set_node_attr(n, 'demand', 0) return g, demand_node, supply_node
def get_obj_value(g)
-
Expand source code
def get_obj_value(g): el = g.get_edge_list() cost = 0 for e in el: cost_e = g.get_edge_attr(e[0], e[1], 'cost') flow_e = g.get_edge_attr(e[0], e[1], 'flow') cost += cost_e*flow_e return cost
def get_solution(g)
-
returns optimal solution (optimal flows) of min cost flow problem for a given graph instance g. return type is dictionary, keys are edge keys values are int.
Expand source code
def get_solution(g): ''' returns optimal solution (optimal flows) of min cost flow problem for a given graph instance g. return type is dictionary, keys are edge keys values are int. ''' sol = {} for e in g.get_edge_list(): sol[e] = g.get_edge_attr(e[0],e[1],'flow') return sol
def random()
-
random() -> x in the interval [0, 1).
def solve(g)
-
Expand source code
def solve(g): el = g.get_edge_list() nl = g.get_node_list() p = LpProblem('min_cost', LpMinimize) capacity = {} cost = {} demand = {} x = {} for e in el: capacity[e] = g.get_edge_attr(e[0], e[1], 'capacity') cost[e] = g.get_edge_attr(e[0], e[1], 'cost') for i in nl: demand[i] = g.get_node_attr(i, 'demand') for e in el: x[e] = LpVariable("x"+str(e), 0, capacity[e]) # add obj objective = lpSum (cost[e]*x[e] for e in el) p += objective # add constraints for i in nl: out_neig = g.get_out_neighbors(i) in_neig = g.get_in_neighbors(i) p += lpSum(x[(i,j)] for j in out_neig) -\ lpSum(x[(j,i)] for j in in_neig)==demand[i] p.solve() return x, value(objective)
Classes
class mGraph (**attrs)
-
Graph class, implemented using adjacency list. See GIMPy README for more information.
API: init(self, **attrs) Description: Graph class constructor. Sets attributes using argument.
Input
**attrs: Graph attributes.
Post
Sets following attributes using **attrs; self.attr, self.graph_type. Creates following initial attributes; self.neighbors, self.in_neighbors, self.nodes, self.out_neighbors, self.cluster
Expand source code
class mGraph(Graph): def __init__(self, **attrs): Graph.__init__(self, **attrs) def network_simplex(self, display, pivot, root): ''' API: network_simplex(self, display, pivot) Description: Solves minimum cost feasible flow problem using network simplex algorithm. It is recommended to use min_cost_flow(algo='simplex') instead of using network_simplex() directly. Returns True when an optimal solution is found, returns False otherwise. 'flow' attribute values of arcs should be considered as junk when returned False. Pre: (1) check Pre section of min_cost_flow() Inputs: pivot: specifies pivot rule. Check min_cost_flow() display: 'off' for no display, 'matplotlib' for live update of spanning tree. Post: (1) Changes 'flow' attribute of edges. Return: Returns True when an optimal solution is found, returns False otherwise. ''' # ==== determine an initial tree structure (T,L,U) # find a feasible flow iter = 0 if not self.find_feasible_flow(): return (False, iter) t = self.simplex_find_tree() # mark spanning tree arcs self.simplex_mark_st_arcs(t) self.set_display_mode(display) # display initial spanning tree self.display() self.set_display_mode('off') # set predecessor, depth and thread indexes t.simplex_search(root, 1) # compute potentials self.simplex_compute_potentials(t, root) # while some nontree arc violates optimality conditions while not self.simplex_optimal(t): self.set_display_mode(display) self.display() self.set_display_mode('off') # select an entering arc (k,l) (k,l) = self.simplex_select_entering_arc(t, pivot) self.simplex_mark_entering_arc(k, l) # determine leaving arc ((p,q), capacity, cycle)=self.simplex_determine_leaving_arc(t,k,l) # mark leaving arc self.simplex_mark_leaving_arc(p, q) self.set_display_mode(display) # display after marking leaving arc self.display() self.simplex_mark_st_arcs(t) self.display() self.set_display_mode('off') # remove arc self.simplex_remove_arc(t, p, q, capacity, cycle) # set predecessor, depth and thread indexes t.simplex_search(root, 1) # compute potentials self.simplex_compute_potentials(t, root) return (True, iter)
Ancestors
Methods
def network_simplex(self, display, pivot, root)
-
Api
network_simplex(self, display, pivot)
Description
Solves minimum cost feasible flow problem using network simplex algorithm. It is recommended to use min_cost_flow(algo='simplex') instead of using network_simplex() directly. Returns True when an optimal solution is found, returns False otherwise. 'flow' attribute values of arcs should be considered as junk when returned False.
Pre
(1) check Pre section of min_cost_flow()
Inputs
pivot: specifies pivot rule. Check min_cost_flow() display: 'off' for no display, 'matplotlib' for live update of spanning tree.
Post
(1) Changes 'flow' attribute of edges.
Return
Returns True when an optimal solution is found, returns False otherwise.
Expand source code
def network_simplex(self, display, pivot, root): ''' API: network_simplex(self, display, pivot) Description: Solves minimum cost feasible flow problem using network simplex algorithm. It is recommended to use min_cost_flow(algo='simplex') instead of using network_simplex() directly. Returns True when an optimal solution is found, returns False otherwise. 'flow' attribute values of arcs should be considered as junk when returned False. Pre: (1) check Pre section of min_cost_flow() Inputs: pivot: specifies pivot rule. Check min_cost_flow() display: 'off' for no display, 'matplotlib' for live update of spanning tree. Post: (1) Changes 'flow' attribute of edges. Return: Returns True when an optimal solution is found, returns False otherwise. ''' # ==== determine an initial tree structure (T,L,U) # find a feasible flow iter = 0 if not self.find_feasible_flow(): return (False, iter) t = self.simplex_find_tree() # mark spanning tree arcs self.simplex_mark_st_arcs(t) self.set_display_mode(display) # display initial spanning tree self.display() self.set_display_mode('off') # set predecessor, depth and thread indexes t.simplex_search(root, 1) # compute potentials self.simplex_compute_potentials(t, root) # while some nontree arc violates optimality conditions while not self.simplex_optimal(t): self.set_display_mode(display) self.display() self.set_display_mode('off') # select an entering arc (k,l) (k,l) = self.simplex_select_entering_arc(t, pivot) self.simplex_mark_entering_arc(k, l) # determine leaving arc ((p,q), capacity, cycle)=self.simplex_determine_leaving_arc(t,k,l) # mark leaving arc self.simplex_mark_leaving_arc(p, q) self.set_display_mode(display) # display after marking leaving arc self.display() self.simplex_mark_st_arcs(t) self.display() self.set_display_mode('off') # remove arc self.simplex_remove_arc(t, p, q, capacity, cycle) # set predecessor, depth and thread indexes t.simplex_search(root, 1) # compute potentials self.simplex_compute_potentials(t, root) return (True, iter)
Inherited members
Graph
:add_edge
add_node
augment_cycle
bfs
check_edge
create
create_cluster
create_residual_graph
cycle_canceling
del_edge
del_node
dfs
display
edge_to_string
fifo_label_correcting
find_cycle_capacity
find_feasible_flow
floyd_warshall
floyd_warshall_get_cycle
floyd_warshall_get_path
get_degrees
get_diameter
get_edge_attr
get_edge_cost
get_edge_list
get_edge_num
get_in_degrees
get_in_neighbors
get_layout
get_negative_cycle
get_neighbors
get_node
get_node_attr
get_node_list
get_node_num
get_out_degrees
get_out_neighbors
get_simplex_solution_graph
label_components
label_correcting_check_cycle
label_correcting_get_cycle
label_strong_component
max_flow
max_flow_preflowpush
min_cost_flow
minimum_spanning_tree_kruskal
minimum_spanning_tree_prim
page_rank
print_flow
process_edge_dijkstra
process_edge_flow
process_edge_prim
process_edge_search
process_node_search
random
relabel
search
set_display_mode
set_edge_attr
set_layout
set_node_attr
show_flow
simplex_augment_cycle
simplex_compute_potentials
simplex_connect
simplex_determine_leaving_arc
simplex_find_cycle
simplex_find_tree
simplex_identify_cycle
simplex_mark_entering_arc
simplex_mark_leaving_arc
simplex_mark_st_arcs
simplex_optimal
simplex_redraw
simplex_remove_arc
simplex_search
simplex_select_entering_arc
strong_connect
tarjan
to_string
write