How to include take the absolute value in objective function solving using pyomo and glpk - pyomo

I have to find the optimum cost of building links between nodes. In my objective function, I am trying to minimise the cost. The problem can be solved to determine the variable, however the optimal value of my cost is incorrect as I want it to take the absolute value of each cost. How can I modify my codes as I cannot use abs() in the objective function?
cost+=(model.a[i,k]-model.a[j,k])model.cmodel.d[i,j].
This value can be negative if model.a[j,k]=1 or positive if model.a[i,k]=1
from pyomo.environ import *
# Creation of a Concrete Model
model = ConcreteModel()
# Sets
model.i = Set(initialize=[1,2,3,4,5,6,7,8,9,10,11,12,13], doc='Nodes')
model.k = Set(initialize=['Orange','SFR', 'Bouygues'], doc='Companies')
# Parameters
model.c = Param(initialize=25, doc='Cost of transforming an existing link into a backbone link in euro/km')
links={
(1, 2) : 1.8,
(1, 7) : 1,
(1, 13) : 5.4,
(2, 8) : 2.3,
(2, 3) : 1.7,
(2, 5) : 7,
(2, 7) : 2,
(2, 12) : 3,
(3, 4) : 2,
(3, 10) : 6.5,
(4, 5) : 1,
(4, 6) : 2,
(5, 8) : 5,
(5, 10) : 1,
(5, 11) : 1.5,
(6, 11) : 2.1,
(7, 12) : 2,
(8, 9) : 2,
(8, 13) : 0.7,
(9, 10) : 1.1,
(10, 11) : 1,
(12, 13) : 2.5,
}
model.d=Param(model.i, model.i,default=0, initialize=links, doc='distance in 10 km between nodes')
# Variables
model.a = Var(model.i, model.k, within=Binary, doc='Binary variable indicating whether node i belongs to company k (0 if it does not belong and 1 if it belongs)')
#Contraints#
def allocation_rule(model, i):
return sum(model.a[i,k] for k in model.k) == 1
model.allocation = Constraint(model.i, rule=allocation_rule, doc='Each node can only belong to one company')
def minimum_rule(model, k):
return sum(model.a[i,k] for i in model.i) >= 2
model.minimum = Constraint(model.k, rule=minimum_rule, doc='Each company must have at least 2 nodes')
#objective
def totalcost(model):
cost=0
for i in model.i:
for j in model.i:
if model.d[i,j]!=0:
for k in model.k:
cost+=(model.a[i,k]-model.a[j,k])*model.c*model.d[i,j]
return cost
model.z = Objective(rule=totalcost, sense=minimize, doc='Minimize the cost of implementing a backbone connecting the three sub-networks')
def total(model):
return model.cost_postive-model.cost_negative
## Display of the output ##
optimizer = SolverFactory("glpk",executable='/usr/bin/glpsol') #creates an optimizer object that uses the glpk package installed to your usr/bin.
optimizer.solve(model) #tells your optimizer to solve the model object
model.display()
I have tried using the cost+=abs((model.a[i,k]-model.a[j,k])model.cmodel.d[i,j]) but this makes the problem non-linear so it cannot be solved.
edited to introduce a new variable p, and added 2 constraints to p>=(model.a[i,k]-model.a[j,k])model.cmodel.d[i,j]) and
p>=-(model.a[i,k]-model.a[j,k])model.cmodel.d[i,j]). However, it returns with error: ERROR:pyomo.core:Rule failed for Param 'd' with index (1, 2):
from pyomo.environ import *
# Creation of a Concrete Model
model = ConcreteModel()
# Sets
model.i = Set(initialize=[1,2,3,4,5,6,7,8,9,10,11,12,13],
doc='Nodes')
model.i = Set(initialize=['Orange','SFR', 'Bouygues'],
doc='Companies')
# Parameters
model.c = Param(initialize=25, doc='Cost of transforming an
existing link into a backbone link in euro/km')
links={
(1, 2) : 1.8,
(1, 7) : 1,
(2, 3) : 1.7,
(2, 5) : 7,
(2, 7) : 2,
(2, 12) : 3,
(3, 4) : 2,
(3, 10) : 6.5,
(4, 5) : 1,
(4, 6) : 2,
(5, 8) : 5,
(5, 10) : 1,
(5, 11) : 1.5,
(6, 11) : 2.1,
(7, 12) : 2,
(8, 9) : 2,
(8, 13) : 0.7,
(9, 10) : 1.1,
(10, 11) : 1,
(12, 13) : 2.5,
(1, 13) : 5.4,
(2, 8) : 2.3,
}
model.d=Param(model.i, model.i,default=0, initialize=links, doc='distance in 10 km between nodes')
# Variables
model.a = Var(model.i, model.k, within=Binary, doc='Binary variable indicating whether node i belongs to company k (0 if it does not belong and 1 if it belongs)')
model.p = Var(model.i,model.k, within=(0.0,None), doc='Cost of building backbone link p_ij')
#Contraints#
def allocation_rule(model, i):
return sum(model.a[i,k] for k in model.k) == 1
model.allocation = Constraint(model.i, rule=allocation_rule, doc='Each node can only belong to one company')
def minimum_rule(model, k):
return sum(model.a[i,k] for i in model.i) >= 2
model.minimum = Constraint(model.k, rule=minimum_rule, doc='Each company must have at least 2 nodes')
def absolute_rule1(model):
return model.p >=(model.a[i,k]-
model.a[j,k])*model.c*model.d[i,j]
model.absolute1 = Constraint(model.i, rule=absolute_rule1, doc='To take the positive cost')
def absolute_rule2(model):
for i in model.i:
for j in model.i:
if model.d[i,j]!=0:
for k in model.k:
return model.p >=-(model.a[i,k]-
model.a[j,k])*model.c*model.d[i,j]
model.absolute2 = Constraint(model.i, rule=absolute_rule2, doc='To take the positive cost')
#objective
def totalcost(model):
cost=0
for i in model.i:
for j in model.i:
if model.d[i,j]!=0:
for k in model.k:
cost+=model.p
return cost
model.z = Objective(rule=totalcost, sense=minimize, doc='Minimize the cost of implementing a backbone connecting the three sub-networks')

Below is a slightly modified approach.
You could put in the helper variables to get to absolute value, but I think that might lead you a bit astray in your objective, as I mentioned in the comment. Specifically, if you have 3 companies, the best you could do for "ownership" would be 1 company owning it, so as you summed over all three companies, you would get one "zero" cost and two actual costs, which is probably not desired.
I reformulated a bit to something which kinda does the same thing with a couple new variables. Realize there is "upward pressure" in the model for link ownership... cost is reduced (good) if more links are owned, so the variable I put in assesses each link by company and only allows ownership if they own both nodes.
The other new variable indicates whether a link is owned or not, independent of company. I think you could probably do without that, but it adds a little clarity. You could get the same thing (remove the variable, I think) by observing:
build_link >= 1 - sum(own_link)
Also, a reminder... I didn't see in your original code that you were inspecting the solver results. Always, always, always do that to ensure the status is "optimal" or you are looking at junk response.
Code:
from pyomo.environ import *
links={
(1, 2) : 1.8,
(1, 7) : 1,
(1, 13) : 5.4,
(2, 8) : 2.3,
(2, 3) : 1.7,
(2, 5) : 7,
(2, 7) : 2,
(2, 12) : 3,
(3, 4) : 2,
(3, 10) : 6.5,
(4, 5) : 1,
(4, 6) : 2,
(5, 8) : 5,
(5, 10) : 1,
(5, 11) : 1.5,
(6, 11) : 2.1,
(7, 12) : 2,
(8, 9) : 2,
(8, 13) : 0.7,
(9, 10) : 1.1,
(10, 11) : 1,
(12, 13) : 2.5,
}
# Creation of a Concrete Model
model = ConcreteModel()
# Sets
model.i = Set(initialize=[1,2,3,4,5,6,7,8,9,10,11,12,13], doc='Nodes')
model.k = Set(initialize=['Orange','SFR', 'Bouygues'], doc='Companies')
model.links = Set(within=model.i*model.i, initialize=links.keys())
# Parameters
model.c = Param(initialize=25, doc='Cost of transforming an existing link into a backbone link in euro/km')
model.d = Param(model.links, default=0, initialize=links, doc='distance in 10 km between nodes')
# Variables
model.a = Var(model.i, model.k, within=Binary, doc='Binary variable indicating whether node i belongs to company k (0 if it does not belong and 1 if it belongs)')
model.own_link = Var(model.links, model.k, within=Binary, doc='Own the link')
model.build_link = Var(model.links, within=Binary, doc='build link')
#Contraints#
def allocation_rule(model, i):
return sum(model.a[i,k] for k in model.k) == 1
model.allocation = Constraint(model.i, rule=allocation_rule, doc='Each node can only belong to one company')
def minimum_rule(model, k):
return sum(model.a[i,k] for i in model.i) >= 2
model.minimum = Constraint(model.k, rule=minimum_rule, doc='Each company must have at least 2 nodes')
def link_owner(model, k, n1, n2):
return model.own_link[n1, n2, k] <= 0.5 * (model.a[n1, k] + model.a[n2, k])
model.link1 = Constraint(model.k, model.links, rule=link_owner)
# link the "build link" variable to lack of link ownership
def link_build(model, *link):
return model.build_link[link] >= 1 - sum(model.own_link[link, k] for k in model.k)
model.build_constraint = Constraint(model.links, rule=link_build)
# objective
cost = sum(model.build_link[link]*model.c*model.d[link] for link in model.links)
model.z = Objective(expr=cost, sense=minimize, doc='Minimize the cost of implementing a backbone connecting the three sub-networks')
## Display of the output ##
optimizer = SolverFactory("glpk") #creates an optimizer object that uses the glpk package installed to your usr/bin.
result = optimizer.solve(model) #tells your optimizer to solve the model object
print(result)
print('Link Ownership Plan:')
for idx in model.own_link.index_set():
if model.own_link[idx].value: # will be true if it is 1, false if 0
print(idx, model.own_link[idx].value)
print('\nLink Build Plan:')
for idx in model.build_link.index_set():
if model.build_link[idx].value: # will be true if it is 1, false if 0
print(idx, model.build_link[idx].value)
Output:
Problem:
- Name: unknown
Lower bound: 232.5
Upper bound: 232.5
Number of objectives: 1
Number of constraints: 105
Number of variables: 128
Number of nonzeros: 365
Sense: minimize
Solver:
- Status: ok
Termination condition: optimal
Statistics:
Branch and bound:
Number of bounded subproblems: 2183
Number of created subproblems: 2183
Error rc: 0
Time: 0.21333098411560059
Solution:
- number of solutions: 0
number of solutions displayed: 0
Link Ownership Plan:
(1, 2, 'Orange') 1.0
(1, 7, 'Orange') 1.0
(1, 13, 'Orange') 1.0
(2, 8, 'Orange') 1.0
(2, 5, 'Orange') 1.0
(2, 7, 'Orange') 1.0
(2, 12, 'Orange') 1.0
(3, 10, 'SFR') 1.0
(4, 6, 'Bouygues') 1.0
(5, 8, 'Orange') 1.0
(6, 11, 'Bouygues') 1.0
(7, 12, 'Orange') 1.0
(8, 9, 'Orange') 1.0
(8, 13, 'Orange') 1.0
(12, 13, 'Orange') 1.0
Link Build Plan:
(2, 3) 1.0
(3, 4) 1.0
(4, 5) 1.0
(5, 10) 1.0
(5, 11) 1.0
(9, 10) 1.0
(10, 11) 1.0

Related

How can you appropriately index a pyomo parameter that is initialized with a list by a multi-dimensional pyomo set?

For my application, I am trying to initialize a parameter for a concrete model using a function rule that outputs a list. The pyomo set I am using to index this parameter is multi-dimensional. It is important for this set to be multi-dimensional because it makes accessing the data structure I am pulling values from for the parameter much simpler. However, when I attempt to index with the set this way, I receive an index error.
Here is a simple test code that illustrates my issue.
First, I have my imports as necessary
## Testing pyomo sets and indexing
import pyomo.environ as pyo
import numpy as np
import itertools as iter
Then I define a concrete model and some numpy arrays
M = pyo.ConcreteModel()
a = np.arange(3)
b = np.arange(3)
Using an itertools product, I generate a list of two-dimensional tuples
T = list(iter.product(a,b))
M.T = pyo.Set(initialize = T)
M.T.pprint()
The output of this print is
T : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 2 : Any : 9 : {(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)}
I define a function that returns a list and define a pyomo parameter that initializes with this function.
def paramListInitialize(M):
List = []
for i in M.T:
List.append(i)
return List
M.Param3 = pyo.Param(M.T, initialize = paramListInitialize(M))
I receive the following error.
ERROR: Rule failed for Param 'Param3' with index 0: KeyError: "Index '0' is
not valid for indexed component 'Param3'"
ERROR: Constructing component 'Param3' from data=None failed: KeyError: "Index
'0' is not valid for indexed component 'Param3'"
KeyError: "Index '0' is not valid for indexed component 'Param3'"
I am confused because I am able to define pyomo variable with this multi-dimensional index set and I can initialize parameters with lists if the index set in one dimensional; however, I am unable to get the pyomo object to associate the tuples in the pyomo set to the values in the initialization list.
It would be very helpful to know why this is not working for me or if there is another way to generate a multi-dimensional pyomo set that is not in the form of a list of tuples.
You need to look more closely at the dox on how to use a rule to initialize here.
The rule needs to return a single value for the index or indices passed in.
Here is an example using your 2-dim set:
import pyomo.environ as pyo
import numpy as np
import itertools as iter
m = pyo.ConcreteModel()
a = np.arange(3)
b = np.arange(3)
T = list(iter.product(a,b))
m.T = pyo.Set(initialize = T)
def example(m, a, b):
return a + b
m.p = pyo.Param(m.T, initialize=example)
m.pprint()
Yields:
1 Set Declarations
T : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 2 : Any : 9 : {(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)}
1 Param Declarations
p : Size=9, Index=T, Domain=Any, Default=None, Mutable=False
Key : Value
(0, 0) : 0
(0, 1) : 1
(0, 2) : 2
(1, 0) : 1
(1, 1) : 2
(1, 2) : 3
(2, 0) : 2
(2, 1) : 3
(2, 2) : 4
2 Declarations: T p

Change 5th number from list

I have a one function for my calendar
def formatweek(self, theweek, events):
week = ''
for d, weekday in theweek:
#return f'<tr style="color:blue";> {week} </tr>'
week += self.formatday(d, events)
return f'<tr> {week} </tr>'
def formatday(self, day, events):
events_per_day = events.filter(start_time__day=day)
d = ''
for event in events_per_day:
d += f'<li> {event.get_html_url} </li>'
if day != 0:
return f"<td><span class='date'>{day}</span><ul> {d} </ul></td>"
return '<td></td>'
where theweek is a list of pairs:
[(1, 0), (2, 1), (3, 2), (4, 3), (5, 4), (6, 5), (7, 6)]
[(8, 0), (9, 1), (10, 2), (11, 3), (12, 4), (13, 5), (14, 6)]
[(15, 0), (16, 1), (17, 2), (18, 3), (19, 4), (20, 5), (21, 6)]
[(22, 0), (23, 1), (24, 2), (25, 3), (26, 4), (27, 5), (28, 6)]
[(29, 0), (30, 1), (31, 2), (0, 3), (0, 4), (0, 5), (0, 6)]
Where the first number(for example(1)) in pairs is day in month (1.januar) and the second is a number for weekday name (0-Monday, 1-Thuesday etc...)
d and weekday become an int in loop. d = days of month (1,2,3,4....)and Weekday = number for days (Mon - 0, Tue - 1 etc...).
It returns this return f'<tr> {week} </tr>'for the table. And I would like to make it return for 5th and 6th weekday numbers (Saturday, Sunday) additionally f'<tr style="color:blue";> {5 (or 6)} </tr>'.
So the simple say: I would like to make a weekend days to become blue.

generating a multi dict with random numbers

I have a data structure defined as follow :
reqList[i] = [multidict({
1: ['type1', randint(1, 5), randint(1, 5), randint(1, 5)],
2: ['type2', randint(1, 5), randint(1, 5), randint(1, 5)],
3: ['type3', randint(1, 5), randint(1, 5), randint(1, 5)],
4: ['type4', randint(1, 5), randint(1, 5), randint(1, 5)]
}),
multidict({
(1, 2): randint(500, 1000),
(2, 3): randint(500, 1000),
(3, 4): randint(500, 1000)
})]
I want to make the creation of this data structure automatic in a for loop for example. I did this:
nodes = {}
for j in range(1, randint(2, 5)):
nodes[j] = ['type%d' % j, randint(1, 5), randint(1, 5), randint(1, 5)]
edges = {}
for kk in range(1, len(nodes)):
edges[(kk, kk + 1)] = randint(500, 1000)
print "EDGES", edges
reqList[i] = [multidict(nodes),
multidict(edges)]
del (nodes, edges)
when I look into the outputed edges the order of the keys is not kept ! For example I am getting this:
EDGES {(1, 2): 583, (3, 4): 504, (2, 3): 993}
I want it to be :
EDGES {(1, 2): 583, (2, 3): 993, (3, 4): 504}
Does the way I am coding it is correct ? if not, could you suggest a better way knowing that I need to get the same result as in the first example?
Dictionary in 2.7 is unordered and you can not keep the order of insert, unless you manually keep a reference on what key got inserted and when in a separate list. The module collections contains a class called OrderedDict that acts like a dictionary but keeps the inserts in ordered, which is what you could use (it also uses a list to keep track of the keys insert but uses a double link list to speed up deletion of keys).
There's no other way other than these two method.
from collections import OrderedDict
nodes = {}
for j in range(1, randint(2, 5)):
nodes[j] = ['type%d' % j, randint(1, 5), randint(1, 5), randint(1, 5)]
edges = OrderedDict()
for kk in range(1, len(nodes)):
edges[(kk, kk + 1)] = randint(500, 1000)
print "EDGES", edges # EDGES OrderedDict([((1, 2), 898), ((2, 3), 814)])
print edges[(1,2)] # still yields the correct number
You can read more about OrderedDict here

ValueError on tensorflow while_loop shape invariants

import tensorflow as tf
cluster_size = tf.constant(6) # size of the cluster
m = tf.constant(6) # number of contigs (column size)
n = tf.constant(3) # number of points in a single contigs (column size)
contigs_index = tf.reshape(tf.range(0, m, 1, dtype=tf.int32), [1, -1])
contigs = tf.constant(
[[1.1, 2.2, 3.3], [6.6, 5.5, 4.4], [7.7, 8.8, 9.9], [11.1, 22.2, 33.3],
[66.6, 55.5, 44.4], [77.7, 88.8, 99.9]])
# pad zeo to the right till fixed length
def rpad_with_zero(points):
points = tf.slice(tf.pad(points, tf.reshape(tf.concat(
[tf.zeros([1, 2], tf.int32), tf.add(
tf.zeros([1, 2], tf.int32),
tf.subtract(cluster_size, tf.size(points)))], 0), [2, -1]), "CONSTANT"),
(0, tf.subtract(cluster_size, tf.size(points))),
(1, cluster_size))
return points
#calculate pearson correlation coefficient r value
def calculate_pcc(row, contigs):
r = tf.divide(tf.subtract(
tf.multiply(tf.to_float(n), tf.reduce_sum(tf.multiply(row, contigs), 1)),
tf.multiply(tf.reduce_sum(row, 1), tf.reduce_sum(contigs, 1))),
tf.multiply(
tf.sqrt(tf.subtract(
tf.multiply(tf.to_float(n), tf.reduce_sum(tf.square(row), 1)),
tf.square(tf.reduce_sum(row, 1)))),
tf.sqrt(tf.subtract(tf.multiply(
tf.to_float(n), tf.reduce_sum(tf.square(contigs), 1)),
tf.square(tf.reduce_sum(contigs, 1)))
)))
return r
#slice first row from contigs
row = tf.slice(contigs, (0, 0), (1, 3))
#calculate pcc
r = calculate_pcc(row, contigs)
#cluster member index whose r value is greater than 0.90, then casting to
# int32,
members0_index = tf.cast(tf.reshape(tf.where(tf.greater(r, 0.90)), [1, -1]),
tf.int32)
#members = index <intersection> members, padding the members index with
# zeros at right, to keep the fixed cluster length
members0_index = rpad_with_zero(
tf.reshape(tf.sets.set_intersection(contigs_index, members0_index).values,
[1, -1]))
#update index with the rest element index from contigs, and padding
contigs_index = rpad_with_zero(
tf.reshape(tf.sets.set_difference(contigs_index, members0_index).values,
[1, -1]))
#def condition(contigs, contigs_index, members0_index):
def condition(contigs_index, members0_index):
return tf.greater(tf.count_nonzero(contigs_index),
0) # iterate until there is a contig
#def body(contigs, contigs_index, members0_index):
def body(contigs_index, members0_index):
i = tf.reshape(tf.slice(contigs_index, [0, 0], [1, 1]),
[]) #the first element in the contigs_index
row = tf.slice(contigs, (i, 0),
(1, 3)) #slice the ith contig from contigs
r = calculate_pcc(row, contigs)
members_index = tf.cast(tf.reshape(tf.where(tf.greater(r, 0.90)), [1, -1]),
tf.int32)
members_index = rpad_with_zero(rpad_with_zero(
tf.reshape(tf.sets.set_intersection(contigs_index, members_index).values,
[1, -1])))
members0_index = tf.concat([members0_index, members_index], 0)
contigs_index = rpad_with_zero(
tf.reshape(tf.sets.set_difference(contigs_index, members_index).values,
[1, -1]))
#return [contigs, contigs_index, members0_index]
return [contigs_index, members0_index]
sess = tf.Session()
sess.run(tf.while_loop(condition, body,
#loop_vars=[contigs, contigs_index, members0_index],
loop_vars=[contigs_index, members0_index],
#shape_invariants=[contigs.get_shape(), contigs_index.get_shape(),
# tf.TensorShape([None, 6])]))
shape_invariants=[contigs_index.get_shape(), tf.TensorShape([None, 6])]))
The error is:
ValueError: The shape for while_12/Merge:0 is not an invariant for the
loop. It enters the loop with shape (1, 6), but has shape (?, ?) after
one iteration. Provide shape invariants using either the
shape_invariants argument of tf.while_loop or set_shape() on the
loop variables.
It seems the variable
contigs_index
is responsible, but i really don't know why! I unfold the loop execute each statement but could not find any shape mismatch!
shape_invariants=[contigs_index.get_shape(), tf.TensorShape([None, 6])])) should become shape_invariants=[tf.TensorShape([None, None]), tf.TensorShape([None, 6])])), to allow for shape changes of contigs_index variable (in the rpad_with_zero call).

Combinations into pairs

I'm working on a directed network problem and trying to compute all valid paths between two points. I need a way to look at paths up to 30 "trips" (represented by an [origin, destination] pair) in length. The full route is then composed of a series of these pairs:
route = [[start, city2], [city2, city3], [city3, city4], [city4, city5], [city5, city6], [city6, city7], [city7, city8], [city8, stop]]
So far my best solution is as follows:
def numRoutes(graph, start, stop, minStops, maxStops):
routes = []
route = [[start, stop]]
if distance(graph, route) != "NO SUCH ROUTE" and len(route) >= minStops and len(route) <= maxStops:
routes.append(route)
if maxStops >= 2:
for city2 in routesFromCity(graph, start):
route = [[start, city2],[city2, stop]]
if distance(graph, route) != "NO SUCH ROUTE" and len(route) >= minStops and len(route) <= maxStops:
routes.append(route)
if maxStops >= 3:
for city2 in routesFromCity(graph, start):
for city3 in routesFromCity(graph, city2):
route = [[start, city2], [city2, city3], [city3, stop]]
if distance(graph, route) != "NO SUCH ROUTE" and len(route) >= minStops and len(route) <= maxStops:
routes.append(route)
if maxStops >= 4:
for city2 in routesFromCity(graph, start):
for city3 in routesFromCity(graph, city2):
for city4 in routesFromCity(graph, city3):
route = [[start, city2], [city2, city3], [city3, city4], [city4, stop]]
if distance(graph, route) != "NO SUCH ROUTE" and len(route) >= minStops and len(route) <= maxStops:
routes.append(route)
if maxStops >= 5:
for city2 in routesFromCity(graph, start):
for city3 in routesFromCity(graph, city2):
for city4 in routesFromCity(graph, city3):
for city5 in routesFromCity(graph, city4):
route = [[start, city2], [city2, city3], [city3, city4], [city4, city5], [city5, stop]]
if distance(graph, route) != "NO SUCH ROUTE" and len(route) >= minStops and len(route) <= maxStops:
routes.append(route)
return routes
Where numRoutes is fed my network graph where numbers represent distances:
[[0, 5, 0, 5, 7], [0, 0, 4, 0, 0], [0, 0, 0, 8, 2], [0, 0, 8, 0, 6], [0, 3, 0, 0, 0]]
a start city, an end city and the parameters for the length of the routes.
distance checks if a route is viable and routesFromCity returns the attached nodes to each fed in city.
I have a feeling there's a far more efficient way to generate all of the routes especially as I move toward many more steps, but I can't seem to get anything else to work.
You could use a recursive function. Your maxStops can be a parameter and each time you call you decrease this by 1. When minStops is 0, you yield a result, When the maxStops is 0 you stop recursing.
Here is a code example:
def routesFromCity(x):
for i in range(2, 10):
yield x * i
def findRoutes(start, stop, minStops, maxStops):
if start == stop:
if minStops <= 0:
yield []
else:
if maxStops > 0:
for nextCity in routesFromCity(start):
for route in findRoutes(nextCity, stop, minStops - 1, maxStops - 1):
yield [(start, nextCity)] + route
for route in findRoutes(1, 12, 2, 5):
print route
Output:
[(1, 2), (2, 4), (4, 12)]
[(1, 2), (2, 6), (6, 12)]
[(1, 2), (2, 12)]
[(1, 3), (3, 6), (6, 12)]
[(1, 3), (3, 12)]
[(1, 4), (4, 12)]
[(1, 6), (6, 12)]