7.3. Pyomo

Pyomo is a third-party open source modeling language developed based on Python. It supports modeling and analysis of linear programming, mixed-integer programming, nonlinear programming and other problems, and calls other commercial or open source solvers to solve them.

Currently, MindOpt can solve linear programming models built with Pyomo on Windows/Linux/OSX platforms. For more details about Pyomo, please refer to Pyomo Official Documentation Repository.

In this section, we describe how to use the Pyomo API to formulate the optimization problem in Example of Linear Programming and call MindOpt to solve it.

7.3.1. Install Pyomo

Users need to install MindOpt first. For installation and configuration of MindOpt, please refer to Software Installation. After MindOpt is installed, users can install Pyomo with the following pip command:

pip install pyomo

For the detailed installation method of Pyomo, please refer to Pyomo Installation.

7.3.2. Pyomo Interface

MindOpt provides two different plug-in interfaces for Pyomo, mindopt_direct and mindopt_persistent.

mindopt_direct is a simple interface that rebuilds a Mindopt model every time the solver is called, and solves by converting the constraints and variables in the pyomo model into constraints and variables of the Mindopt model. This interface is suitable for simple optimization problems, and may cause some solver performance losses for complex problems.

mindopt_persistent is a more advanced interface that stores model objects in memory, allowing multiple modifications and repeated solutions. It is very useful for problems that need to solve the same model multiple times, which can avoid the overhead of repeatedly creating and destroying model objects, thereby improving the solution efficiency.

7.3.2.1. Pyomo Direct Interface

MindOpt defines the relevant interfaces required by Pyomo to call MindOpt in the Pyomo Direct interface file (mindopt_pyomo.py). This interface is inherited from Pyomo’s DirectSolver class, see the interface file in the installation package for implementation details:

<MDOHOME>/<VERSION>/<PLATFORM>/lib/pyomo/mindopt_persistent.py
<MDOHOME>/<VERSION>/<PLATFORM>/sh/pyomo/mindopt_pyomo.py

Users need to move the interface file to the current working directory first, and load the MindoDirect class defined in this module in the Python code:

30from mindopt_pyomo import MindoDirect

Next, we call the Pyomo API to build the optimization problem in Example of Linear Programming. For a detailed description of the Pyomo API, please refer to Pyomo Official Documentation API.

41    # Define variables and constraints.
42    variable_names = ['x0', 'x1', 'x2', 'x3']
43    var_lb = {'x0':0, 'x1':0, 'x2':0, 'x3':0}
44    var_ub = {'x0':10, 'x1':None, 'x2':None, 'x3':None}
45    objective_coeff = {'x0': 1, 'x1': 1, 'x2': 1, 'x3': 1}
46    constraint_names = ['c0', 'c1']
47    constraint_bound = {'c0': 1, 'c1': 1}
48    constraint_coeff = {('x0', 'c0'): 1, ('x1', 'c0'): 1, ('x2', 'c0'): 2, ('x3', 'c0'): 3,
49                        ('x0', 'c1'): 1, ('x1', 'c1'): -1, ('x2', 'c1'): 0, ('x3', 'c1'): 6}
50
51    # Create model.
52    model = ConcreteModel(name="ex1")
53
54    # Build decision variables.
55    model.Set = Set(initialize=variable_names)
56    model.Variable = Var(model.Set, within=NonNegativeReals, bounds=fb)
57
58    # Objective.
59    model.obj = Objective(expr=sum(objective_coeff[var_name] * model.Variable[var_name] for var_name in variable_names), sense=minimize)
60
61    # Constraints.
62    model.dual = Suffix(direction=Suffix.IMPORT)
63    model.cons1 = Constraint(expr = ConstraintsRule(model, 'c0') >= constraint_bound['c0'])
64    model.cons2 = Constraint(expr = ConstraintsRule(model, 'c1') == constraint_bound['c1'])

Before solving, we specify to use the MindOpt solver and set the relevant parameters of the solution (see Parameters for the parameters of the solver):

69    # Solve problem by MindOpt solver.
70    opt = SolverFactory("mindo_direct")
71    
72    # Set options.
73    opt.options['Method'] = -1
74    opt.options['IPM/PrimalTolerance'] = 1e-10

Finally, call Pyomo’s solving function solve() to solve and get the relevant results:

79    # Solve.
80    results = opt.solve(model)
81
82    # Summary of result.
83    results.write()
84
85    if (results.solver.status == SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal):
86        print("The solution is optimal and feasible.")
87        model.Variable.display()
88        model.dual.display()
89        model.obj.display()
90    elif (results.solver.termination_condition == TerminationCondition.infeasible):
91        print("The model is infeasible.")
92        print("Solver Status: ",  results.solver.status)
93    else:
94        print("Something else is wrong.")
95        print("Solver Status: ",  results.solver.status)

7.3.2.2. Pyomo Persistent Interface

MindOpt defines the relevant persistent interfaces required by Pyomo to call MindOpt in the Pyomo Persistent interface file (mindopt_persistent.py). This interface inherits from Pyomo’s PersistentSolver class, see the interface file in the installation package for implementation details:

<MDOHOME>/<VERSION>/<PLATFORM>/sh/pyomo/mindopt_persistent.py

When using it, the user needs to first move the interface file to the current working directory, and import the MindoptPersistent class defined in this module in the Python code:

from mindopt_persistent import MindoptPersistent

Next, we call the Pyomo API to build the optimization problem in Example of Linear Programming. For a detailed description of the Pyomo API, please refer to Pyomo Official Documentation API.

model = ConcreteModel()

products = ['A', 'B']
materials = ['X', 'Y']

model.x = Var(products, domain=NonNegativeReals)

model.profit = Objective(expr=10 * model.x['A'] + 15 * model.x['B'], sense=maximize)

model.material_x = Constraint(expr=2 * model.x['A'] + model.x['B'] <= 10)
model.material_y = Constraint(expr=model.x['A'] + 2 * model.x['B'] <= 6)

solver = SolverFactory('mindopt_persistent')

The model must be saved in memory using set_instance before solving.

solver.set_instance(model)
solver.solve(model)

After solving, you can also use the functions provided by Persistent to obtain model attributes/parameters, and you can also modify the original model.

var_attr = solver.get_var_attr(model.x['A'], 'lb')
print(var_attr)

7.3.3. SOS Function

SOS (Special Ordered Sets) constraints are a special type of constraints, which are used to limit that there can only be at most N variables in a set of variables that can take non-zero values. SOS constraints are very useful in some optimization problems. For example, in scheduling problems, SOS constraints can be used to ensure that only one task can be selected for execution at each time point.

Whether you use the mindopt_direct or mindopt_persistent interface, you can use the SOS function of MindOpt.

When users use SOS through Pyomo modeling code, they need to first define an SOS collection to group specific variables into ordered collections. For example, assuming two variables x and y belong to the same SOS group, the following code can be implemented:

from pyomo.environ import *

model = ConcreteModel()
model.x = Var(within=NonNegativeReals)
model.y = Var(within=NonNegativeReals)

model.sos = SOSConstraint(
   rule=lambda m: [(m.x, m.y)]
)

In the above example, the parameter rule of SOSConstraint is used to specify the rule that the variable belongs to the SOS collection. In this example, we group variables x and y into the same SOS collection.

7.3.4. Callback Function

Whether using the mindopt_direct or mindopt_persistent interface, users can use the callback function of MindOpt to provide some heuristic decisions to optimize the solution speed.

However, considering that the models that need to use the callback function are generally more complex, it is recommended that users use the mindopt_persistent interface.

To use Mindopt’s callback function in Pyomo, you first need to define a callback function to handle specific events.

from pyomo.environ import *

def my_callback(model, where):
   where_names = {
         3: "MIPSTART",
         4: "MIPSOL",
         5: "MIPNODE"
   }

   print("where = {}".format(where_names[where]))

   # Counter for calls
   model._calls += 1

   # Print the constraint matrix of the presolved model. The getA() function returns a scipy.sparse.csc_matrix.
   # Note that the model passed to the callback function is a presolved model in Python SDK.
   # Any modifications to the constraint matrix should not be allowed as it may lead to unforeseen issues.
   print(model.getA())

   bounds = []
   for v in model.getVars():
         bounds.append((model.cbGet(MDO.Callback.PRE_LB, v), model.cbGet(MDO.Callback.PRE_LB, v)))

   # Print the upper and lower bounds for the original problem
   print(bounds)

   bounds.clear()
   for v in model.getVars():
         bounds.append((model.cbGet(MDO.Callback.MIP_LB, v), model.cbGet(MDO.Callback.MIP_LB, v)))

In the above example, we defined a callback function called my_callback. In this callback function, we use the cbGet method to obtain some information of the current preprocessing model, such as the lower bound of variables, etc. Then, you need to connect the callback function with the Mindopt solver and register the callback function for specific events.

opt = SolverFactory('mindopt_persistent')
opt.set_instance(model)

# Register the callback function
opt.set_callback(my_callback)

results = opt.solve()

In the above example, we use SolverFactory to create a mindopt_persistent solver instance, and register the callback function my_callback to the solver. Then, we use the solve method to solve the model, and trigger the corresponding callback event.

7.3.5. Modeling Example: mdo_pyomo_lo_ex1

The full code is available in the file link mdo_pyomo_lo_ex1.py:

 1"""
 2/**
 3 *  Description
 4 *  -----------
 5 *
 6 *  Linear optimization.
 7 *
 8 *  Formulation
 9 *  -----------
10 *
11 *  Minimize
12 *    obj: 1 x0 + 1 x1 + 1 x2 + 1 x3
13 *  Subject To
14 *   c1 : 1 x0 + 1 x1 + 2 x2 + 3 x3 >= 1
15 *   c2 : 1 x0 - 1 x2        + 6 x3 == 1
16 *  Bounds
17 *    0 <= x0 <= 10
18 *    0 <= x1
19 *    0 <= x2
20 *    0 <= x3
21 *  End
22 */
23"""
24
25from pyomo.environ import *
26from pyomo.core.base.util import Initializer
27from pyomo.opt import SolverFactory
28from pyomo.opt import SolverStatus, TerminationCondition
29
30from mindopt_pyomo import MindoDirect
31from mindoptpy import *
32
33def ConstraintsRule(model, p):
34    return sum(constraint_coeff[i, p] * model.Variable[i] for i in variable_names)
35
36def fb(model, i):
37    return (var_lb[i], var_ub[i])
38
39if __name__ == '__main__':
40
41    # Define variables and constraints.
42    variable_names = ['x0', 'x1', 'x2', 'x3']
43    var_lb = {'x0':0, 'x1':0, 'x2':0, 'x3':0}
44    var_ub = {'x0':10, 'x1':None, 'x2':None, 'x3':None}
45    objective_coeff = {'x0': 1, 'x1': 1, 'x2': 1, 'x3': 1}
46    constraint_names = ['c0', 'c1']
47    constraint_bound = {'c0': 1, 'c1': 1}
48    constraint_coeff = {('x0', 'c0'): 1, ('x1', 'c0'): 1, ('x2', 'c0'): 2, ('x3', 'c0'): 3,
49                        ('x0', 'c1'): 1, ('x1', 'c1'): -1, ('x2', 'c1'): 0, ('x3', 'c1'): 6}
50
51    # Create model.
52    model = ConcreteModel(name="ex1")
53
54    # Build decision variables.
55    model.Set = Set(initialize=variable_names)
56    model.Variable = Var(model.Set, within=NonNegativeReals, bounds=fb)
57
58    # Objective.
59    model.obj = Objective(expr=sum(objective_coeff[var_name] * model.Variable[var_name] for var_name in variable_names), sense=minimize)
60
61    # Constraints.
62    model.dual = Suffix(direction=Suffix.IMPORT)
63    model.cons1 = Constraint(expr = ConstraintsRule(model, 'c0') >= constraint_bound['c0'])
64    model.cons2 = Constraint(expr = ConstraintsRule(model, 'c1') == constraint_bound['c1'])
65
66    # Print formulation of model.
67    model.pprint()
68
69    # Solve problem by MindOpt solver.
70    opt = SolverFactory("mindo_direct")
71    
72    # Set options.
73    opt.options['Method'] = -1
74    opt.options['IPM/PrimalTolerance'] = 1e-10
75    
76    # Solve.
77    results = opt.solve(model)
78
79    # Summary of result.
80    results.write()
81
82    if (results.solver.status == SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal):
83        print("The solution is optimal and feasible.")
84        model.Variable.display()
85        model.dual.display()
86        model.obj.display()
87    elif (results.solver.termination_condition == TerminationCondition.infeasible):
88        print("The model is infeasible.")
89        print("Solver Status: ",  results.solver.status)
90    else:
91        print("Something else is wrong.")
92        print("Solver Status: ",  results.solver.status)

See Pyomo Gallery for other Pyomo examples.