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:

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:

from scipy import optimize

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

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)

def add2history(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[0] - w[1]

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

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
            Gradient evaluations: 2


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

Comments

Note you can comment without any login by:

  1. Typing your comment
  2. Selecting "sign up with Disqus"
  3. Then checking "I'd rather post as a guest"