# 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


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)


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