## Optimization with Constraints in Python

Aug. 24, 2018, 8:20 p.m.

## Motivation

It is useful to be able to optimize the parameters of a function in order to mimimize or maximize some value, while at the same time obeying some constraints on the possible values of the parameter values.

One example of where this comes in useful is for optimizing the allocation of monetary funds to different assets (eg stocks). You have a set of assets you want to consider investing in, and you want to know what portion of your funds to allocate to each one of them in order to maximize your returns. In this case, the constraints are the following:

• You can only allocate a value between 0 to 1.0 for any particular asset.
• because you cannot allocate more money than whats in your fund.
• The sum of allocations must add up to 1.0

## Toy example in Python

We can make use of Scipy's optimization functions to perform optimization with constraints.

Below is a toy example that demonstrates how the minimize function can be used.

The toy problem being addressed is the following:

• The parameters available represent the lengths of the sides of a rectangle.
• We want to find the lengths that will maximize the area of the rectangle, given the following constraints:
• the sum of the sides must be less than 5.
• the length of one of the sides must be exactly twice as big as the other one.
```from scipy import optimize

def area(w):
""" Given the weights (lengths of each side) of a rectangle, it returns
the area.
"""
return w * w

def calculate_error(w):
""" Since we want the greatest area possible, but are performing a
minimization oprtation, we turn the area into a negative as the loss
"""
return -area(w)

""" Add a new set of weights to the list of historical weight estimates """
historical_weights.append(w)

# constraint 1 - the sum of the two sides must be less than 5
def constraint_func1(w):
return 5 - w - w

# constraint 2 - the length of one side must be twice as long as the other
def constraint_func2(w):
return w - 2*w

constraint = ({"type": "eq", "fun": constraint_func1},
{"type": "ineq", "fun": constraint_func2}
)

w0 = [0.1,4.9]               # initial guess
historical_weights = [w0]    # estimates at each iteration of optimization
result = optimize.minimize(
fun=calculate_error,     # func that calculates value we want to optimize
x0=w0,                   # Initial weights
method="SLSQP",          # Optimization algorithm to use
# args=(data),           # other positional args to pass to error function
# bounds=((0,5), (0,5)), # constrain upper and lower values for weights
constraints=constraint,  # Constraints
callback=add2history,    # A function called after every iteration
options={"disp":True}    # print feedback
)

print("Optimal Weights: {}\nOptimal Area: {}\nSum of Lengths: {}".format(result.x, area(result.x), result.x.sum()))
print("Historical weights: ", historical_weights)
```
```[OUTPUT]
Optimization terminated successfully.    (Exit mode 0)
Current function value: -5.555555555555555
Iterations: 2
Function evaluations: 8

Optimal Weights: [1.66666667 3.33333333]
Optimal Area: 5.555555555555555
Sum of Lengths: 5.0
Historical weights:  [[0.1, 4.9], array([1.66666667, 3.33333333]), array([1.66666667, 3.33333333]), array([1.66666667, 3.33333333]), array([1.66666667, 3.33333333])]
```