How to define parameters which change value depending on variable in CXYPY - python

I'm trying to solve a convex optimisation problem using CVXPY. I've values x, y which are 150 and 60. These are the initial demand of 2 timeslots. The objective is to minimise the total cost.
Total cost = x * price1 + y * price2 + penalty
Penalty = (150 - x) * 6
So I want to find what are the optimal values of x and y. price1 and price 2 are based on the demand value.
My code is
import cvxpy as cp
def price_func(demand):
prices = [14, 15, ......., 36.3]
limits = [0.51, 0.61, ......, 1]
for i in range(len(limits)):
if limits[i] * 155 >= demand:
return prices[i]
x = cp.Variable()
y = cp.Variable()
x.value = 150
y.value = 60
constraints = [x + y == 210,
x >= y,
y >= 60]
# Form objective.
obj = cp.Minimize((x*price_func(x.value) + y*price_func(y.value)) * 0.5 + (150 - x.value) * 6)
# Form and solve problem.
prob = cp.Problem(obj, constraints)
prob.solve(warm_start=True)
However, as I understand, my objective in cvxpy looks as
minimize (var0 # 144.6 + var1 # 14.0) # 0.5
This implies cvxpy doesn't use my price function. But rather it as a fixed value.
How can I fix that issue and make sure cvxpy pick my correct objective function?

Related

Problem while looping variables for Scipy Optimization (SLSQP, COBYLA)

I have a constrained optimization problem where I am trying to minimize an objective function of 100+ variables which is of the form
Min F(x) = f(x1) + f(x2) + ... + f(xn)
Subject to functional constraint
(g(x1) + g(x2) + ... + g(xn))/(f(x1) + f(x2) + ... + f(xn)) - constant >= 0
I also have individual bounds for each variable x1, x2, x3...xn
a <= x1 <= b
c <= x2 <= d
...
For this, I wrote a python script, using the scipy.optimize.minimize implementation with constraints and bounds, but I am unable to fulfill my bounds and constraints in the solutions. These are all cases where optimization could converge to a solution (message: success)
Here is a sample of my code:
df is my pandas dataset
B(x) is LogNorm transform based on x and other constants
Values U, c, lb, ub are pre-calculated constant dictionaries for each index in df
import scipy
df = pd.DataFrame(..)
k = set(df.index.values) ## list of indexes to iterate on
val = 0.25 ## Arbitrary
def obj(x):
fn = 0
for n,i in enumerate(k):
x0 = x[n]
fn1 = (U[i]) * B(x0) * (x0)
fn += fn1
return fn
def cons(x):
cn = 1
c1 = 0
c2 = 0
for n,i in enumerate(k):
x0 = x[n]
c1 += (U[i]) * (B(x0) * (x0 - c[i])
c2 += (U[i]) * (B(x0) * (x0)
cn = c1/(c2)
return cn - val
const = [{'type':'ineq', 'fun':cons}]
bnds = tuple((lb[i], ub[i]) for i in k) ## Lower, Upper for each element ((lb1, ub1), (lb2, ub2)...)
x_init = [lb[i] for i in k] ## for eg. starting from lower bound
## Solution
sol = scipy.optimize.minimize(obj, x_init, method = 'COBYLA', bounds = bnds, constraints = const)
I have more pointed questions if that helps:
Is there a way to construct the same equation concisely/ without the use of loops (given the number of variables could depend on input data and I have no control over it)?
Is there any noticeable issue in my application of bounds? I can't seem to get the final values of all variables follow individual bounds.
Similarly, is there a visible flaw in the construction on constraint equation? My results often DO NOT follow the constraints is repeated runs with different inputs.
Any help with either of the questions can help me progress further at work.
I have also looked into a Lagrangian solution of the same but so far I am unable to solve it for undefined number of (n) variables.
Thanks!

Scipy minimise, How to get an int value array as an output

I have this kind of data :
import random
data=random.sample(range(1, 100), 5)
x= [-1,1,1,-1,1]
def f(x,data):
prod=[a * b for a, b in zip(data, x)]
result=abs(sum(prod))
return result
I Would like to find the best x composed of -1 or 1 to minimize the value of f(x)
Maybe we can use scipy.minimise() but how can we add the -1 or 1 as a constrain on the value inside of x ?
Does somebody have an idea ?
You want to solve a mixed-integer linear programming problem (MILP), which aren't supported yet by scipy.optimize.
However, you can use a modelling package like PuLP to formulate your MILP and pass it to a MILP solver. Note that your MIP can be formulated as
(P)
min |f(x)| = |d_0 * x_0 + ... + d_n * x_n|
s.t. x_i ∈ {-1, 1} ∀ i = 0,...,n
which is the same as
(P')
min |f(x)| = |d_0 * (2*x_0 - 1) + ... + d_n * (2*x_n - 1)|
s.t. x_i ∈ {0, 1} ∀ i = 0,...,n
and can be implemented like this
min abs_obj
s.t. f(x) <= abs_obj
f(x) >= -1.0*abs_obj
x_i ∈ {0, 1} ∀ i = 0,...,n
In code:
import pulp
import random
data = random.sample(range(1, 100), 5)
# pulp model
mdl = pulp.LpProblem("our_model", sense=pulp.LpMinimize)
# the binary variables x
x = pulp.LpVariable.dicts("x", range(5), cat="Binary")
# the variable that stores the absolute value of the objective
abs_obj = pulp.LpVariable("abs_obj")
# set the MIP objective
mdl += abs_obj
# Define the objective: |f(x)| = abs_obj
mdl += pulp.lpSum((2 * x[i] - 1) * data[i] for i in range(5)) <= abs_obj
mdl += pulp.lpSum((2 * x[i] - 1) * data[i] for i in range(5)) >= -1.0*abs_obj
# solve the problem
mdl.solve()
# your solution
signs = [1 if var.varValue > 0 else -1 for var in x.values()]
Alternatively, if you don't want to use another package, you can use scipy.optimize.minimize and implement a simple penalty method. Thereby you solve the problem (P') by solving the penalty problem
min |f(x)| + Ɛ * (x_0 * (1 - x_0) + ... + x_n * (1 - x_n))
with 0 <= x_i <= 1
where Ɛ is a given penalty parameter. Here, the idea is that the right penalty term equals zero for an integer solution.
Note that as the case may be that you need to solve a sequence of penalty problems to achieve convergence to an integer solution. Thus, I'd highly recommend sticking to a MILP solver instead of implementing a penalty method on your own.
Yes, you can do it using scipy.optimize.minimize:
from scipy.optimize import minimize
minimize(f, [0] * len(data), args=data, bounds=[(-1, 1)] * len(data))
This call minimizes f which you defined in the original post.
It passes a zero array as an initial guess for the minimization problem.
The argument f requires is 'data' which is specified by the argument 'args'.
The constraints you want are specified by the argument 'bounds' as a list of min/max tuples with the length of the input data.

Cvxpy portfolio optimization with constraint on the maximum number of assets

I'm using cvxpy library to perform Portfolio Optimization.
However, instead of using the Markowitz covariance model, I would like to introduce new variables where yi variable is a binary variable that assumes value 1 if the asset i is included in the portfolio and 0 otherwise; m is the maximum number of assets I want to include in the portfolio; r is the return I want to get.
The Markowitz model, with constraint on the return is the following:
import numpy as np
import pandas as pd
from cvxpy import *
# assets names
tickers = ["AAA", "BBB", "CCC", "DDD", "EEE", "FFF"]
# return matrix
ret = pd.DataFrame(np.random.rand(1,6), columns = tickers)
# Variance_Coviariance matrix
covm = pd.DataFrame(np.random.rand(6,6), columns = tickers, index = tickers)
# problem setting
x = Variable(len(tickers)) # xi variables
er = np.asarray(ret.T) * x # expected return
min_ret = 0.2 # minimum return
risk = quad_form(x, np.asmatrix(covm)) # risk
prob = Problem(Minimize(risk), # problem setting function
[sum(x) == 1, er >= min_ret, x >= 0])
prob.solve()
The solution of this problem gives out a percentage to invest in each asset. But what if I want to invest on a limited number of asset m?
In order to do that I need to implement yi variables and make sure that their sum is equal to m
Hence, it should be something like this:
x = Variable(n)
er = np.asarray(ret.T) * x
risk = quad_form(x, np.asmatrix(covm))
y = Variable(n, boolean=True) #adding boolean variables
prob = Problem(Minimize(risk), [sum(x) == 1, er >= min_ret, x >= 0, sum(y) == k, sum(x) <= sum(y)])
prob.solve()
print(x.value)
print(y.value)
Unfortunately, this last chunk of code doesn't produce any result. Do you know why? Is there another method to solve this problem?
In short, you have to link the variables x and y.
In case of long only constraints:
eps = 1e-5
[-1 + eps <= x - y, x - y <= 0]
This will set y to 1 if x > 0 and y to 0 if x == 0.
To make it work properly and not to be bothered by assets being just marginally above 0, you should also introduce a buy-in threshold.
[x - y >= buy_in_threshold - 1]
Note, that this problem is a mixed integer problem.
The ECOS BB solver can deal with that, if the problem remains small. Otherwise, you will need a commercial grade optimizer.

Access current time step in scipy.integrate.odeint within the function

Is there a way to access what the current time step is in scipy.integrate.odeint?
I am trying to solve a system of ODEs where the form of the ode depends on whether or not a population will be depleted. Basically I take from population x provided x doesn't go below a threshold. If the amount I need to take this timestep is greater than that threshold I will take all of x to that point and the rest from z.
I am trying to do this by checking how much I will take this time step, and then allocating between populations x and z in the DEs.
To do this I need to be able to access the step size within the ODE solver to calculate what will be taken this time step. I am using scipy.integrate.odeint - is there a way to access the time step within the function defining the odes?
Alternatively, can you access what the last time was in the solver? I know it won't necessarily be the next time step, but it's likely a good enough approximation for me if that is the best I can do. Or is there another option I've not thought of to do this?
The below MWE is not my system of equations but what I could come up with to try to illustrate what I'm doing. The problem is that on the first time step, if the time step were 1 then the population will go too low, but since the timestep will be small, initially you can take all from x.
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
plt.interactive(False)
tend = 5
tspan = np.linspace(0.0, tend, 1000)
A = 3
B = 4.09
C = 1.96
D = 2.29
def odefunc(P,t):
x = P[0]
y = P[1]
z = P[2]
if A * x - B * x * y < 0.6:
dxdt = A/5 * x
dydt = -C * y + D * x * y
dzdt = - B * z * y
else:
dxdt = A * x - B * x * y
dydt = -C * y + D * x * y
dzdt = 0
dPdt = np.ravel([dxdt, dydt, dzdt])
return dPdt
init = ([0.75,0.95,100])
sol = odeint(odefunc, init, tspan, hmax = 0.01)
x = sol[:, 0]
y = sol[:, 1]
z = sol[:, 2]
plt.figure(1)
plt.plot(tspan,x)
plt.plot(tspan,y)
plt.plot(tspan,z)
Of course you can hack something together that might work.
You could log t but you have to be aware that the values
might not be constantly increasing. This depends on the ODE algorithm and how it works (forward, backward, and central finite differences).
But it will give you an idea where about you are.
logger = [] # visible in odefunc
def odefunc(P,t):
x = P[0]
y = P[1]
z = P[2]
print(t)
logger.append(t)
if logger: # if the list is not empty
if logger[-1] > 2.5: # then read the last value
print('hua!')
if A * x - B * x * y < 0.6:
dxdt = A/5 * x
dydt = -C * y + D * x * y
dzdt = - B * z * y
else:
dxdt = A * x - B * x * y
dydt = -C * y + D * x * y
dzdt = 0
dPdt = np.ravel([dxdt, dydt, dzdt])
return dPdt
print(logger)
As pointed out in the another answer, time may not be strictly increasing at each call to the ODE function in odeint, especially for stiff problems.
The most robust way to handle this kind of discontinuity in the ode function is to use an event to find the location of the zero of (A * x - B * x * y) - 0.6 in your example. For a discontinuous solution, use a terminal event to stop the computation precisely at the zero, and then change the ode function. In solve_ivp you can do this with the events parameter. See the solve ivp documentation and specifically the examples related to the cannonball trajectories. odeint does not support events, and solve_ivp has an LSODA method available that calls the same Fortran library as odeint.
Here is a short example, but you may want to additionally check that sol1 reached the terminal event before solving for sol2.
from scipy.integrate import solve_ivp
tend = 10
def discontinuity_zero(t, y):
return y[0] - 10
discontinuity_zero.terminal = True
def ode_func1(t, y):
return y
def ode_func2 (t, y):
return -y**2
sol1 = solve_ivp(ode_func1, t_span=[0, tend], y0=[1], events=discontinuity_zero, rtol=1e-8)
t1 = sol1.t[-1]
y1 = [sol1.y[0, -1]]
print(f'time={t1} y={y1} discontinuity_zero={discontinuity_zero(t1, y1)}')
sol2 = solve_ivp(ode_func2, t_span=[t1, tend], y0=y1, rtol=1e-8)
plt.plot(sol1.t, sol1.y[0,:])
plt.plot(sol2.t, sol2.y[0,:])
plt.show()
This prints the following, where the time of the discontinuity is accurate to 7 digits.
time=2.302584885712467 y=[10.000000000000002] discontinuity_zero=1.7763568394002505e-15

How to use a variable as a divisor in PuLP

I was trying to resolve a LP problem with a constraint that is calculated by dividing variable A by variable B.
The simple version of the problem is as below:
The product is made by two materials (A and B)
% of A should be greater than 50%
% of B should be less than 40%
Total amount of A and B are 100
Objective: What's the minimum amount of A?
The code is like:
from pulp import *
prob = LpProblem('Simple problem', LpMinimize)
x = LpVariable('x', 0, None, 'Integer')
y = LpVariable('y', 0, None, 'Integer')
prob += x
prob += x / (x + y) > 0.5 # <== Where the error happens
prob += y / (x + y) < 0.4
prob += x + y == 100
prob.solve()
print 'Result: %s' % LpStatus[prob.status]
print 'Amount of A: %s' % value(prob.objective)
However I'm getting an error message saying:
TypeError: Expressions cannot be divided by a non-constant expression
It looks like PuLP does not support variable as divisor.
https://github.com/coin-or/pulp/blob/master/src/pulp/pulp.py#L800
Any idea? If PuLP is not the right library to use, I'm happy to switch to any library that fits in.
UPDATE 27 Nov 2015
For some reason, the sample above does not make sense (not working as expected). I am very newbie to this library. Maybe it's not the right one to solve my problem at all. So if any one has suggestions of other libraries, it'd be appreciated.
BTW, Koen Peters's advice below is great. The error is gone after taking his advice. Thank you.
Linear Programming doesn't understand divisions, hence the error :)
You have to reformulate it so that the division is formulated linearly.
In this case:
prob += x / (x + y) > 0.5
prob += y / (x + y) < 0.4
is equivalent to:
prob += x > 0.5 * (x + y)
prob += y < 0.4 * (x + y)
Which are linear constraints.
Good luck!
I felt like zero shouldn't be allowed in my solution — and I included a variable that was the sum of x and y (think you're referring to it as A).
from pulp import LpProblem, LpStatus, LpVariable
from pulp import LpMinimize, LpMaximize, lpSum, value
# I feel like zero shouldn't be allowed for lower bound...
x = LpVariable("x", lowBound=1, cat="Integer")
y = LpVariable("y", lowBound=1, cat="Integer")
z = LpVariable("z", lowBound=1, cat="Integer")
model = LpProblem("Divison problem", LpMinimize)
model += x
model += z == x + y
model += z == 100
# Rather than division, we can use multiplication
model += x >= z * 0.5
model += y <= z * 0.4
model.solve()
print(LpStatus[model.status])
print(f"""
x = {int(x.varValue):>3} # 60
y = {int(y.varValue):>3} # 40
z = {int(z.varValue):>3} # 100
""")

Categories

Resources