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/macOS 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>/lib/pyomo/mindopt_pyomo.py
Users need to move the interface file to the current working directory first, and load the MindoptDirect
class defined in this module in the Python code:
30from mindopt_pyomo import MindoptDirect
Meanwhile, user need to load Pyomo:
25from pyomo.environ import *
26from pyomo.core.base.initializer import Initializer
27from pyomo.opt import SolverFactory
28from pyomo.opt import SolverStatus, TerminationCondition
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.
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
39# Define variables and constraints.
40variable_names = ['x0', 'x1', 'x2', 'x3']
41var_lb = {'x0':0, 'x1':0, 'x2':0, 'x3':0}
42var_ub = {'x0':10, 'x1':None, 'x2':None, 'x3':None}
43objective_coeff = {'x0': 1, 'x1': 1, 'x2': 1, 'x3': 1}
44constraint_names = ['c0', 'c1']
45constraint_bound = {'c0': 1, 'c1': 1}
46constraint_coeff = {('x0', 'c0'): 1, ('x1', 'c0'): 1, ('x2', 'c0'): 2, ('x3', 'c0'): 3,
47 ('x0', 'c1'): 1, ('x1', 'c1'): -1, ('x2', 'c1'): 0, ('x3', 'c1'): 6}
48
49# Create model.
50model = ConcreteModel(name="ex1")
51
52# Build decision variables.
53model.Set = Set(initialize=variable_names)
54model.Variable = Var(model.Set, within=NonNegativeReals, bounds=fb)
55
56# Objective.
57model.obj = Objective(expr=sum(objective_coeff[var_name] * model.Variable[var_name] for var_name in variable_names), sense=minimize)
58
59# Constraints.
60model.dual = Suffix(direction=Suffix.IMPORT)
61model.cons1 = Constraint(expr = ConstraintsRule(model, 'c0') >= constraint_bound['c0'])
62model.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):
67# Solve problem by MindOpt solver.
68opt = SolverFactory("mindopt_direct")
69
70# Set options.
71opt.options['Method'] = -1
72opt.options['IPM/PrimalTolerance'] = 1e-10
Finally, call Pyomo’s solving function solve()
to solve and get the relevant results:
74# Solve.
75results = opt.solve(model)
76
77if (results.solver.status == SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal):
78 print("The solution is optimal and feasible.")
79 model.Variable.display()
80 model.dual.display()
81 model.obj.display()
82elif (results.solver.termination_condition == TerminationCondition.infeasible):
83 print("The model is infeasible.")
84 print("Solver Status: ", results.solver.status)
85else:
86 print("Something else is wrong.")
87 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>/lib/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
Meanwhile, users need to load Pyomo:
from pyomo.environ import * from pyomo.core.base.initializer import Initializer from pyomo.opt import SolverFactory from pyomo.opt import SolverStatus, TerminationCondition
Next, we call the Pyomo API to build the optimization problem. 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) print(solver._solver_model.objval)
After solving, you can also use the functions provided by Persistent to obtain model attributes/parameters, and you can also modify the original model.
# Retrieving variable attributes var_attr = solver.get_var_attr(model.x['A'], 'lb') print(var_attr) # Setting and retrieving MindOpt parameters solver.set_mindopt_param("MaxTime", 1000) solver.set_mindopt_param("MaxTi*", 100) print(solver.get_mindopt_param_info("MaxTi*"))
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 variables y belong to the same SOS group, the following code can be implemented:
from pyomo.environ import * from mindopt_pyomo import MindoptDirect import pyomo.environ as pyo N = 1 model = pyo.ConcreteModel() model.x = pyo.Var([1], domain=pyo.NonNegativeReals, bounds=(0,40)) model.A = pyo.Set(initialize=[1,2,4,6]) model.y = pyo.Var(model.A, domain=pyo.NonNegativeReals, bounds=(0,2)) model.OBJ = pyo.Objective( expr=(1*model.x[1]+ 2*model.y[1]+ 3*model.y[2]+ -0.1*model.y[4]+ 0.5*model.y[6]) ) model.ConstraintYmin = pyo.Constraint( expr = (model.x[1]+ model.y[1]+ model.y[2]+ model.y[6] >= 0.25 ) ) model.mysos = pyo.SOSConstraint( var=model.y, sos=N ) opt = pyo.SolverFactory('mindopt_direct') opt.solve(model, tee=False) print(opt._solver_model.objval)
In the above example, the parameter var of SOSConstraint
is used to specify the rule that the variable belongs to the SOS collection. In this example, we group variables 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.
import pyomo.environ as pe from mindopt_persistent import MindoptPersistent m = pe.ConcreteModel() m.x = pe.Var(bounds=(0, 4)) m.y = pe.Var(within=pe.Integers, bounds=(0, None)) m.obj = pe.Objective(expr=2*m.x + m.y) m.cons = pe.ConstraintList() # for the cutting planes def my_callback(model, where): where_names = { 3: "MIPSTART", 4: "MIPSOL", 5: "MIPNODE" } print("where = {}".format(where_names[where])) # Count call times model._calls += 1 # When using the Python SDK, the model passed to the callback is a presolved model. Note that the C SDK uses the original model. # Print the constraint matrix of the presolved model here. The getA() function returns a scipy.sparse.csc_matrix. # The constraint matrix returned by getA() is a reference, and any modifications to the constraint matrix should not be allowed as it may lead to unforeseen issues. print(model.getA()) cur_sols = model.cbGetSolution(model.getVars()) # Print the current best solutions print(cur_sols) rel_sols = model.cbGetNodeRel(model.getVars()) # Print the current relaxation solution print(rel_sols)
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 = pe.SolverFactory('mindopt_persistent') opt.set_instance(m) # 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.initializer import Initializer
27from pyomo.opt import SolverFactory
28from pyomo.opt import SolverStatus, TerminationCondition
29
30from mindopt_pyomo import MindoptDirect
31
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
39# Define variables and constraints.
40variable_names = ['x0', 'x1', 'x2', 'x3']
41var_lb = {'x0':0, 'x1':0, 'x2':0, 'x3':0}
42var_ub = {'x0':10, 'x1':None, 'x2':None, 'x3':None}
43objective_coeff = {'x0': 1, 'x1': 1, 'x2': 1, 'x3': 1}
44constraint_names = ['c0', 'c1']
45constraint_bound = {'c0': 1, 'c1': 1}
46constraint_coeff = {('x0', 'c0'): 1, ('x1', 'c0'): 1, ('x2', 'c0'): 2, ('x3', 'c0'): 3,
47 ('x0', 'c1'): 1, ('x1', 'c1'): -1, ('x2', 'c1'): 0, ('x3', 'c1'): 6}
48
49# Create model.
50model = ConcreteModel(name="ex1")
51
52# Build decision variables.
53model.Set = Set(initialize=variable_names)
54model.Variable = Var(model.Set, within=NonNegativeReals, bounds=fb)
55
56# Objective.
57model.obj = Objective(expr=sum(objective_coeff[var_name] * model.Variable[var_name] for var_name in variable_names), sense=minimize)
58
59# Constraints.
60model.dual = Suffix(direction=Suffix.IMPORT)
61model.cons1 = Constraint(expr = ConstraintsRule(model, 'c0') >= constraint_bound['c0'])
62model.cons2 = Constraint(expr = ConstraintsRule(model, 'c1') == constraint_bound['c1'])
63
64# Print formulation of model.
65model.pprint()
66
67# Solve problem by MindOpt solver.
68opt = SolverFactory("mindopt_direct")
69
70# Set options.
71opt.options['Method'] = -1
72opt.options['IPM/PrimalTolerance'] = 1e-10
73
74# Solve.
75results = opt.solve(model)
76
77if (results.solver.status == SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal):
78 print("The solution is optimal and feasible.")
79 model.Variable.display()
80 model.dual.display()
81 model.obj.display()
82elif (results.solver.termination_condition == TerminationCondition.infeasible):
83 print("The model is infeasible.")
84 print("Solver Status: ", results.solver.status)
85else:
86 print("Something else is wrong.")
87 print("Solver Status: ", results.solver.status)
See Pyomo Gallery for other Pyomo examples.