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_edgeadd_nodeaugment_cyclebfscheck_edgecreatecreate_clustercreate_residual_graphcycle_cancelingdel_edgedel_nodedfsdisplayedge_to_stringfifo_label_correctingfind_cycle_capacityfind_feasible_flowfloyd_warshallfloyd_warshall_get_cyclefloyd_warshall_get_pathget_degreesget_diameterget_edge_attrget_edge_costget_edge_listget_edge_numget_in_degreesget_in_neighborsget_layoutget_negative_cycleget_neighborsget_nodeget_node_attrget_node_listget_node_numget_out_degreesget_out_neighborsget_simplex_solution_graphlabel_componentslabel_correcting_check_cyclelabel_correcting_get_cyclelabel_strong_componentmax_flowmax_flow_preflowpushmin_cost_flowminimum_spanning_tree_kruskalminimum_spanning_tree_primpage_rankprint_flowprocess_edge_dijkstraprocess_edge_flowprocess_edge_primprocess_edge_searchprocess_node_searchrandomrelabelsearchset_display_modeset_edge_attrset_layoutset_node_attrshow_flowsimplex_augment_cyclesimplex_compute_potentialssimplex_connectsimplex_determine_leaving_arcsimplex_find_cyclesimplex_find_treesimplex_identify_cyclesimplex_mark_entering_arcsimplex_mark_leaving_arcsimplex_mark_st_arcssimplex_optimalsimplex_redrawsimplex_remove_arcsimplex_searchsimplex_select_entering_arcstrong_connecttarjanto_stringwrite