Search code examples
pythonruntime-errorpyomopiecewise

Piecewise linear functions with multiple indices variables Pyomo


I am currently working on modeling a facility location-allocation using optimization with Pyomo module. For my model, I'd like to employ Piecewise linear function, with the constrained variables have two indices. I have tried three approaches that can be seen below, nonetheless none of them seems working properly (or having error)

Does anybody have any ideas on how to solve this?

piece_wise = {'ab': {0:1,1:1,2:2}, 'cd': {0:1,1:1,2:3}, 'ef': {0:1,1:2,2:4}}
piece_wise_ab = {0:1,1:1,2:2}
piece_wise_cd = {0:1,1:1,2:3}
piece_wise_ef = {0:1,1:2,2:4}




################# Option 1 - not working 
model.y_ab = Var(model.Nodes, model.Vehicles,  within = NonNegativeIntegers, bounds = (0, max_chargers))
model.arriv_ab = Var(model.Nodes, model.Vehicles,  within = NonNegativeIntegers, bounds = (0, max_chargers))

model.y_cd = Var(model.Nodes, model.Vehicles,  within = NonNegativeIntegers, bounds = (0, max_chargers))
model.arriv_cd = Var(model.Nodes, model.Vehicles,  within = NonNegativeIntegers, bounds = (0, max_chargers))

model.y_ef = Var(model.Nodes, model.Vehicles,  within = NonNegativeIntegers, bounds = (0, max_chargers))
model.arriv_ef = Var(model.Nodes, model.Vehicles,  within = NonNegativeIntegers, bounds = (0, max_chargers))

model.piece_wise_ab = Piecewise(model.Nodes, model.Vehicles, model.arriv_ab, model.y_ab, 
                                pw_pts = list(piece_wise_ab.values()), f_rule = list(piece_wise_ab.keys()), pw_repn = 'SOS2', pw_constr_type='EQ',unbounded_domain_var=True)
        
model.piece_wise_cd = Piecewise(model.Nodes, model.Vehicles, model.arriv_cd, model.y_cd, 
                                pw_pts = list(piece_wise_cd.values()), f_rule = list(piece_wise_cd.keys()), pw_repn = 'SOS2', pw_constr_type='EQ',unbounded_domain_var=True)
        
model.piece_wise_ef = Piecewise(model.Nodes, model.Vehicles, model.arriv_cd, model.y_cd, 
                                pw_pts = list(piece_wise_ef.values()), f_rule = list(piece_wise_ef.keys()), pw_repn = 'SOS2', pw_constr_type='EQ',unbounded_domain_var=True)


def service_rule (model,i,veh):
    try:
        if 'ab' in veh:
            return sum(model.arrival [o,d,veh] * model.lam[i,o,d,veh]  for o,d in model.sp) <= model.y_ab[i]
        elif 'cd' in veh:
            return sum(model.arrival [o,d,veh] * model.lam[i,o,d,veh]  for o,d in model.sp) <= model.y_cd[i]
        elif 'ef' in veh:
            return sum(model.arrival [o,d,veh] * model.lam[i,o,d,veh]  for o,d in model.sp) <= model.y_ef[i]
    except:
        return Constraint.Skip

model.service = Constraint(model.Nodes, model.Vehicles, rule = service_rule)



#################################### Option 2 - not working
                                
model.piecewise_constraint = ConstraintList()

all_breakpoints = set(breakpoint for breakpoints in piece_wise.values() for breakpoint in breakpoints)

model.y_pc = Var(model.Nodes, model.Vehicles, within = NonNegativeIntegers, bounds = (0, max_chargers) )
model.arriv_pc = Var(model.Nodes, model.Vehicles, all_breakpoints,  within = Binary)

for veh in model.Vehicles:
    for node in model.Nodes:
        breakpoints = list(piece_wise[veh].keys())
        values_at_breakpoints = list(piece_wise[veh].values())

        print(breakpoints, all_breakpoints)

        model.piecewise_constraint.add(model.y_pc[node, veh] == sum(values_at_breakpoints[k] * model.arriv_pc[node, veh, k] for k in breakpoints))

 

############################## Option 3 - Not working
for node in model.Nodes:
    for veh in model.Vehicles:
        if "ab" in veh:

            model.piecewise_constraint.add (model.y_pc [node, veh] == Piecewise (expr = [(model.arriv_pc [node, veh], model.y_pc [node, veh])],
                                                                                pw_pts = list(piece_wise_ab.values()), f_rule = list(piece_wise_ab.keys()), pw_repn = 'SOS2',
                                                                                pw_constr_type='EQ',unbounded_domain_var=True))
        elif "cd" in veh:
            model.piecewise_constraint.add (model.y_pc [node, veh] == Piecewise (expr = model.arriv_pc [node, veh],
                                                                    pw_pts = list(piece_wise_cd.values()), f_rule = list(piece_wise_cd.keys()), pw_repn = 'SOS2',
                                                                    pw_constr_type='EQ',unbounded_domain_var=True))
        elif "ef" in veh:
            model.piecewise_constraint.add (model.y_pc [node, veh] == Piecewise (expr = model.arriv_pc [node, veh],
                                                        pw_pts = list(piece_wise_ef.values()), f_rule = list(piece_wise_ef.keys()), pw_repn = 'SOS2',
                                                        pw_constr_type='EQ',unbounded_domain_var=True))

Thank you in Advance!


Solution

  • Here is an example that develops a piecewise linear constraint between variables x and y, both of which are dual-indexed variables.

    You aren't following the dox located here for construction of the Piecewise constraint. Specifically, see the guidance on the f_rule.

    Code

    from pyomo.environ import Objective, NonNegativeIntegers, ConcreteModel, Constraint, Var, Set, SolverFactory, \
        sum_product, maximize, Piecewise
    
    m = ConcreteModel('piecewise_example')
    
    piecewise_elements = {0: 1, 5: 2, 10: 20}
    
    m.Nodes = Set(initialize=[1, 2, 3])
    m.Vehicles = Set(initialize=['a81', 'b22', 'c43'])
    
    m.x = Var(m.Nodes, m.Vehicles, domain=NonNegativeIntegers, bounds=(0, 10))
    m.y = Var(m.Nodes, m.Vehicles, domain=NonNegativeIntegers)
    
    # max the sum of m.y...
    m.obj = Objective(expr=sum_product(m.y), sense=maximize)
    
    # subset the "c" cars...
    m.c_vehicles = Set(initialize=[v for v in m.Vehicles if 'c' in v])
    
    
    # limit the x vals based on node
    @m.Constraint(m.Nodes, m.Vehicles)
    def x_limit(m, node, vehicle):
        if node == 1:
            return m.x[node, vehicle] <= 4
        return m.x[node, vehicle] <= 9
    
    
    def limit_vals(model, node, vehicle, x_val):
        # per the dox, the limit function needs to catch the model and all indices, but does
        # not need to use them...
        return piecewise_elements[x_val]
    
    
    m.pw_limit = Piecewise(
        m.Nodes,
        m.Vehicles,
        m.y,
        m.x,
        pw_pts=list(piecewise_elements.keys()),
        f_rule=limit_vals,
        pw_constr_type='UB')
    
    m.pprint()
    
    solver = SolverFactory('cbc')
    res = solver.solve(m)
    
    print(res)
    
    for idx in m.x.index_set():
        print(idx, m.x[idx].value, m.y[idx].value)
    

    Partial Output:

    (1, 'a81') 4.0 1.0
    (1, 'b22') 4.0 1.0
    (1, 'c43') 4.0 1.0
    (2, 'a81') 9.0 16.0
    (2, 'b22') 9.0 16.0
    (2, 'c43') 9.0 16.0
    (3, 'a81') 9.0 16.0
    (3, 'b22') 9.0 16.0
    (3, 'c43') 9.0 16.0