Model Components¶
In this section, several model components are discussed with examples. See Examples to learn more about how you can use these components to define optimization models.
Expressions¶
Expression
objects represent linear and nonlinear mathematical
expressions in sasoptpy.
Creating expressions¶
You can create an Expression
object as follows:
In [1]: profit = so.Expression(5 * sales - 3 * material, name='profit')
In [2]: print(repr(profit))
sasoptpy.Expression(exp = 5 * sales - 3 * material, name='profit')
Nonlinear expressions¶
Expression
objects are linear by default. It is possible to create
nonlinear expressions, but there are some limitations.
In [3]: nonexp = sales ** 2 + (1 / material) ** 3
In [4]: print(nonexp)
(sales) ** (2) + ((1) / (material)) ** (3)
Currently, it is not possible to get or print values of nonlinear expressions. Moreover, if your model includes a nonlinear expression, you need to use SAS Viya 3.4 or later or any SAS/OR release for solving your problem.
To use mathematical operations, you need to import sasoptpy.math functions.
Mathematical expressions¶
sasoptpy provides mathematical functions for generating mathematical expressions to be used in optimization models.
You need to import sasoptpy.math into your code to use these functions. Available mathematical functions are listed in Math Functions.
In [5]: import sasoptpy.math as sm
In [6]: newexp = sm.max(sales, 10) ** 2
In [7]: print(newexp._expr())
(max(sales , 10)) ^ (2)
In [8]: import sasoptpy.math as sm
In [9]: angle = so.Variable(name='angle')
In [10]: newexp = sm.sin(angle) ** 2 + sm.cos(angle) ** 2
In [11]: print(newexp._expr())
(sin(angle)) ^ (2) + (cos(angle)) ^ (2)
Operations¶
Getting the current value
After the solve is completed, you can obtain the current value of an expression by using the
Expression.get_value()
method:
>>> print(profit.get_value())
42.0
Getting the dual value
You can retrieve the dual values of Expression
objects by using
Variable.get_dual()
and Constraint.get_dual()
methods.
>>> m.solve()
>>> ...
>>> print(x.get_dual())
1.0
Addition
You can add, subtract, multiply and divide components using regular Python functionality:
In [12]: tax = 0.5
In [13]: profit_after_tax = profit - tax
In [14]: print(repr(profit_after_tax))
sasoptpy.Expression(exp = 5 * sales - 3 * material - 0.5, name=None)
In [15]: share = 0.2 * profit
In [16]: print(share)
sales - 0.6 * material
Summation
For faster summations compared to Python’s native sum
function,
sasoptpy provides expr_sum()
(formerly quick_sum()
)
In [17]: import time
In [18]: x = m.add_variables(1000, name='x')
In [19]: t0 = time.time()
In [20]: e = so.expr_sum(2 * x[i] for i in range(1000))
In [21]: print(time.time()-t0)
0.035427093505859375
In [22]: t0 = time.time()
In [23]: f = sum(2 * x[i] for i in range(1000))
In [24]: print(time.time()-t0)
0.9818449020385742
Renaming an expression¶
You can rename expressions by using the Expression.set_name()
method:
In [25]: e = so.Expression(x[5] + 2 * x[6], name='e1')
In [26]: print(repr(e))
sasoptpy.Expression(exp = x[5] + 2 * x[6], name='e1')
In [27]: e.set_name('e2');
In [28]: print(repr(e))
sasoptpy.Expression(exp = x[5] + 2 * x[6], name='e2')
Copying an expression¶
You can copy an Expression
by using the Expression.copy()
method:
In [29]: copy_profit = profit.copy(name='copy_profit')
In [30]: print(repr(copy_profit))
sasoptpy.Expression(exp = 5 * sales - 3 * material, name='copy_profit')
Objective Functions¶
Setting and getting an objective function¶
You can use any valid Expression
as the objective function of a model.
You can also use an existing expression as an objective function by using
the Model.set_objective()
method. The objective function of a model can
be obtained by using the Model.get_objective()
method.
>>> profit = so.Expression(5 * sales - 2 * material, name='profit')
>>> m.set_objective(profit, so.MAX)
>>> print(m.get_objective())
- 2.0 * material + 5.0 * sales
Getting the value¶
After a solve, you can retrieve the objective value by using the
Model.get_objective_value()
method.
>>> m.solve()
>>> print(m.get_objective_value())
42.0
Variables¶
Creating variables¶
You can create variables either stand-alone or inside a model.
Creating a variable outside a model
The first way to create a variable uses the default constructor:
>>> x = so.Variable(vartype=so.INT, ub=5, name='x')
When a variable is created separately, it needs to be included (or added) inside the model:
>>> y = so.Variable(name='y', lb=5)
>>> m.add_variable(y)
Equivalently, you could do this in one step:
>>> y = m.add_variable(name='y', lb=5)
Creating a variable inside a model
The second way is to use Model.add_variable()
. This method creates
a Variable
object and returns a pointer.
>>> x = m.add_variable(vartype=so.INT, ub=5, name='x')
Arguments¶
There are three types of variables: continuous variables, integer variables,
and binary variables.
Continuous variables are the default type, which
you can specify by using the vartype=so.CONT
argument.
You can create integer variables and binary
variables by using the vartype=so.INT
and vartype=so.BIN
arguments, respectively.
The default lower bound for variables is 0, and the upper bound is infinity. Name is a required argument.
Changing bounds¶
The Variable.set_bounds()
method changes the bounds of a variable.
>>> x = so.Variable(name='x', lb=0, ub=20)
>>> print(repr(x))
sasoptpy.Variable(name='x', lb=0, ub=20, vartype='CONT')
>>> x.set_bounds(lb=5, ub=15)
>>> print(repr(x))
sasoptpy.Variable(name='x', lb=5, ub=15, vartype='CONT')
Setting initial values¶
You can pass the initial values of variables to the solvers for certain problems.
The Variable.set_init()
method changes the initial value for variables.
You can set also this value at the creation of the variable.
>>> x.set_init(5)
>>> print(repr(x))
sasoptpy.Variable(name='x', ub=20, init=5, vartype='CONT')
Working with a set of variables¶
You can create a set of variables by using a single index or by using multiple indices.
Valid index sets include list, dict, and pandas.Index
objects.
See Handling Data for more information about allowed index types.
Creating a set of variables outside a model
>>> production = VariableGroup(PERIODS, vartype=so.INT, name='production',
lb=min_production)
>>> print(repr(production))
sasoptpy.VariableGroup(['Period1', 'Period2', 'Period3'], name='production')
>>> m.include(production)
Creating a set of variables inside a model
>>> production = m.add_variables(PERIODS, vartype=so.INT,
name='production', lb=min_production)
>>> print(production)
>>> print(repr(production))
Variable Group (production) [
[Period1: production['Period1',]]
[Period2: production['Period2',]]
[Period3: production['Period3',]]
]
sasoptpy.VariableGroup(['Period1', 'Period2', 'Period3'],
name='production')
Constraints¶
Creating constraints¶
Similar to Variable
objects, you can create Constraint
objects inside or outside optimization models.
Creating a constraint outside a model
>>> c1 = so.Constraint(3 * x - 5 * y <= 10, name='c1')
>>> print(repr(c1))
sasoptpy.Constraint( - 5.0 * y + 3.0 * x <= 10, name='c1')
Creating a constraint inside a model
>>> c1 = m.add_constraint(3 * x - 5 * y <= 10, name='c1')
>>> print(repr(c1))
sasoptpy.Constraint( - 5.0 * y + 3.0 * x <= 10, name='c1')
Modifying variable coefficients¶
You can update the coefficient of a variable inside a constraint by using the
Constraint.update_var_coef()
method:
>>> c1 = so.Constraint(exp=3 * x - 5 * y <= 10, name='c1')
>>> print(repr(c1))
sasoptpy.Constraint( - 5.0 * y + 3.0 * x <= 10, name='c1')
>>> c1.update_var_coef(x, -1)
>>> print(repr(c1))
sasoptpy.Constraint( - 5.0 * y - x <= 10, name='c1')
Working with a set of constraints¶
You can add a set of constraints by using a single index or by using multiple indices.
Valid index sets include list, dict, and pandas.Index
objects.
See Handling Data for more information about allowed index types.
Creating a set of constraints outside a model
>>> z = so.VariableGroup(2, ['a', 'b', 'c'], name='z', lb=0, ub=10)
>>> cg = so.ConstraintGroup((2 * z[i, j] + 3 * z[i-1, j] >= 2 for i in
[1] for j in ['a', 'b', 'c']), name='cg')
>>> print(cg)
Constraint Group (cg) [
[(1, 'a'): 3.0 * z[0, 'a'] + 2.0 * z[1, 'a'] >= 2]
[(1, 'b'): 3.0 * z[0, 'b'] + 2.0 * z[1, 'b'] >= 2]
[(1, 'c'): 2.0 * z[1, 'c'] + 3.0 * z[0, 'c'] >= 2]
]
Creating a set of constraints inside a model
>>> z = so.VariableGroup(2, ['a', 'b', 'c'], name='z', lb=0, ub=10)
>>> cg2 = m.add_constraints((2 * z[i, j] + 3 * z[i-1, j] >= 2 for i in
[1] for j in ['a', 'b', 'c']), name='cg2')
>>> print(cg2)
Constraint Group (cg2) [
[(1, 'a'): 2.0 * z[1, 'a'] + 3.0 * z[0, 'a'] >= 2]
[(1, 'b'): 3.0 * z[0, 'b'] + 2.0 * z[1, 'b'] >= 2]
[(1, 'c'): 2.0 * z[1, 'c'] + 3.0 * z[0, 'c'] >= 2]
]
Range constraints¶
You can give a range for an expression by using a list of two values (lower and upper bound) after an == sign:
>>> x = m.add_variable(name='x')
>>> y = m.add_variable(name='y')
>>> c1 = m.add_constraint(x + 2*y == [2,9], name='c1')
>>> print(repr(c1))
sasoptpy.Constraint( x + 2.0 * y == [2, 9], name='c1')