Search code examples
pythonpyomo

PYOMO: Defining dataset using Sets and Parameters for solving an optimization problem


I am trying to formulate some data into a PYOMO model for an optimization problem.

materials = ['steel', 'alum', 'carbon', 'cheese']

Each material has 2 properties - density and conductivity and their values are defined as follows.

density =   {   'steel' : 1.2,
            'alum'  : 0.8,
            'carbon': 1.8,
            'cheese': 0.7}

conductivity = {'steel' : 6.4,
               'alum'  : 3.1,
               'carbon': 4.4,
               'cheese': 0.3}

The objective function calculates the weight of 2 rectangular plates as given below:

Objective function = Area_1 * thickness_1 * density_1 + Area_2 * thickness_2 * density_2

Where, the Area_1, thickness_1, and density_1 are area, thickness and density of plate 1.

Area and thickness are fixed for each plates. But the density value depends on the material chosen by the solver to get the best results. The model also have a constraint defined as follows:

(conductivity_1/thickness_1) + (conductivity_2/thickness_2)  => 22

So, when the solver chooses a density value for a plate, it must also choose the conductivity value of the same material.

I would appreciate it if someone can help me with this problem. I also welcome if you have different ideas to solve this problem. Thank you.


Solution

  • Here is an example model that I think meets all of your questions.

    Once you set up the second index to be the plates P = {1, 2, 3} in this case for 3 plates, then we need to double index our decision variable to represent the assignment of material m to plate p. In this example, 4 materials, 3 plates.

    Many other variations of constraints are possible here, but the ones I added answer your question about conductivity in aggregate. Note that I have also added a constraint to ensure that 1 and only 1 material is assigned to each plate. You may/may not need this depending on other constraints in your model, but it is good insurance against bogus answers. This is also an example of the "for every" style of constraint using the function - rule combo in pyomo.

    The result... an aluminum and cheese sandwich... :)

    # material selection model
    
    import pyomo.environ as pyo
    
    # data
    materials = ['steel', 'alum', 'carbon', 'cheese']
    
    density =   {   'steel' : 1.2,
                    'alum'  : 0.8,
                    'carbon': 1.8,
                    'cheese': 0.7}
    
    conductivity = {'steel' : 40.8,
                    'alum'  : 30.1,
                    'carbon': 42.4,
                    'cheese': 15.3}
    
    price =     {   'steel' : 2.3,
                    'alum'  : 3.5,
                    'carbon': 5.8,
                    'cheese': 6.0}
    
                      # t     area
    plate_dims = {  1: (10,   150), 
                    2: (12.5, 200),
                    3: (8,    125)}
    
    mdl = pyo.ConcreteModel('material selector')
    
    # SETS (used to index the decision variable and the parameters)
    mdl.M = pyo.Set(initialize=materials)
    mdl.P = pyo.Set(initialize=plate_dims.keys())
    
    # VARIABLES
    mdl.x = pyo.Var(mdl.M, mdl.P, domain=pyo.Binary)  # select material M for plate P
    
    # PARAMETERS
    mdl.density =       pyo.Param(mdl.M, initialize=density)
    mdl.conductivity =  pyo.Param(mdl.M, initialize=conductivity)
    mdl.price =         pyo.Param(mdl.M, initialize=price)
    mdl.p_thickness =   pyo.Param(mdl.P, initialize= {k:v[0] for k,v in plate_dims.items()})
    mdl.p_area =        pyo.Param(mdl.P, initialize= {k:v[1] for k,v in plate_dims.items()})
    
    # OBJ (minimize total density)
    mdl.obj = pyo.Objective(expr=sum(mdl.x[m, p] * mdl.p_thickness[p] 
                            * mdl.p_area[p] * mdl.density[m] 
                            for m in mdl.M for p in mdl.P))
    
    # CONSTRAINTS
    # minimum conductivity
    mdl.c1 = pyo.Constraint(expr=sum(mdl.x[m, p] * mdl.conductivity[m]/mdl.p_thickness[p]
                            for m in mdl.M for p in mdl.P) >= 5.0)
    
    # must populate all plates with 1 material
    def c2(model, plate):
        return sum(mdl.x[m, plate] for m in mdl.M) == 1
    mdl.c2 = pyo.Constraint(mdl.P, rule=c2)
    
    # solve it
    solver = pyo.SolverFactory('glpk')
    result = solver.solve(mdl)
    mdl.display()
    

    Yields:

    Model material selector
    
      Variables:
        x : Size=12, Index=x_index
            Key           : Lower : Value : Upper : Fixed : Stale : Domain
              ('alum', 1) :     0 :   0.0 :     1 : False : False : Binary
              ('alum', 2) :     0 :   0.0 :     1 : False : False : Binary
              ('alum', 3) :     0 :   1.0 :     1 : False : False : Binary
            ('carbon', 1) :     0 :   0.0 :     1 : False : False : Binary
            ('carbon', 2) :     0 :   0.0 :     1 : False : False : Binary
            ('carbon', 3) :     0 :   0.0 :     1 : False : False : Binary
            ('cheese', 1) :     0 :   1.0 :     1 : False : False : Binary
            ('cheese', 2) :     0 :   1.0 :     1 : False : False : Binary
            ('cheese', 3) :     0 :   0.0 :     1 : False : False : Binary
             ('steel', 1) :     0 :   0.0 :     1 : False : False : Binary
             ('steel', 2) :     0 :   0.0 :     1 : False : False : Binary
             ('steel', 3) :     0 :   0.0 :     1 : False : False : Binary
    
      Objectives:
        obj : Size=1, Index=None, Active=True
            Key  : Active : Value
            None :   True : 3600.0
    
      Constraints:
        c1 : Size=1
            Key  : Lower : Body              : Upper
            None :   5.0 : 6.516500000000001 :  None
        c2 : Size=3
            Key : Lower : Body : Upper
              1 :   1.0 :  1.0 :   1.0
              2 :   1.0 :  1.0 :   1.0
              3 :   1.0 :  1.0 :   1.0