0

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')        
Bubblytwo
  • 3
  • 2
  • Does this answer your question? [How to make integer optimization with absolute values in python?](https://stackoverflow.com/questions/64108936/how-to-make-integer-optimization-with-absolute-values-in-python) – AirSquid Nov 20 '22 at 00:06
  • Thanks for the suggestion. So i understand that I have to introduce a new dummy variable and then add 2 additional constraints. As I am new to pyomo, I would like to understand how do I introduce this new "dummy variable" into my code? How can I define that z(i,j,k)=cost(i,j,k) and my optimisation function is to minimise the sum of all the z? where 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 – Bubblytwo Nov 20 '22 at 12:03
  • Do I understand your intent with the cost function correctly.... If one company owns both nodes in a link, the cost is zero, else the cost is computed from the parameters? You realize that this will be very lopsided as it will motivate the model to have 1 company own as many as possible? – AirSquid Nov 20 '22 at 16:11
  • Yes, you are absolutely right, the cost is computed only if two different companies own both nodes. Because originally, all the nodes are owned by one company and now there is a need to distribute to 2 other companies which will incur cost. And the constraint is that each company needs at least 2 nodes – Bubblytwo Nov 20 '22 at 18:51
  • This is what I tried to create a new variable (p) and 2 new constraints. But it is still not right model.p = Var(model.i,model.i, within=(0.0,None), doc='Cost of building backbone link p_ij') def absolute_rule1(model,i): 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,i): return model.p>=-(model.a[i,k]-model.a[j,k])*model.c*model.d[i,j] model.absolute1 = Constraint(model.i, rule=absolute_rule2, doc='To take the absolute of the negative cost') – Bubblytwo Nov 20 '22 at 19:05
  • 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') – Bubblytwo Nov 20 '22 at 19:05
  • aside: if you have more than about 1 code statement, it is better to just pop it into the original as an edit, perhaps down at the bottom. The above comments make me seasick! ;) – AirSquid Nov 21 '22 at 01:30
  • And just looking at this objective again... Let's say the total cost of a link was 5. In your current objective construct, it appears the best you could do would be a "penalty" or cost of 10 because 2 of the 3 companies would not own the link and you are summing over all companies, and the worst you could do would be 15 if no company owned the nodes. Is that desirable? – AirSquid Nov 21 '22 at 01:54
  • I have added the new codes above. I dont quite understand your question..every node must be owned by one company (constraint). There are different distances between each node and this would determine the cost of building the link. The objective is to minmise the total cost of building the links for 13 nodes (only need to build a link between 2 nodes if they are owned by 2 different companies) – Bubblytwo Nov 21 '22 at 07:48

1 Answers1

1

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
AirSquid
  • 10,214
  • 2
  • 7
  • 31
  • thank you so much for your help! you're really a saviour! This is my first optimisation problem with Pyomo and you've helped me understand it so much better! – Bubblytwo Nov 21 '22 at 21:02