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')