pyomoglpk

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


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')        

Solution

  • 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